Distribution:
Points: 1 000

Key concept — D3 data join in updateDots()

D3's data join is the central idea: you bind an array of data to a selection of DOM elements and D3 computes which elements are new (enter), changed (update), or removed (exit). Each case is handled declaratively — you describe what the element should look like, and D3 + SVG take care of rendering. Because every dot is a real <circle> in the DOM, the browser handles hit-testing and CSS transitions for free, but performance degrades fast once you have tens of thousands of elements.

function updateDots(newData) {
  g.selectAll(".dot")
    .data(newData)
    .join(
      // Entering dots — append a circle at r=4
      enter => enter.append("circle")
        .attr("class", "dot")
        .attr("cx", d => xScale(d.x))
        .attr("cy", d => yScale(d.y))
        .attr("r", 4),

      // Updating dots — animate to the new position
      update => update
        .transition().duration(600).ease(d3.easeCubicInOut)
        .attr("cx", d => xScale(d.x))
        .attr("cy", d => yScale(d.y)),

      // Exiting dots — remove immediately
      exit => exit.remove()
    );
}

Hover & color

Every <circle> is a real DOM node, so the browser's CSS engine handles :hover intrinsically — no JavaScript, no hit-testing, no manual repaints. A transition: fill 0.15s rule gives the smooth color change for free. This is one of SVG's biggest ergonomic advantages over canvas-based renderers.

/* SVG circles are DOM nodes — CSS :hover works out of the box */
.dot {
  fill: #f4be00;
  stroke: #0a0032;
  stroke-width: 0.5;
  transition: fill 0.15s;
}

.dot:hover {
  fill: #e5ffff;
  fill-opacity: 1;
}