Skip to content

Instantly share code, notes, and snippets.

@jimkang
Forked from mbostock/.block
Last active December 16, 2015 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jimkang/5426892 to your computer and use it in GitHub Desktop.
Save jimkang/5426892 to your computer and use it in GitHub Desktop.

Click to add new points. Hit the DELETE key to remove the selected point. Use the dropdown menu to change the interpolation mode.

Updated from mbostock's original gist to allow editing the coordinates in text fields.

<!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;
}
.coordField {
font-size: 1.4em;
border: dashed 2px #ccc;
padding-left: 1em;
padding-right: 1em;
padding-top: 0.3em;
padding-bottom: 0.3em;
}
li {
padding-top: 1em;
padding-bottom: 1em;
}
ul {
list-style-type: none;
}
#listContainer {
float: right;
}
</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 = 480;
var 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();
}
refreshList();
}
// Set up text fields listing points.
function refreshList() {
var listSel = d3.select('#pointsList');
if (listSel.empty()) {
var outerSel = d3.select('body').append('div').attr('id', 'listContainer')
.attr('height', '400px');
listSel = outerSel.append('ul').attr('id', 'pointsList');
}
// Append new list item hierarchies for each point.
var itemSel = listSel.selectAll('li').data(points, function(d) { return d; });
itemSel.enter().append('li').call(function (sel) {
var spanXSel = sel.append('span').attr({
id: 'xField',
contenteditable: true,
class: 'coordField'
});
var spanYSel = sel.append('span').attr({
id: 'yField',
contenteditable: true,
class: 'coordField'
});
function editKeydown(d, i) {
switch (d3.event.keyCode) {
case 13: // Enter
// Stop editing.
this.blur();
if (this.getAttribute('id') === 'xField') {
d[0] = parseFloat(this.textContent);
}
else {
d[1] = parseFloat(this.textContent);
}
redraw();
break;
case 27: // Escape.
this.blur();
// TODO: Revert.
break;
}
}
spanXSel.on("keydown", editKeydown);
spanYSel.on("keydown", editKeydown);
});
// Remove hierarchies for removed points.
itemSel.exit().remove();
// Update point values.
itemSel.selectAll('#xField').text(function(d) { return d[0]; });
itemSel.selectAll('#yField').text(function(d) { return d[1]; });
}
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();
// console.log(points);
}
function mouseup() {
if (!dragged) return;
mousemove();
dragged = null;
}
function keydown() {
if (!selected || document.activeElement.tagName === 'SPAN') 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