Last active
March 11, 2017 06:12
-
-
Save sgpinkus/fe019e73011a40f2978740b907e95b8f to your computer and use it in GitHub Desktop.
utility-function-editor-mock
This file contains 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> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Spline Curve Editor</title> | |
<style> | |
* { border: 0; padding: 0; margin: 0; } | |
html, body { height: 100%; } | |
body { | |
font: 13px sans-serif; | |
position: relative; | |
} | |
article { display:flex; flex-flow: row nowrap; } | |
article { margin:auto auto; } | |
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; | |
} | |
svg { | |
border: solid 1px #000; | |
} | |
.level-canvas rect { | |
fill: steelblue; | |
} | |
.level-canvas text { | |
fill: white; | |
font: 10px sans-serif; | |
text-anchor: end; | |
} | |
</style> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script> | |
var width = 840; | |
var height = 400; | |
var margin = 50; | |
var sum = 0; | |
var points = [ | |
[10,height-margin], | |
[width*(0.25),(height-margin)/2], | |
[width*(0.50),margin], | |
[width*(0.75),(height-margin)/2], | |
[width-10,height-margin] | |
]; | |
points = points.map((p, i) => { return {i:i, x:p[0], y:p[1]}; }); | |
var dragged = null; | |
var selected = null; | |
var line; | |
var svg; | |
function init() { | |
d3.select(window) | |
.on("mousemove", mousemove) | |
.on("mouseup", mouseup) | |
d3.select("#interpolate") | |
.on("change", change) | |
.selectAll("option") | |
.data([ | |
"cardinal", | |
"cardinal-open", | |
"cardinal-closed", | |
"linear", | |
"step-before", | |
"step-after", | |
"basis", | |
"basis-open", | |
"basis-closed", | |
"monotone" | |
]) | |
.enter().append("option") | |
.attr("value", function(d) { return d; }) | |
.text(function(d) { return d; }); | |
line = d3.svg.line(); | |
d3.select(".level-canvas") | |
.attr("width", 100) | |
.attr("height", height) | |
.attr("tabindex", 2) | |
.append("g") | |
svg = d3.select(".main-canvas") | |
.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.map((n) => {return [n.x,n.y];})) | |
.attr("class", "line") | |
.call(redraw); | |
line.interpolate("cardinal"); | |
redraw(); | |
} | |
/** | |
* Redraw and also init the canvas | |
*/ | |
function redraw() { | |
bounds() | |
sum = width*height - integrate_polyline(points.map((n) => {return [n.x,n.y];})); | |
console.log(sum, width*height) | |
svg.select("path").datum(points.map((n) => {return [n.x,n.y];})).attr("d", line); | |
var circle = svg.selectAll("circle").data(points); | |
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.x; }) | |
.attr("cy", function(d) { return d.y; }); | |
circle.exit().remove(); | |
var bar = d3.select(".level-canvas g").selectAll("rect").data([sum/(width*height)]) | |
bar.enter() | |
.append("rect") | |
bar.attr("width", "100%") | |
.attr("height", function(d) { return d*height;}) | |
.attr("transform", function(d) { return "translate(0," + ( height - d*height ) + ")"; }); | |
if (d3.event) { | |
d3.event.preventDefault(); | |
d3.event.stopPropagation(); | |
} | |
} | |
/** | |
* Ensure points are within bounds. | |
* In x: min < s1 < peak < s2 < max. | |
* In y: min and max are pinned to base, peak pinned to top. | |
*/ | |
function bounds() { | |
points = points.map((p,i) => { | |
p.x = Math.min(Math.max(p.x, (i+1)*margin), width-(points.length-i)*margin) | |
p.y = Math.min(Math.max(p.y, margin), height-margin) | |
return p; | |
}) | |
points[0].y = height-margin | |
points[1].y = Math.max(points[1].y, margin*2) | |
points[2].y = margin | |
points[3].y = Math.max(points[3].y, margin*2) | |
points[4].y = height-margin | |
if (!dragged) return; | |
for(i = 0; i < points.length; i++) { | |
gap = i - dragged.i | |
if(gap > 0) { | |
points[i].x = Math.max(points[i].x, dragged.x+gap*margin) | |
} | |
else if(gap < 0) { | |
points[i].x = Math.min(points[i].x, dragged.x+gap*margin) | |
} | |
} | |
} | |
function change() { | |
line.interpolate(this.value); | |
redraw(); | |
} | |
function mousedown() { | |
redraw(); | |
} | |
function mousemove() { | |
if (!dragged) return; | |
var m = d3.mouse(svg.node()); | |
var d = dragged | |
dragged.x = Math.max(0, Math.min(width, m[0])); | |
dragged.y = Math.max(0, Math.min(height, m[1])); | |
redraw(); | |
} | |
function mouseup() { | |
dragged = null; | |
selected = null; | |
redraw(); | |
} | |
function integrate_polyline(l) { | |
sum = 0; | |
step = 1; | |
for(var i = 1; i < l.length; i++) { | |
xa = l[i-1][0], xb = l[i][0], ya = l[i-1][1], yb = l[i][1]; | |
if(xb <= xa) | |
throw new Error("List must have strictly increasing x values") | |
b = xb-xa | |
h = ya | |
sum += b*h + (b*(yb-ya)*0.5) | |
} | |
return sum | |
} | |
</script> | |
</head> | |
<body onload="init()"> | |
<article> | |
<svg class="main-canvas"></svg> | |
<svg class="level-canvas"></svg> | |
<form> | |
<label for="interpolate">Interpolate:</label> | |
<select id="interpolate"></select><br> | |
</form> | |
</article> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment