d3.js: limit size of brush

2019-04-28 07:19发布

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));
    };

}

4条回答
劫难
2楼-- · 2019-04-28 07:55

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).

查看更多
疯言疯语
3楼-- · 2019-04-28 07:58

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.

查看更多
祖国的老花朵
4楼-- · 2019-04-28 08:17

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.

查看更多
等我变得足够好
5楼-- · 2019-04-28 08:19

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)

查看更多
登录 后发表回答