Search code examples
d3.jszoomingbrush

D3js brush area on zoom not centered


I'm working on zooming and brushing on a barchart in D3js. I'm following this example: https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172.

I'm having issue's when zooming with the brush area. It's not centered correctly (most of the times). When it's not centering correct it's centered too much centered to the left (see screenshot).

enter image description here

This is the code for brushing and zooming

// Zoom
  var zoom = d3.zoom()
    .scaleExtent([.9, 20])
    .translateExtent([[0, 0], [graph.width(), graph.height()]])
    .on('zoom', zoomUpdateChart);

  svg.call(zoom);

  function zoomUpdateChart() {
    if (d3.event.sourceEvent && d3.event.sourceEvent.type == "brush") return;

    // recover the new scale
    var transform = d3.event.transform;
    var zoomLevel = d3.event.transform.k;
    graph._xScale.domain(transform.rescaleX(graph._xScaleZoom).domain());

    resetZoomBtn.attr("opacity", 1)
      .on("click", resetZoom);
    graph.TooltipRemove(tooltip);

    // regenerating the box axis
    axis.remove();
    axis = graph.RenderBoxAxis(g, graph._xScale);
    bars.attr("x", function(d) { return graph._xScale(d.x); })
        .attr("width", barWidth * zoomLevel );

    zoomSection.select(".brush").call(brush.move, graph._xScale.range().map(transform.invertX, transform));
  }

  function resetZoom() {
    svg.transition()
      .duration(750)
      .call(zoom.transform, d3.zoomIdentity)
      .on("end", () => { 
        resetZoomBtn.attr("opacity", 0.2)
          .on("click", null); 
      });
    graph.TooltipRemove(tooltip);
  }

  // Brush
  var brush = d3.brushX()
    .extent([[0, 0], [graph._width, graph._heightZoom]])
    .on("brush", brushed);

  zoomSection.append("g")
    .attr("class", "brush")
    .call(brush)
    .call(brush.move, graph._xScale.range());

  function brushed() {
    if (d3.event.sourceEvent && d3.event.sourceEvent.type == "zoom") return;

    const selection = d3.event.selection || graph._xScaleZoom.range();
    graph._xScale.domain(selection.map(graph._xScaleZoom.invert, graph._xScaleZoom));
    const selectionWidth = selection[1] - selection[0];
    const scale = (selectionWidth / graph._width);
    const zoomLevel = 1 / scale;

    resetZoomBtn.attr("opacity", 1)
      .on("click", resetZoom);
    graph.TooltipRemove(tooltip);

    // regenerating the box axis
    axis.remove();
    axis = graph.RenderBoxAxis(g, graph._xScale);
    bars.attr("x", function(d) { return graph._xScale(d.x) })
        .attr("width", barWidth * zoomLevel)

    svg.call(zoom.transform, d3.zoomIdentity
        .scale(graph._width / selectionWidth)
        .translate(-selection[0], 0));
  }

I've done some research myself. The problem is within setting the range for the new brush position.

zoomSection.select(".brush").call(brush.move, graph._xScale.range().map(transform.invertX, transform));

It's set to [-166.07655066619142, 298.5037864592652]. The first index should be a positive number but is not calculated correctly. I've looked over it a couple of hours but haven't found the solution.

Some more details: The page loads more graphs of different types like areachart and line charts.


Solution

  • After much searching I found the anwser. I wasn't setting .extent() on zoom. The correct code is:

    var zoom = d3.zoom()
        .scaleExtent([0.9, 20])
        .extent([[0, 0], [graph._width, graph._height]])
        .translateExtent([[0, 0], [graph._width, graph._height]])
        .on('zoom', zoomUpdateChart);