Example 'draw this chart' workflow
-
-
Save markmarkoh/229d21685e7eb082ae830cc5ff0508f3 to your computer and use it in GitHub Desktop.
Draw This Graph!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>Spline Editor</title> | |
<style> | |
body { | |
font: 13px sans-serif; | |
position: relative; | |
height: 100%; | |
} | |
form { | |
position: absolute; | |
bottom: 30px; | |
left: 10px; | |
} | |
svg:hover circle { | |
stroke: steelblue; | |
} | |
svg:hover circle.selected { | |
fill: #ff7f0e; | |
stroke: #ff7f0e; | |
} | |
rect { | |
fill: none; | |
pointer-events: all; | |
} | |
circle, | |
.line { | |
fill: none; | |
stroke-width: 1.5px; | |
} | |
.line { | |
stroke: steelblue; | |
} | |
circle { | |
fill: #fff; | |
fill-opacity: .2; | |
cursor: move; | |
} | |
.axis { | |
shape-rendering: crispEdges; | |
} | |
.x.axis line { | |
stroke: #222; | |
opacity: 0.1; | |
} | |
.x.axis .minor { | |
stroke-opacity: .5; | |
} | |
.x.axis path { | |
display: none; | |
} | |
.y.axis line, | |
.y.axis path { | |
fill: none; | |
stroke: #000; | |
} | |
</style> | |
<form> | |
<label for="interpolate">Interpolate:</label> | |
<select id="interpolate"></select><br> | |
<button id="reset">Reset</button> | |
</form> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script> | |
var width = 960, | |
height = 500; | |
var numPoints = 10; | |
var startingPointY = height / 2; | |
var parse = d3.time.format("%b %Y").parse; | |
var x = d3.time.scale() | |
.range([0, width - 50]); | |
var y = d3.scale.linear() | |
.range([height - 50, 0]); | |
x.domain([parse("Jan 1910"), parse("Jan 2016")]) | |
y.domain([0, height - 50]).nice(); | |
var points; | |
var version = 0 | |
function setDefaultPoints() { | |
points = d3.range(0, numPoints).map(function(i) { | |
return { | |
x: (i * width / numPoints) + 20, | |
y: startingPointY, | |
index: i, | |
edited: false, | |
version: version | |
} | |
}); | |
version++ | |
} | |
setDefaultPoints(); | |
var dragged = null, | |
selected = points[0]; | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.ticks(9) | |
.tickSize(-height); | |
var yAxis = d3.svg.axis() | |
.scale(y) | |
.ticks(4) | |
.orient("right"); | |
var line = d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
var drag = d3.behavior.drag(); | |
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); | |
svg.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(25," + (height - 20) + ")") | |
.call(xAxis); | |
svg.append("g") | |
.attr("class", "y axis") | |
.attr("transform", "translate(" + (width - 30)+ ",0)") | |
.call(yAxis); | |
d3.select(window) | |
.on("mousemove", mousemove) | |
.on("drag", drag) | |
.on("mouseup", mouseup) | |
.on("keydown", keydown); | |
d3.select("#interpolate") | |
.on("change", change) | |
.selectAll("option") | |
.data([ | |
"linear", | |
"step-before", | |
"step-after", | |
"basis", | |
"cardinal", | |
"monotone" | |
]) | |
.enter().append("option") | |
.attr("value", function(d) { return d; }) | |
.text(function(d) { return d; }); | |
d3.select("#reset") | |
.on("click", function() { | |
setDefaultPoints(); | |
svg.select('.line').datum(points).call(redraw); | |
}) | |
svg.node().focus(); | |
function redraw(shouldSetX) { | |
svg.select("path").attr("d", line); | |
var circle = svg.selectAll("circle") | |
.data(points, function(d) {return d.x + '.' + d.y + '.' + d.version; }); | |
circle.enter().append("circle") | |
.attr("r", 1e-6) | |
.on("mousedown", function(d) { selected = dragged = d; redraw(); }) | |
.on("dragstart", function(d) { console.log("ohno")}) | |
//.call(drag) | |
.transition() | |
.duration(750) | |
.ease("elastic") | |
.attr("r", 10); | |
circle | |
.classed("selected", function(d) { return d.index === selected.index; }) | |
.attr("cy", function(d) { return d.y; }); | |
if (shouldSetX) { | |
circle.attr("cx", function(d) { return d.x; }) | |
} | |
circle.exit().remove(); | |
if (d3.event) { | |
d3.event.preventDefault(); | |
d3.event.stopPropagation(); | |
} | |
} | |
function change() { | |
line.interpolate(this.value); | |
redraw(); | |
} | |
function mousedown() { | |
return | |
} | |
function mousemove() { | |
if (!dragged) return; | |
var m = d3.mouse(svg.node()); | |
dragged.x = dragged.x; | |
dragged.y = Math.max(0, Math.min(height, m[1])); | |
dragged.edited = true; | |
var previousNonEditedIndex = null | |
for(var i = dragged.index - 1; i >= 0; i--) { | |
if (points[i].edited) { | |
break; | |
} | |
previousNonEditedIndex = points[i] | |
} | |
if (previousNonEditedIndex !== null) { | |
var scale = d3.scale.linear() | |
.domain([dragged.index, previousNonEditedIndex.index]) | |
.range([dragged.y, previousNonEditedIndex.y]) | |
for(var i = dragged.index; i > previousNonEditedIndex.index; i--) { | |
points[i].y = scale(i) | |
} | |
} | |
for(var i = dragged.index + 1; i < points.length; i++) { | |
if (points[i].edited) { | |
break; | |
} | |
points[i].y = dragged.y | |
} | |
redraw(); | |
} | |
drag.on("dragstart", function() { | |
console.log('drag start', d3.touch(svg.node())) | |
selected = dragged = d3.touch(svg.node()) | |
}) | |
drag.on("drag", function() { | |
console.log('drag!') | |
if (!dragged) return; | |
var m = d3.touch(svg.node()); | |
dragged.x = dragged.x; | |
dragged.y = Math.max(0, Math.min(height, m[1])); | |
dragged.edited = true; | |
var previousNonEditedIndex = null | |
for(var i = points.length; i > dragged.index; i--) { | |
if (points[i].edited) { | |
break; | |
} | |
previousNonEditedIndex = points[i].index | |
} | |
if (previousNonEditedIndex !== null) { | |
for(var i = dragged.index; i > previousNonEditedIndex; i--) { | |
// points[i].y = dragged.y - (i * 10) | |
} | |
} | |
for(var i = dragged.index + 1; i < points.length; i++) { | |
if (points[i].edited) { | |
break; | |
} | |
points[i].y = dragged.y | |
} | |
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