score:2

I believe this will get you most of the way there. You need to update your `circle` attributes in the `zoomed` function along with the other elements:

``````function zoomed() {
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);

// update axes
gX.call(xAxis.scale(new_xScale));
gY.call(yAxis.scale(new_yScale));

//redraw data ppints
points.data(data)
.attr('cx', function(d) {return new_xScale(d.x)})
.attr('cy', function(d) {return new_yScale(d.y)});

// The new part:

// the transform
let trans     = d3.event.transform
// the approximate domain value of the circle 'cx' for converting later
let cx_domain = xScale.invert(200 + margin.left)
// the approximate domain value of the circle 'cy' for converting later
let cy_domain = yScale.invert(200 + margin.top)
// the circle
let circ      =  d3.select('.scatter-group circle')
let rad       = 75

// reset the circle 'cx' and 'cy' according to the transform
circ
.attr('cx',function(d) { return new_xScale(cx_domain)})
.attr('cy',function(d) { return new_yScale(cy_domain)})
// reset the radius by the scaling factor
.attr('r', function(d) { return rad*trans.k })

}
``````

See this fiddle

You'll notice the circle does not scale or move at quite the same rate as the scatter dots. This is possibly because of the use of the `invert` function, because the conversion from `range` to `domain` and back to `range` is imperfect. This issue is documented

For a valid value y in the range, continuous(continuous.invert(y)) approximately equals y; similarly, for a valid value x in the domain, continuous.invert(continuous(x)) approximately equals x. The scale and its inverse may not be exact due to the limitations of floating point precision.

Your original idea to assign dynamic values to `cx`, `cy` and `r` will likely compensate for this, because you can then avoid the inversion.