|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
svg { |
|
font: 10px sans-serif; |
|
} |
|
|
|
.area { |
|
fill: #FC582F; /* material Orange */ |
|
fill-opacity: .9; |
|
clip-path: url(#clip); |
|
} |
|
|
|
.axis path, |
|
.axis line { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.brush .extent { |
|
stroke: #fff; |
|
fill-opacity: .125; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
#btnDiv { |
|
fill: #fff; |
|
} |
|
|
|
</style> |
|
<body> |
|
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script> |
|
var margin = { top: 10, right: 10, bottom: 100, left: 40 }, |
|
margin2 = { top: 430, right: 10, bottom: 20, left: 40 }, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom, |
|
height2 = 500 - margin2.top - margin2.bottom; |
|
|
|
var parseDate = d3.time.format("%b %Y").parse; |
|
|
|
var x = d3.time.scale().range([0, width]), |
|
x2 = d3.time.scale().range([0, width]), |
|
y = d3.scale.linear().range([height, 0]), |
|
y2 = d3.scale.linear().range([height2, 0]); |
|
|
|
var xAxis = d3.svg.axis().scale(x).orient("bottom"), |
|
xAxis2 = d3.svg.axis().scale(x2).orient("bottom"), |
|
yAxis = d3.svg.axis().scale(y).orient("left"); |
|
|
|
var brush = d3.svg.brush() |
|
.x(x2) |
|
.on("brush", brushed); |
|
|
|
var area = d3.svg.area() |
|
.interpolate("monotone") |
|
.x(function (d) { return x(d.date); }) |
|
.y0(height) |
|
.y1(function (d) { return y(d.price); }); |
|
|
|
var area2 = d3.svg.area() |
|
.interpolate("monotone") |
|
.x(function (d) { return x2(d.date); }) |
|
.y0(height2) |
|
.y1(function (d) { return y2(d.price); }); |
|
|
|
|
|
|
|
// make some buttons to drive our zoom |
|
d3.select("body").append("div") |
|
.attr("id","btnDiv") |
|
.style('font-size','75%') |
|
.style("width","280px") |
|
.style("position","absolute") |
|
.style("left", 1.5*margin.left + "px") |
|
.style("top","200px") |
|
|
|
d3.select("#btnDiv")[0][0].innerHTML = [ |
|
'<h3>Numbers to Drive Our Zoom</h3>', |
|
'<p>specify a range, push zoom, and watch the brush react</p>', |
|
'<ul>', |
|
'<li>the transition is deliberately slowed down so each step can be seen. This also demonstrates how to inject a transition</li>', |
|
'<br>', |
|
'<li>play with the brush after it is drawn to see how the chart acts if we draw with our mouse</li>', |
|
'</ul>' |
|
].join('\n') |
|
|
|
|
|
d3.select("#btnDiv") |
|
.append("input") |
|
.attr({ |
|
"id": "a", |
|
"value": 0 |
|
}) |
|
|
|
d3.select("#btnDiv") |
|
.append("input") |
|
.attr({ |
|
"id": "b", |
|
"value": 1 |
|
}) |
|
|
|
// style both of the inputs at once |
|
// more on HTML5 <input> at https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input |
|
d3.selectAll("input") |
|
.attr({ |
|
"type": "text", |
|
"size": 3, |
|
"autofocus": "true", |
|
"inputmode": "numeric" |
|
}) |
|
.style({ |
|
"text-align": "center", |
|
"display": "inline-block", |
|
"margin-right": "10px" |
|
}); |
|
|
|
|
|
var btns = d3.select("#btnDiv").selectAll("button").data(["zoom"]) |
|
btns = btns.enter().append("button").style("display","inline-block") |
|
|
|
// fill the buttons with the year from the data assigned to them |
|
btns.each(function (d) { |
|
this.innerText = d; |
|
}) |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom); |
|
|
|
svg.append("defs").append("clipPath") |
|
.attr("id", "clip") |
|
.append("rect") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var focus = svg.append("g") |
|
.attr("class", "focus") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
var context = svg.append("g") |
|
.attr("class", "context") |
|
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")"); |
|
|
|
d3.csv("sp500.csv", type, function (error, data) { |
|
x.domain(d3.extent(data.map(function (d) { return d.date; }))); |
|
y.domain([0, d3.max(data.map(function (d) { return d.price; }))]); |
|
x2.domain(x.domain()); |
|
y2.domain(y.domain()); |
|
|
|
focus.append("path") |
|
.datum(data) |
|
.attr("class", "area") |
|
.attr("d", area); |
|
|
|
focus.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height + ")") |
|
.call(xAxis); |
|
|
|
focus.append("g") |
|
.attr("class", "y axis") |
|
.call(yAxis); |
|
|
|
context.append("path") |
|
.datum(data) |
|
.attr("class", "area") |
|
.attr("d", area2); |
|
|
|
context.append("g") |
|
.attr("class", "x axis") |
|
.attr("transform", "translate(0," + height2 + ")") |
|
.call(xAxis2); |
|
|
|
context.append("g") |
|
.attr("class", "x brush") |
|
.call(brush) |
|
.selectAll("rect") |
|
.attr("y", -6) |
|
.attr("height", height2 + 7); |
|
|
|
// call drawBrush once on load with the default value |
|
var zoomA = d3.select("input#a")[0][0].value; |
|
var zoomB = d3.select("input#b")[0][0].value; |
|
drawBrush(zoomA, zoomB); |
|
|
|
// update the extent and call drawBrush again |
|
window.setTimeout(function() { |
|
d3.select("input#a")[0][0].value = .2; |
|
d3.select("input#b")[0][0].value = .7; |
|
var zoomA = d3.select("input#a")[0][0].value; |
|
var zoomB = d3.select("input#b")[0][0].value; |
|
drawBrush(zoomA, zoomB) |
|
}, 2500); |
|
|
|
btns.on("click", function(){ |
|
zoomA = d3.select("input#a")[0][0].value; // the d3 selection returns a DOM element wrapped in two arrays, hence the [0][0] |
|
console.log("zoomA", zoomA) |
|
zoomB = d3.select("input#b")[0][0].value; |
|
console.log("zoomB", zoomB) |
|
drawBrush(zoomA, zoomB); |
|
}); |
|
|
|
function drawBrush(a, b) { |
|
// define our brush extent |
|
|
|
// note that x0 and x1 refer to the lower and upper bound of the brush extent |
|
// while x2 refers to the scale for the second x-axis, for the context or brush area. |
|
// unfortunate variable naming :-/ |
|
var x0 = x2.invert(a*width) |
|
var x1 = x2.invert(b*width) |
|
console.log("x0", x0) |
|
console.log("x1", x1) |
|
brush.extent([x0, x1]) |
|
|
|
// now draw the brush to match our extent |
|
// use transition to slow it down so we can see what is happening |
|
// set transition duration to 0 to draw right away |
|
brush(d3.select(".brush").transition().duration(500)); |
|
|
|
// now fire the brushstart, brushmove, and brushend events |
|
// set transition the delay and duration to 0 to draw right away |
|
brush.event(d3.select(".brush").transition().delay(1000).duration(500)) |
|
|
|
} |
|
}); |
|
|
|
function brushed() { |
|
x.domain(brush.empty() ? x2.domain() : brush.extent()); |
|
focus.select(".area").attr("d", area); |
|
focus.select(".x.axis").call(xAxis); |
|
} |
|
|
|
function type(d) { |
|
d.date = parseDate(d.date); |
|
d.price = +d.price; |
|
return d; |
|
} |
|
|
|
</script> |
|
</body> |