|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
circle, path { |
|
fill: none; |
|
stroke: steelblue; |
|
stroke-width: 2; |
|
} |
|
|
|
.dotted { |
|
stroke: lightgrey; |
|
stroke-width: 1; |
|
stroke-dasharray: 10, 2; |
|
} |
|
.handle { |
|
fill: lightblue; |
|
cursor: col-resize; |
|
} |
|
.target { |
|
fill: yellow; |
|
} |
|
|
|
.label { |
|
font-family: monospace; |
|
font-size: 11px; |
|
} |
|
|
|
</style> |
|
<body> |
|
<script src="https://d3js.org/d3.v3.min.js"></script> |
|
<script> |
|
|
|
var width = 960, |
|
height = 500, |
|
radius = 6; |
|
|
|
var markers = [300, 350, 450, 600, 800]; |
|
var narrow = [400, 500, 700]; |
|
|
|
// Scales |
|
|
|
// Here we have a bunch of scales, with the domain and range being similar, to |
|
// see how the scales do their rounding and magic. |
|
|
|
// The quantize scale's input domain is a two-element array of numbers. If the |
|
// array contains more than two numbers, only the first and last number are used. |
|
var qz = d3.scale.quantize() |
|
.domain(d3.extent(markers)) |
|
.range(markers); |
|
|
|
// The input domain of a quantile scale is a set of discrete numeric values. |
|
// The array must not be empty, and must contain at least one numeric value. |
|
var ql1 = d3.scale.quantile() |
|
.domain(d3.extent(markers)) |
|
.range(markers); |
|
|
|
var ql2 = d3.scale.quantile() |
|
.domain(markers) |
|
.range(markers); |
|
|
|
// If the number of values in this scale's range is N + 1, the number of values |
|
// in the domain must be N. If there are fewer than N domain elements, the |
|
// additional values in the range are ignored. |
|
var th = d3.scale.threshold() |
|
.domain(markers.slice(0, -1)) |
|
.range(markers); |
|
|
|
// Here are the same scales again, but with a narrower range, to illustrate. |
|
|
|
var nqz = d3.scale.quantize() |
|
.domain(d3.extent(markers)) |
|
.range(narrow); |
|
|
|
var nql = d3.scale.quantile() |
|
.domain(d3.extent(markers)) |
|
.range(narrow); |
|
|
|
var nth = d3.scale.threshold() |
|
.domain(d3.extent(markers)) |
|
.range(narrow); |
|
|
|
// Boundaries |
|
|
|
var svg = d3.select("body") |
|
.append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
|
|
var gs = svg.selectAll(".markers") |
|
.data(markers) |
|
.enter().append("g") |
|
.attr("class", "markers"); |
|
|
|
gs.append("path") |
|
.attr("class", "dotted") |
|
.attr("d", function(d) { return "M" + d + ",25 L" + d + ",300"; }); |
|
|
|
gs.append("text") |
|
.attr("class", "label") |
|
.style("text-anchor", "middle") |
|
.attr("dx", function(d) { return d; }) |
|
.attr("dy", 15) |
|
.text(function(d) { return d; }); |
|
|
|
var gs = svg.selectAll(".narrow") |
|
.data(narrow) |
|
.enter().append("g") |
|
.attr("class", "narrow"); |
|
|
|
gs.append("path") |
|
.attr("class", "dotted") |
|
.attr("d", function(d) { return "M" + d + ",325 L" + d + ",490"; }); |
|
|
|
gs.append("text") |
|
.attr("class", "label") |
|
.style("text-anchor", "middle") |
|
.attr("dx", function(d) { return d; }) |
|
.attr("dy", 315) |
|
.text(function(d) { return d; }); |
|
|
|
// Drag group |
|
|
|
var drag = d3.behavior.drag() |
|
.origin(function(d) { return {x: d.x}; }) |
|
.on("drag", dragMove); |
|
|
|
var g = svg.append("g") |
|
.datum(function(d) { |
|
return {x: 250, qz: 250, ql1: 250, ql2: 250, th: 250, nqz: 250, nql: 250, nth: 250}; |
|
}) |
|
.call(drag); |
|
|
|
g.append("path") |
|
.attr("class", "joiner") |
|
.attr("d", joinPath); |
|
|
|
g.append("text") |
|
.attr("class", "label position") |
|
.style("text-anchor", "middle") |
|
.style("font-weight", "bold") |
|
.attr("dx", function(d) { return d.x; }) |
|
.attr("dy", 45) |
|
.text(function(d) { return d.x; }); |
|
|
|
function addCircle(y, cls, fnx) { |
|
return g.append("circle") |
|
.attr("class", cls) |
|
.attr("cx", fnx) |
|
.attr("cy", y) |
|
.attr("r", radius); |
|
} |
|
|
|
addCircle(60, "handle", function(d) { return d.x; }); |
|
addCircle(110, "target qz", function(d) { return d.qz; }); |
|
addCircle(170, "target ql1", function(d) { return d.ql1; }); |
|
addCircle(215, "target ql2", function(d) { return d.ql2; }); |
|
addCircle(275, "target th", function(d) { return d.th; }); |
|
addCircle(350, "target nqz", function(d) { return d.nqz; }); |
|
addCircle(410, "target nql", function(d) { return d.nql; }); |
|
addCircle(470, "target nth", function(d) { return d.nth; }); |
|
|
|
function joinPath(d) { |
|
return "M" + d.x + ",60" |
|
+ "L" + d.qz + ",110" |
|
+ "L" + d.ql1 + ",170" |
|
+ "L" + d.ql2 + ",215" |
|
+ "L" + d.th + ",275" |
|
+ "L" + d.nqz + ",350" |
|
+ "L" + d.nql + ",410" |
|
+ "L" + d.nth + ",470"; |
|
} |
|
|
|
function dragMove(d) { |
|
var g = d3.select(this); |
|
d.x = Math.max(radius, Math.min(width - radius, d3.event.x)); |
|
|
|
g.select(".target.qz").attr("cx", d.qz = qz(d.x)); |
|
g.select(".target.ql1").attr("cx", d.ql1 = ql1(d.x)); |
|
g.select(".target.ql2").attr("cx", d.ql2 = ql2(d.x)); |
|
g.select(".target.th").attr("cx", d.th = th(d.x)); |
|
g.select(".target.nqz").attr("cx", d.nqz = nqz(d.x)); |
|
g.select(".target.nql").attr("cx", d.nql = nql(d.x)); |
|
g.select(".target.nth").attr("cx", d.nth = nth(d.x)); |
|
|
|
g.select(".joiner").attr("d", joinPath(d)); |
|
g.select(".position").attr("dx", d.x).text(d.x); |
|
g.select(".handle").attr("cx", d.x); |
|
} |
|
|
|
// Labels and decoration |
|
|
|
function addLabel(y, text) { |
|
return svg.append("g") |
|
.attr("transform", "translate(10," + (y - 15) + ")") |
|
.selectAll("text") |
|
.data(text.split("\n")) |
|
.enter().append("text") |
|
.attr("class", "label") |
|
.attr("dy", function(d, i) { return (i + 0.35) + "em"; }) |
|
.text(function(d) { return d; }); |
|
} |
|
|
|
addLabel(60, "Drag this:").style("font-weight", "bold"); |
|
addLabel(110, "d3.scale.quantize()\n.domain(" + qz.domain() + ")\n.range(" + qz.range() + ")"); |
|
addLabel(170, "d3.scale.quantile()\n.domain(" + ql1.domain() + ")\n.range(" + ql1.range() + ")"); |
|
addLabel(215, "d3.scale.quantile()\n.domain(" + ql2.domain() + ")\n.range(" + ql2.range() + ")"); |
|
addLabel(275, "d3.scale.threshold()\n.domain(" + th.domain() + ")\n.range(" + th.range() + ")"); |
|
addLabel(350, "d3.scale.quantize()\n.domain(" + nqz.domain() + ")\n.range(" + nqz.range() + ")"); |
|
addLabel(410, "d3.scale.quantile()\n.domain(" + nql.domain() + ")\n.range(" + nql.range() + ")"); |
|
addLabel(470, "d3.scale.threshold()\n.domain(" + nth.domain() + ")\n.range(" + nth.range() + ")"); |
|
|
|
</script> |