Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js: limit size of brush

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

}
like image 935
raffazizzi Avatar asked Sep 10 '12 15:09

raffazizzi


2 Answers

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.

like image 52
raffazizzi Avatar answered Oct 02 '22 21:10

raffazizzi


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.

like image 38
ericsoco Avatar answered Oct 02 '22 21:10

ericsoco