Skip to content

Instantly share code, notes, and snippets.

@markmarkoh
Forked from mbostock/.block
Last active September 18, 2016 01:25
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 markmarkoh/229d21685e7eb082ae830cc5ff0508f3 to your computer and use it in GitHub Desktop.
Save markmarkoh/229d21685e7eb082ae830cc5ff0508f3 to your computer and use it in GitHub Desktop.
Draw This Graph!

Example 'draw this chart' workflow

<!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