Forked from timelyportfolio‘s bl.ock which in turn forked Mike Bostock‘s focus+context zoom gist to demonstrate how we can drive a d3 brush with code.
anybody know how to brush with code in #d3js?
— klr (@timelyportfolio) September 10, 2014
So timelyportfolio found this discussion, but he could not find an example demonstrating the steps proposed by Athan Reines.
For auto-redraw so that the focus of a 1D brush matches the graphed domain, you need to do as follows: (1) Apply the brush scale to the graphed domain (i.e., the brush extent) --> store in var brushExtent; (units: pixels) (2) Within the brush element (class=`'brush'`), select the `` with class=`'extent'`. (3) If the brush is horizontal (i.e., for the x-axis), set the 'x' attribute to the first value in brushExtent. This moves the start position of the focus ` ` to match the graphed domain. If the brush is vertical (i.e., for the y-axis), set the `'y'` attribute to the second value in brushExtent. (4) Next, set the 'width' attribute of the extent to `brushExtent[1] - brushExtent[0]`. The end of the brush focus is `brushExtent[1]`, but the length of the focus is this minus the offset introduced by `brushExtent[0]`. (If a y-axis brush, switch [0] and [1]). (*) The extent should now programmatically match the graphed domain. I use this procedure as part of a resize function. Hope this works. -KG
timelyportfolio and I do not follow these steps exactly. Here is the code that drives our brush when a range is specified and the zoom button is clicked.
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 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(10duration(500))
}
This examples demonstrates how to use D3’s brush component to implement focus + context zooming. Click and drag in the small chart below to pan or zoom.
MIT License
forked from micahstubbs‘s block: programmatic control of a d3 brush - specify zoom
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.area {
fill: #69015d;
fill-opacity: 0.4;
clip-path: url(#clip);
}
.axis path,
.axis line {
fill: none;
stroke: #6b6b6b;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
#btnDiv {
fill: #fff;
}
</style>
<body>
<script src="//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>
date,price
Jan 2000,1394.46
Feb 2000,1366.42
Mar 2000,1498.58
Apr 2000,1452.43
May 2000,1420.6
Jun 2000,1454.6
Jul 2000,1430.83
Aug 2000,1517.68
Sep 2000,1436.51
Oct 2000,1429.4
Nov 2000,1314.95
Dec 2000,1320.28
Jan 2001,1366.01
Feb 2001,1239.94
Mar 2001,1160.33
Apr 2001,1249.46
May 2001,1255.82
Jun 2001,1224.38
Jul 2001,1211.23
Aug 2001,1133.58
Sep 2001,1040.94
Oct 2001,1059.78
Nov 2001,1139.45
Dec 2001,1148.08
Jan 2002,1130.2
Feb 2002,1106.73
Mar 2002,1147.39
Apr 2002,1076.92
May 2002,1067.14
Jun 2002,989.82
Jul 2002,911.62
Aug 2002,916.07
Sep 2002,815.28
Oct 2002,885.76
Nov 2002,936.31
Dec 2002,879.82
Jan 2003,855.7
Feb 2003,841.15
Mar 2003,848.18
Apr 2003,916.92
May 2003,963.59
Jun 2003,974.5
Jul 2003,990.31
Aug 2003,1008.01
Sep 2003,995.97
Oct 2003,1050.71
Nov 2003,1058.2
Dec 2003,1111.92
Jan 2004,1131.13
Feb 2004,1144.94
Mar 2004,1126.21
Apr 2004,1107.3
May 2004,1120.68
Jun 2004,1140.84
Jul 2004,1101.72
Aug 2004,1104.24
Sep 2004,1114.58
Oct 2004,1130.2
Nov 2004,1173.82
Dec 2004,1211.92
Jan 2005,1181.27
Feb 2005,1203.6
Mar 2005,1180.59
Apr 2005,1156.85
May 2005,1191.5
Jun 2005,1191.33
Jul 2005,1234.18
Aug 2005,1220.33
Sep 2005,1228.81
Oct 2005,1207.01
Nov 2005,1249.48
Dec 2005,1248.29
Jan 2006,1280.08
Feb 2006,1280.66
Mar 2006,1294.87
Apr 2006,1310.61
May 2006,1270.09
Jun 2006,1270.2
Jul 2006,1276.66
Aug 2006,1303.82
Sep 2006,1335.85
Oct 2006,1377.94
Nov 2006,1400.63
Dec 2006,1418.3
Jan 2007,1438.24
Feb 2007,1406.82
Mar 2007,1420.86
Apr 2007,1482.37
May 2007,1530.62
Jun 2007,1503.35
Jul 2007,1455.27
Aug 2007,1473.99
Sep 2007,1526.75
Oct 2007,1549.38
Nov 2007,1481.14
Dec 2007,1468.36
Jan 2008,1378.55
Feb 2008,1330.63
Mar 2008,1322.7
Apr 2008,1385.59
May 2008,1400.38
Jun 2008,1280
Jul 2008,1267.38
Aug 2008,1282.83
Sep 2008,1166.36
Oct 2008,968.75
Nov 2008,896.24
Dec 2008,903.25
Jan 2009,825.88
Feb 2009,735.09
Mar 2009,797.87
Apr 2009,872.81
May 2009,919.14
Jun 2009,919.32
Jul 2009,987.48
Aug 2009,1020.62
Sep 2009,1057.08
Oct 2009,1036.19
Nov 2009,1095.63
Dec 2009,1115.1
Jan 2010,1073.87
Feb 2010,1104.49
Mar 2010,1140.45