function dotEditor(data, options) {
data = data.map(d => ({...d}));
const dots = Plot.dot(data, options);
let chart;
const _render = dots.render;
dots.render = function(index, {color, x, y, r}) {
const g = _render.apply(this, arguments);
let dragDelta;
const drag = d3.drag()
.on("start", function() {
d3.select(this).style("cursor", "grabbing");
})
.on("drag", function({x: dx, y: dy}, i) {
if (!dragDelta) {
const me = d3.select(this).raise();
dragDelta = [+me.attr("cx") - dx, +me.attr("cy") - dy];
}
data[i].x = x.invert(dx);
data[i].y = y.invert(dy);
d3.select(this).attr("cx", dx + dragDelta[0]).attr("cy", dy + dragDelta[1]);
signal();
})
.on("end", function() {
d3.select(this).style("cursor", "grab");
dragDelta = null;
})
const zoom = d3.zoom()
.on("start", function() {
d3.select(this).style("cursor", "grabbing");
})
.on("end", function() {
d3.select(this).style("cursor", "grab");
})
.on("zoom", function({transform: {k}}, i) {
data[i].r = data0[i].r * k * k;
d3.select(this).attr("r", r(data[i].r));
signal();
});
d3.select(g)
.selectAll("circle")
.call(drag)
.call(zoom).on("dblclick.zoom", null)
.style("cursor", "grab")
.on("click", function(event, i) {
data[i].label = `a${Math.random() * 10|0}`;
d3.select(this).attr("fill", color(data[i].label));
signal();
});
requestAnimationFrame(() => {
chart = g.ownerSVGElement;
if (chart?.parentElement?.nodeName === "FIGURE") chart = chart.parentElement;
signal();
});
return g;
}
function signal() {
chart.value = data;
chart.dispatchEvent(new CustomEvent("input", {bubbles: true}));
}
return dots
}