chart = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const g = svg.append("g").attr("cursor", "grab");
g.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", ({ x }) => x)
.attr("cy", ({ y }) => y)
.attr("r", radius)
.attr("fill", (d, i) => d3.interpolateRainbow(i / 360))
.call(
d3
.zoom()
.scaleExtent([1, 1])
.on("start", dragstarted)
.on("zoom", dragged)
.on("end", dragended)
);
svg.call(
d3
.zoom()
.extent([[0, 0], [width, height]])
.scaleExtent([0.1, 8])
.on("zoom", zoomed)
);
function defaultSubject(event, d, that) {
const p = d3.pointer(event.sourceEvent, that.parentElement);
return d == null ? { x: p[0], y: p[1] } : d;
}
let subject, dx, dy;
function dragstarted(event, d) {
const p = d3.pointer(event.sourceEvent, this.parentElement);
subject = defaultSubject(event, d, this);
dx = subject.x - p[0] || 0;
dy = subject.y - p[1] || 0;
console.log("dragstart", { dx, dy, p, event, d });
d3.select(this).raise();
g.attr("cursor", "grabbing");
}
function dragged(event, d) {
const p = d3.pointer(event.sourceEvent, this.parentElement);
event.x = dx + p[0];
event.y = dy + p[1];
console.log("dragged", { dx, dy, p, x: event.x, y: event.y, event, d });
d3.select(this)
.attr("cx", (d.x = event.x))
.attr("cy", (d.y = event.y));
}
function dragended() {
g.attr("cursor", "grab");
}
function zoomed(event) {
g.attr("transform", event.transform);
}
return svg.node();
}