d3.js: limit size of brush

2019-04-28 07:32发布

问题:

Is there a way to limit the size of a brush, even though the extent is larger?

I put together a brush with only an x-scale that can be moved and resized. I would like to be able to limit the extent to which it can be resized by the user (basically only up to a certain point).

In the following example, the brush function stops updating when the brush gets bigger than half the maximum extent. The brush itself, though, can still be extended. Is there a way to prevent this from happening? Or is there a better way of handling this?

Many thanks!

See this code in action here: http://bl.ocks.org/3691274 (EDIT: This demo now works)

bar = function(range) {

      var x_range = d3.scale.linear()
          .domain([0, range.length])
          .range([0, width]); 

      svg.selectAll("rect.items").remove();

      svg.selectAll("rect.items")
          .data(range)
        .enter().append("svg:rect")
          .attr("class", "items")
          .attr("x", function(d, i) {return x_range(i);})
          .attr("y", 0)
          .attr("width",  width/range.length-2)
          .attr("height", 100)
          .attr("fill", function(d) {return d})
          .attr("title", function(d) {return d});
}

var start = 21;
bar(data.slice(0, start), true);

var control_x_range = d3.scale.linear()
    .domain([0, data.length])
    .range([0, width]); 

controlBar = svg.selectAll("rect.itemsControl")
    .data(data)
  .enter().append("svg:rect")
    .attr("class", "itemsControl")
    .attr("x", function(d, i) {return control_x_range(i);})
    .attr("y", 110)
    .attr("width",  width/data.length-2)
    .attr("height", 20)
    .attr("fill", function(d) {return d});

svg.append("g")
    .attr("class", "brush")
    .call(d3.svg.brush().x(d3.scale.linear().range([0, width]))
    .extent([0,1*start/data.length])
    .on("brush", brush))
  .selectAll("rect")
    .attr("y", 110)
    .attr("height", 20);

function brush() {
    var s = d3.event.target.extent();

    if (s[1]-s[0] < 0.5) {
        var start = Math.round((data.length-1)*s[0]);
        var end = Math.round((data.length-1)*s[1]);

        bar(data.slice(start,end));
    };

}

回答1:

I eventually solved it by redrawing the brush to its maximum allowed size when the size is exceeded:

function brush() {
    var s = d3.event.target.extent();
    if (s[1]-s[0] < 0.5) {
        var start = Math.round((data.length-1)*s[0]);
        var end = Math.round((data.length-1)*s[1]);

        bar(data.slice(start,end));
    }
    else {d3.event.target.extent([s[0],s[0]+0.5]); d3.event.target(d3.select(this));}
} 

Demo: http://bl.ocks.org/3691274

I'm still interested in reading better solutions.



回答2:

Here's another strategy using d3.v4 an ES6:

brush.on('end', () => {
    if (d3.event.selection[1] - d3.event.selection[0] > maxSelectionSize) {
        // selection is too large; animate back down to a more reasonable size
        let brushCenter = d3.event.selection[0] + 0.5 * (d3.event.selection[1] - d3.event.selection[0]);
        brushSel.transition()
            .duration(400)
        .call(brush.move, [
            brushCenter - 0.49 * maxSelectionSize,
            brushCenter + 0.49 * maxSelectionSize
        ]);
    } else {
        // valid selection, do stuff
    }
});

If the brush selection size is too large when the user lets go of it, it animates back down to the specified maximum size (maxSelectionSize). At that point, the 'end' event will fire again, with an acceptable selection size.

Note the 0.49 scalar: this is required to prevent floating point / rounding errors that could cause an infinite loop if the brush is move()d to a size that is still too large.



回答3:

Here is an example of limiting the brush's minimum width as 100px and maximum width as 200ps. I added a few lines into d3.v4.js, for setting the limitation of the brush width.

Added brush.limit([min, max]) for set the limitation:

var _limit = null;
brush.limit = function (l) {
  _limit = l;
}

Break mouse move event in move() function:

if (_limit && e1 - w1 < _limit[0]) {
  return;
}

(Demo) (Source Code)



回答4:

Nice piece of code, but I found a little bug. It does lock your brush whenever s[1]-s[0] < 0.5, but if you keep pressed the resize and bring all your brush to the oposite direction, it starts "moving" the brush without any action (i.e. it doesn't behave as it should).

I am pretty sure I can come up with a reasonable solution. If so, I'll re-post it here.

(sorry to post it here as an answer, but as you've already answered it once, I cannot post as a comment, I guess).