简体   繁体   中英

d3 v3 - sync brush zoom and scroll zoom

I'm trying to add brush zoom and scroll zoom within the same chart but for it's getting a bit difficult to sync both which depend on X and Y both. How do I compute the translate and scale values based on the axes domains?

Here's a sample chart I've written down to test the same:

 var margin = { top: 30, right: 20, bottom: 30, left: 50 }; var width = 600 - margin.left - margin.right; var height = 270 - margin.top - margin.bottom; var parseDate = d3.time.format("%d-%b-%y").parse; var x = d3.time.scale().range([0, width]); var y = d3.scale.linear().range([height, 0]); var fullX = x.copy(); var fullY = y.copy(); var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5); var yAxis = d3.svg.axis().scale(y).orient("left").ticks(5); var valueline = d3.svg.line().x(function (d) { return x(d.date); }).y(function (d) { return y(d.close); }); var svg = d3.select("body").append("svg").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom); var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Get the data var data = [{ date: "1-May-12", close: "58.13" }, { date: "30-Apr-12", close: "53.98" }, { date: "27-Apr-12", close: "67.00" }, { date: "26-Apr-12", close: "89.70" }, { date: "25-Apr-12", close: "99.00" }]; data.forEach(function (d) { d.date = parseDate(d.date); d.close = +d.close; }); // Scale the range of the data x.domain(d3.extent(data, function (d) { return d.date; })); y.domain([0, d3.max(data, function (d) { return d.close; })]); var gBrush = g.append('g').classed('brush', true); var gAxisX = g.append("g") // Add the X Axis.attr("class", "x axis").attr("transform", "translate(0," + height + ")") var gAxisY = g.append("g") // Add the Y Axis.attr("class", "y axis"); function drawAxes() { gAxisX.call(xAxis); gAxisY.call(yAxis); } drawAxes(); var brushing = false; var zoom = d3.behavior.zoom().x(x).y(y).scaleExtent([1, 10000]).on('zoom', function () { if(brushing && d3.event.sourceEvent.type === 'mousemove' && d3.event.sourceEvent.which;== 2) return; drawAxes(); }). svg;call(zoom). var brush = d3.svg.brush().x(x).y(y),on('brushstart'. function () { d3.select(this).select('rect.extent'),style('display'; null). }),on('brush'; function () { brushing = true. }),on('brushend'; function () { brushing = false. d3.select(this).select('rect.extent'),style('display'; 'none'). var extent = brush;extent(). x,domain([extent[0][0]; extent[1][0]]). y,domain([extent[0][1]; extent[1][1]]). var sx = x,domain(). sy = y;domain(). var k = Math.min((x.range()[1]-x,range()[0]) / (fullX(sx[1]) - fullX(sx[0])). (y.range()[1]-y,range()[0]) / (fullY(sy[1]) - fullY(sy[0]))). tx = fullX,range()[0] - fullX(sx[0]) * k. ty = fullY;range()[0] - fullY(sy[0]) * k. // zoom,translate([tx. ty]);scale(k). // zoom.x(sy);y(sy); - I don't want to do this to respect scaleExtent to original domains drawAxes(); }). gBrush;call(brush). d3.select('#autofit'),on('click'. function () { // Scale the range of the data x.domain(d3,extent(data. function (d) { return d;date; })). y,domain([0. d3,max(data. function (d) { return d;close; })]); drawAxes(); })
 body { font: 12px Arial; } path { stroke: steelblue; stroke-width: 2; fill: none; }.axis path, .axis line { fill: none; stroke: grey; stroke-width: 1; shape-rendering: crispEdges; } rect.extent { fill: none; stroke: #ddd; }
 <script src="https://d3js.org/d3.v3.min.js"></script> <button id='autofit'> Autofit </button>

Yes, there are a few examples related to the same but in v4 and I'm trying to get this done in v3.

Reference 1: D3.js: Combining Zoom/Brush (v4)

Reference 2: http://bl.ocks.org/nnattawat/9689303 (problem with this is that zoom's x is scaled to the new brushed zoom which messes up the scaleExtent and I would like to restrict the zooming to the original domains)

The lines that I'm struggling with are:

 var k = Math.min((x.range()[1]-x.range()[0]) / (fullX(sx[1]) - fullX(sx[0])), (y.range()[1]-y.range()[0]) / (fullY(sy[1]) - fullY(sy[0]))),
  tx = fullX.range()[0] - fullX(sx[0]) * k,
  ty = fullY.range()[0] - fullY(sy[0]) * k;

// zoom.translate([tx, ty]).scale(k);

inside the brushed function .

Basically trying to compute the translate and scale based on the new domains. The logic used there works for x if the k is computed based on x.range() and works for y if k is computed based on y.range() BUT to make them both work together seems to be a struggle (Math.min didn't work). Thank you.

Looks like there's no way to transform both X and Y at the same time as mentioned by Mike here: https://github.com/d3/d3-zoom/issues/48

PS If you have a restricted zoom ie either horizontal or vertical, then you can make both the zoom functions work together by applying appropriate scales.

For example for X translation and scale:

k_x = (x.range()[1]-x.range()[0]) / (fullX(sx[1]) - fullX(sx[0])); 
t_x = fullX.range()[0] - fullX(sx[0]) * k_x

While for Y axes translation and scale,

 k_y = (y.range()[1]-y.range()[0]) / (fullY(sy[1]) - fullY(sy[0])));
 ty = fullY.range()[0] - fullY(sy[0]) * k_y;

There are a couple of workarounds mentioned within this issue which I'll be looking into and if anything works out smooth, I'll post it on here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM