chart = {
let selected = mutable points[0];
const svg = d3.create("svg")
.attr("viewBox", [-14, 0, width + 28, height])
.attr("tabindex", 1)
.attr("pointer-events", "all")
.call(d3.drag()
.subject(dragsubject)
.on("start", dragstarted)
.on("drag", dragged));
svg.append("style").text(`
svg[tabindex] {
display: block;
margin: 0 -14px;
border: solid 2px transparent;
box-sizing: border-box;
}
svg[tabindex]:focus {
outline: none;
border: solid 2px lightblue;
}
`);
svg.append("rect")
.attr("fill", "none")
.attr("width", width)
.attr("height", height);
svg.append("path")
.datum(mutable points)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.call(update);
d3.select(window)
.on("keydown", keydown);
function update() {
svg.select("path").attr("d", line);
const circle = svg.selectAll("g")
.data(mutable points, d => d)
circle.enter().append("g")
.call(g => g.append("circle")
.attr("r", 30)
.attr("fill", "none"))
.call(g => g.append("circle")
.attr("r", 0)
.attr("stroke", "black")
.attr("stroke-width", 1.5)
.transition()
.duration(750)
.ease(d3.easeElastic)
.attr("r", 5))
.merge(circle)
.attr("transform", d => `translate(${d})`)
.select("circle:last-child")
.attr("fill", d => d === selected ? "lightblue" : "black");
circle.exit().remove();
}
function dragsubject(event) {
let subject = event.sourceEvent.target.__data__;
if (!subject) {
mutable points.push(subject = [event.x, event.y]);
mutable points = mutable points;
update();
}
return subject;
}
function dragstarted({subject}) {
selected = subject;
update();
}
function dragged(event) {
event.subject[0] = Math.max(-14, Math.min(width + 14, event.x));
event.subject[1] = Math.max(0, Math.min(height, event.y));
mutable points = mutable points;
update();
}
function keydown(event) {
if (!selected) return;
switch (event.key) {
case "Backspace":
case "Delete": {
event.preventDefault();
const i = mutable points.indexOf(selected);
mutable points.splice(i, 1);
mutable points = mutable points;
selected = mutable points.length ? mutable points[i > 0 ? i - 1 : 0] : null;
update();
break;
}
}
}
return svg.node();
}