Click to add new points. Hit the DELETE key to remove the selected point. Use the dropdown menu to change the interpolation mode.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <title>Spline Editor</title> | |
| <style> | |
| body { | |
| font: 13px sans-serif; | |
| position: relative; | |
| width: 960px; | |
| height: 500px; | |
| } | |
| form { | |
| position: absolute; | |
| bottom: 10px; | |
| left: 10px; | |
| } | |
| rect { | |
| fill: none; | |
| pointer-events: all; | |
| } | |
| circle, | |
| .line { | |
| fill: none; | |
| stroke: steelblue; | |
| stroke-width: 1.5px; | |
| } | |
| circle { | |
| fill: #fff; | |
| fill-opacity: .2; | |
| cursor: move; | |
| } | |
| .selected { | |
| fill: #ff7f0e; | |
| stroke: #ff7f0e; | |
| } | |
| </style> | |
| <form> | |
| <label for="interpolate">Interpolate:</label> | |
| <select id="interpolate"></select><br> | |
| </form> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| var width = 960, | |
| height = 500; | |
| var points = d3.range(1, 5).map(function(i) { | |
| return [i * width / 5, 50 + Math.random() * (height - 100)]; | |
| }); | |
| var dragged = null, | |
| selected = points[0]; | |
| var line = d3.svg.line(); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width) | |
| .attr("height", height) | |
| .attr("tabindex", 1); | |
| svg.append("rect") | |
| .attr("width", width) | |
| .attr("height", height) | |
| .on("mousedown", mousedown); | |
| svg.append("path") | |
| .datum(points) | |
| .attr("class", "line") | |
| .call(redraw); | |
| d3.select(window) | |
| .on("mousemove", mousemove) | |
| .on("mouseup", mouseup) | |
| .on("keydown", keydown); | |
| d3.select("#interpolate") | |
| .on("change", change) | |
| .selectAll("option") | |
| .data([ | |
| "linear", | |
| "step-before", | |
| "step-after", | |
| "basis", | |
| "basis-open", | |
| "basis-closed", | |
| "cardinal", | |
| "cardinal-open", | |
| "cardinal-closed", | |
| "monotone" | |
| ]) | |
| .enter().append("option") | |
| .attr("value", function(d) { return d; }) | |
| .text(function(d) { return d; }); | |
| svg.node().focus(); | |
| function redraw() { | |
| svg.select("path").attr("d", line); | |
| var circle = svg.selectAll("circle") | |
| .data(points, function(d) { return d; }); | |
| circle.enter().append("circle") | |
| .attr("r", 1e-6) | |
| .on("mousedown", function(d) { selected = dragged = d; redraw(); }) | |
| .transition() | |
| .duration(750) | |
| .ease("elastic") | |
| .attr("r", 6.5); | |
| circle | |
| .classed("selected", function(d) { return d === selected; }) | |
| .attr("cx", function(d) { return d[0]; }) | |
| .attr("cy", function(d) { return d[1]; }); | |
| circle.exit().remove(); | |
| if (d3.event) { | |
| d3.event.preventDefault(); | |
| d3.event.stopPropagation(); | |
| } | |
| } | |
| function change() { | |
| line.interpolate(this.value); | |
| redraw(); | |
| } | |
| function mousedown() { | |
| points.push(selected = dragged = d3.mouse(svg.node())); | |
| redraw(); | |
| } | |
| function mousemove() { | |
| if (!dragged) return; | |
| var m = d3.mouse(svg.node()); | |
| dragged[0] = Math.max(0, Math.min(width, m[0])); | |
| dragged[1] = Math.max(0, Math.min(height, m[1])); | |
| redraw(); | |
| } | |
| function mouseup() { | |
| if (!dragged) return; | |
| mousemove(); | |
| dragged = null; | |
| } | |
| function keydown() { | |
| if (!selected) return; | |
| switch (d3.event.keyCode) { | |
| case 8: // backspace | |
| case 46: { // delete | |
| var i = points.indexOf(selected); | |
| points.splice(i, 1); | |
| selected = points.length ? points[i > 0 ? i - 1 : 0] : null; | |
| redraw(); | |
| break; | |
| } | |
| } | |
| } | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment