javascript - D3 brushing on grouped bar chart -
i trying brushing work similar example, grouped bar chart: http://bl.ocks.org/mbostock/1667367
i don't have understanding of how brushing works (i haven't been able find tutorials), i'm bit @ loss going wrong. try include relevant bits of code below. chart tracking time fix broken builds day , grouped portfolio. far brush created , user can move , drag it, bars in main chart re-drawn oddly , x axis not updated @ all. can give appreciated. thank you.
// x0 time scale on x axis var main_x0 = d3.scale.ordinal().rangeroundbands([0, main_width-275], 0.2); var mini_x0 = d3.scale.ordinal().rangeroundbands([0, main_width-275], 0.2); // x1 portfolio scale on x axis var main_x1 = d3.scale.ordinal(); var mini_x1 = d3.scale.ordinal(); // define x axis var main_xaxis = d3.svg.axis() .scale(main_x0) .tickformat(dateformat) .orient("bottom"); var mini_xaxis = d3.svg.axis() .scale(mini_x0) .tickformat(dateformat) .orient("bottom");
after binding data...
// define axis domains main_x0.domain(data.result.map( function(d) { return d.date; } ) .sort(d3.ascending)); mini_x0.domain(data.result.map( function(d) { return d.date; } ) .sort(d3.ascending)); main_x1.domain(data.result.map( function(d) { return d.portfolio; } ) .sort(d3.ascending)) .rangeroundbands([0, main_x0.rangeband() ], 0); mini_x1.domain(data.result.map( function(d) { return d.portfolio; } ) .sort(d3.ascending)) .rangeroundbands([0, main_x0.rangeband() ], 0); // create brush mini graph var brush = d3.svg.brush() .x(mini_x0) .on("brush", brushed);
after adding axis's, etc.
// create bars var bar = main.selectall(".bars") .data(nested) .enter().append("g") .attr("class", function(d) { return d.key + "-group bar"; }) .attr("fill", function(d) { return color(d.key); } ); bar.selectall("rect").append("rect") .data(function(d) { return d.values; }) .enter().append("rect") .attr("class", function(d) { return d.portfolio; }) .attr("transform", function(d) { return "translate(" + main_x0(d.date) + ",0)"; }) .attr("width", function(d) { return main_x1.rangeband(); }) .attr("x", function(d) { return main_x1(d.portfolio); }) .attr("y", function(d) { return main_y(d.buildfixtime); }) .attr("height", function(d) { return main_height - main_y(d.buildfixtime); });
here brush function (trying several different options)...
function brushed() { main_x1.domain(brush.empty() ? mini_x1.domain() : brush.extent()); //main.select("rect") //.attr("x", function(d) { return d.values; }) //.attr("width", function(d) { return d.values; }); bar.select("rect") .attr("width", function(d) { return main_x1.rangeband(); }) .attr("x", function(d) { return main_x1(d.portfolio); }); //.attr("y", function(d) { console.log(d); return main_y(d.buildfixtime); }) //.attr("height", function(d) { return main_height - main_y(d.buildfixtime); }); main.select(".x.axis").call(main_xaxis); }
the problem comes trying use brush set x-scale domain, when x-scale ordinal scale. in other words, expected domain of x-axis list of categories, not max-min numerical extent. problem right @ top of brushing function:
function brushed() { main_x0.domain(brush.empty() ? mini_x0.domain() : brush.extent());
the domain set brush.extent()
array of 2 numbers, throws off ordinal scale.
according wiki, if 1 of scales attached brush function ordinal scale, values returned brush.extent()
values in output range, not in input domain. ordinal scales don't have invert()
method convert range values domain values.
so, have few options on how proceed:
you re-do whole graph using linear time scale main x-axes instead of ordinal scale. have write own function figure out width of each day on axis instead of being able use .rangeband()
.
you can create own "invert" function figure out categorical values (dates on mini_x0.domain
) included in range returned brush.extent()
. have both reset main_x0.domain
include dates on axis, and filter out rectangles draw rectangles.
or can leave domain of main_x0.
be, , change range instead. making range of graph larger, space out bars greater. in combination clipping path cut off bars outside plotting area, has effect of showing subset of bars, want anyway.
but should new range be? range returned brush.extent()
beginning , end positions of brushing rectangle. if used these values range on main graph, entire graph squished down width. that's opposite of want. want area of graph originally filled width stretched fill entire plotting area.
so, if original x range [0,100], , brush covers area [20,60], need new range satisfies these conditions:
- the 20% mark of new range width @ 0;
- the 60% mark of new range width @ 100.
therefore,
- the total width of new range ( (100-0) / (60-20) )*(100-0) = 250;
- the start of new range @ (0 - (20/100)*250) = -50;
- the end of new range @ (-50) + 250 = 200.
now algebra figuring out conversion yourself. type of scaling equation, why not create new scale function convert between old range , zoomed-in range.
specifically, need linear scale, output range set actual range of plotting area. set domain according range of brushed area want stretch cover plotting area. finally, figure out range of ordinal scale using linear scale figure out how far off screen original max , min values of range be. , there, we-can resize other ordinal scale , reposition rectangles.
in code:
//initialization: var main_xzoom = d3.scale.linear() .range([0, main_width - 275]) .domain([0, main_width - 275]); //brushing function: function brushed() { var originalrange = main_xzoom.range(); main_xzoom.domain(brush.empty() ? originalrange: brush.extent() ); main_x0.rangeroundbands( [ main_xzoom(originalrange[0]), main_xzoom(originalrange[1]) ], 0.2); main_x1.rangeroundbands([0, main_x0.rangeband()], 0); bar.selectall("rect") .attr("transform", function (d) { return "translate(" + main_x0(d.date) + ",0)"; }) .attr("width", function (d) { return main_x1.rangeband(); }) .attr("x", function (d) { return main_x1(d.portfolio); }); main.select("g.x.axis").call(main_xaxis); }
working fiddle based on simplified code (note: still need set clipping rectangle on main plot):
http://fiddle.jshell.net/cjad3/1/
Comments
Post a Comment