You’re not allowed to have circular cell definitions. You can’t have a zoom cell that depends on the svg cell (to set the “transform” attribute via selection.attr) and an svg cell that depends on the zoom cell (to apply the zoom behavior via selection.call).
You could define a standalone zoom behavior without an event listener:
zoom = d3.zoom()
.scaleExtent([1, 18])
.translateExtent([[0,0], [width, height]])
But then you have to modify the zoom instance inside the svg cell to add your zoom event listener, which isn’t clean. You could go the opposite way and have the zoom cell modify the svg element, but that isn’t very clean either (and requires removing the viewof from your svg definition):
{
const s = d3.select(svg);
s.call(d3.zoom()
.on("zoom", () => s.select(".map-layers").attr("transform", d3.event.transform))
.scaleExtent([1, 18])
.translateExtent([[0,0], [width, height]]));
}
If you want to break this out into a separate cell, I’d probably do that by wrapping it in a function rather than using mutation, because the relationship between these two objects (the behavior and the selection) is inherently circular.
function zoom(svg) {
svg.call(d3.zoom()
.on("zoom", () => svg.select(".map-layers").attr("transform", d3.event.transform))
.scaleExtent([1, 18])
.translateExtent([[0,0], [width, height]]));
}
Then you can enable your zoom behavior in the svg cell simply as svg.call(zoom).