score:1

Accepted answer

First of all, I'm not getting the error you described.

The problem in your snippet seems to be that the brushed out bars are piling up at the top of the SVG. This is due to the fact that you're changing the y scale's domain, and because of that some bars get undefined for y(d.month). Therefore, instead of nicely disappearing from the view, they are simply painted at 0 in the y position.

The best alternative is rethinking all your brush/zoom code. However, for minimal changes in the code, a quick and dirty solution is simply turning the bars that have undefined for the y position transparent:

.style("opacity", d => y(d.month) ? 1 : 0)

Also, make the pointer-events: none; for the bars in the brush, that makes a better user experience:

.attr("pointer-events", "none")

Here is your code with those change:

const data = [{
    month: "jan",
    value: 12
  },
  {
    month: "feb",
    value: 25
  },
  {
    month: "mar",
    value: 10
  },
  {
    month: "apr",
    value: 15
  }
];

var svg = d3.select("svg"),
  margin = {
    top: 10,
    right: 35,
    bottom: 10,
    left: 35
  },
  width = +svg.attr("width") - margin.left - margin.right,
  height = +svg.attr("height") - margin.top - margin.bottom;

var g = svg.append("g")
  .attr("transform",
    "translate(" + margin.left + "," + margin.top + ")");

// === Bar ===

var x = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)]).nice()
  .range([margin.left * 2, width])

var y = d3.scaleBand()
  .domain(data.map(d => d.month))
  .range([height, 0])
  .padding(0.1)

var yAxis = g => g
  .attr("transform", `translate(${margin.left * 2},0)`)
  .call(d3.axisLeft(y).tickSizeOuter(0))

g.append("g").selectAll(".bar")
  .data(data).enter().append("rect")
  .attr("fill", "steelblue")
  .attr("class", "bar")
  .attr("x", x(0))
  .attr("y", d => y(d.month))
  .attr("width", d => x(d.value) - x(0))
  .attr("height", y.bandwidth());

g.append("g")
  .attr("class", "y-axis")
  .call(yAxis);

// === Brush ===

var xB = d3.scaleLinear()
  .domain([0, d3.max(data, d => d.value)])
  .range([0, margin.left]);

var yB = d3.scaleBand()
  .domain(data.map(d => d.month))
  .range([height, 0])
  .padding(0.1);

var brush = d3.brushY()
  .extent([
    [0, 0],
    [margin.left, height]
  ])
  .on("start brush", brushed);

var yAxisB = g => g
  .call(d3.axisLeft(yB).tickSizeOuter(0))

g.append("g")
  .attr("class", "brush")
  .call(brush)
  .call(brush.move, yB.range().reverse())

g.append("g").selectAll(".brushBar")
  .data(data).enter().append("rect")
  .attr("fill", "steelblue")
  .attr("class", "brushBar")
  .attr("pointer-events", "none")
  .attr("x", xB(0))
  .attr("y", d => yB(d.month))
  .attr("width", d => xB(d.value) - xB(0))
  .attr("height", yB.bandwidth());

g.append("g")
  .call(yAxisB);

// === Brush & Zoom ===

var bExtent = [
  [0, 0],
  [width, height]
]

var zoom = d3.zoom()
  .scaleExtent([1, 2])
  .translateExtent(bExtent)
  .extent(bExtent)
  .on("zoom", zoomed)

g.append("rect")
  .attr("class", "zoom")
  .attr("width", width)
  .attr("height", height)
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
  .call(zoom);

function zoomed() {

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

  var t = d3.event.transform;

  y.range([height, 0]
    .map(d => d3.event.transform.applyY(d)));

  g.selectAll(".bar")
    .attr("y", d => y(d.month))
    .attr("height", y.bandwidth());

  g.selectAll(".y-axis").call(yAxis);

  g.select(".brush").call(brush.move,
    yB.range().reverse().map(t.invertY, t))
}

function brushed() {

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

  var s = d3.event.selection,
    nD = [];

  yB.domain().forEach((d) => {
    var pos = yB(d) + yB.bandwidth() / 2;
    if (pos > s[0] && pos < s[1]) {
      nD.push(d);
    }
  });

  y.domain(nD);

  g.selectAll(".y-axis").call(yAxis);

  g.selectAll(".bar")
    .attr("y", d => y(d.month))
    .style("opacity", d => y(d.month) ? 1 : 0)
    .attr("height", y.bandwidth());

  //g.select(".zoom").call(zoom.transform, d3.zoomIdentity
  //	.scale(2)
  //	.translate(-s[0], 0));
}
.zoom {
  cursor: move;
  fill: none;
  pointer-events: all;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="540" height="120"></svg>


Related Query

More Query from same tag