Skip to content

Instantly share code, notes, and snippets.

@cdagli
Last active August 31, 2022 05:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cdagli/3f6b27139323e59e0b445de1a04615c3 to your computer and use it in GitHub Desktop.
Save cdagli/3f6b27139323e59e0b445de1a04615c3 to your computer and use it in GitHub Desktop.
Brushing Bar Chart
license: mit

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. With the abitility to zoom and pan in the Focus view using d3.zoom behavior.

forked from sbreslav's block: Focus+Context via Brushing & Zoom & Pan

<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.bar {
fill: steelblue;
clip-path: url(#clip);
}
.subBar {
fill: gray;
opacity: 0.5;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill: steelblue;
fill-opacity: .25;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var data = [];
for (var i = 0; i < 300; i++) {
var datum = {};
datum.date = i;
datum.price = Math.floor(Math.random() * 600);
data.push(datum);
}
var margin = {top: 10, right: 10, bottom: 100, left: 40},
margin2 = {top: 430, right: 10, bottom: 20, left: 40},
width = 750 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var x = d3.scale.ordinal().rangeBands([0, width], .1),
x2 = d3.scale.ordinal().rangeBands([0, width], .1),
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").tickValues([]),
yAxis = d3.svg.axis().scale(y).orient("left");
x.domain(data.map(function(d){ return d.date}));
y.domain([0, d3.max(data, function(d) { return d.price;})]);
x2.domain(x.domain());
y2.domain(y.domain());
var brush = d3.svg.brush()
.x(x2)
.on("brush", brushed);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
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 + ")");
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis);
console.log(x(data[0].date))
enter(data)
updateScale(data)
var subBars = context.selectAll('.subBar')
.data(data)
subBars.enter().append("rect")
.classed('subBar', true)
.attr(
{
height: function (d)
{
return height2 - y2(d.price);
},
width: function(d){ return x.rangeBand()},
x: function(d) {
return x2(d.date);
},
y: function(d)
{
return y2(d.price)
}
})
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);
function brushed() {
var selected = null;
selected = x2.domain()
.filter(function(d){
return (brush.extent()[0] <= x2(d)) && (x2(d) <= brush.extent()[1]);
});
var start;
var end;
if(brush.extent()[0] != brush.extent()[1])
{
start = selected[0];
end = selected[selected.length - 1] + 1;
} else {
start = 0;
end = data.length;
}
var updatedData = data.slice(start, end);
update(updatedData);
enter(updatedData);
exit(updatedData);
updateScale(updatedData)
}
function updateScale(data)
{
var tickScale = d3.scale.pow().range([data.length / 10, 0]).domain([data.length, 0]).exponent(.5)
var brushValue = brush.extent()[1] - brush.extent()[0];
if(brushValue === 0){
brushValue = width;
}
var tickValueMultiplier = Math.ceil(Math.abs(tickScale(brushValue)));
var filteredTickValues = data.filter(function(d, i){return i % tickValueMultiplier === 0}).map(function(d){ return d.date})
focus.select(".x.axis").call(xAxis.tickValues(filteredTickValues));
focus.select(".y.axis").call(yAxis);
}
function update(data)
{
x.domain(data.map(function(d){ return d.date}));
y.domain([0, d3.max(data, function(d) { return d.price;})]);
var bars = focus.selectAll('.bar')
.data(data)
bars
.attr(
{
height: function (d, i)
{
return height - y(d.price);
},
width: function(d){
return x.rangeBand()
},
x: function(d) {
return x(d.date);
},
y: function(d)
{
return y(d.price)
}
})
}
function exit(data)
{
var bars = focus.selectAll('.bar').data(data)
bars.exit().remove()
}
function enter(data)
{
x.domain(data.map(function(d){ return d.date}));
y.domain([0, d3.max(data, function(d) { return d.price;})]);
var bars = focus.selectAll('.bar')
.data(data)
bars.enter().append("rect")
.classed('bar', true)
.attr(
{
height: function (d, i)
{
return height - y(d.price);
},
width: function(d){
return x.rangeBand()
},
x: function(d) {
return x(d.date);
},
y: function(d)
{
return y(d.price)
}
})
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment