ajb
ajb

Reputation: 702

javascript d3.js: initialize brush with brush.extent and stop data from spilling over margin

I have 2 issues I want to fix with my current d3 app which is based off this:

Here's the fiddle: http://jsfiddle.net/zoa5m20z/

  1. I want to initialize my brush so that only a small specific portion is brushed by default when the app starts up. I tried the following with .extent but with no luck.

    //date parsing function
    var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
    
    //setting up brush and defaultExtent
    var brush = d3.svg.brush()
    .x(x2)
    .extent([parseDate("2014-08-11 10:20:00"), parseDate("2014-08-11 18:20:00")]) //trying to intialize brush
    .on("brush", brushed);
    
  2. I want to stop my plotted circles from overlapping with the yaxis. I'd like to only plot/show circles to the right of the y-axis when the brush is moved. Just like the canonical Bostock Focus+Context via Brushing Block. I tried playing with the margins and the scales, domains, ranges but to no avail yet.

What I'm trying to avoid:

enter image description here

I'm new to d3.js, so all tips & advice is welcome and appreciated!

Upvotes: 4

Views: 4853

Answers (2)

Chaoyu
Chaoyu

Reputation: 842

Here is a working example based on your code: http://jsfiddle.net/26sd8uc9/4/

1 - You are right about the .extent, the problem is that you haven't specify the domain for you x2 scale. By adding the following code it works:

x2 = d3.time.scale()
   .domain([
     parseDate("2014-08-11 05:30:00"), 
     parseDate("2014-08-12 19:25:00")
   ])
   .nice(d3.time.minute)
   .range([0, width]);

And to initialize the circles, you also have to call the brushed event after creating the brush by adding .call(brush.event):

  // brush slider display
  context.append("g")
      .attr("class", "x brush")
      .call(brush)
      .call(brush.event)
      .selectAll("rect")
      .attr("y", -6)
      .attr("height", height2 + 7);

2 - use a variable to keep track where is the current range under the brush, and hide the circles that are not in the range by setting the radius to zero(alternatively you can set the visibility)

var currentRange;

var inRange = function(d) { 
    if(!currentRange || d.time < currentRange[0] || d.time > currentRange[1] ) {
        return 0;
    } else {
        return 5;
    }
}

function brushed() {

  currentRange = (brush.empty()? undefined : brush.extent());

  x.domain(brush.empty() ? x2.domain() : brush.extent());
  focus.select(".x.axis").call(xAxis);
  mydots.selectAll(".circle")
   .attr("cx", xMap)
   .attr("cy", yMap)
   .attr("r", inRange); // set radius to zero if it's not in range
  console.log(brush.extent())
}

For the scale definition, it will be better to write something like this:

        .domain([
            d3.min(dataset, function(d){ return d.time; }), 
            d3.max(dataset, function(d){ return d.time; })
        ])

In your example, you have to make sure to parse and initialize the data before you do this.

Upvotes: 2

Bill
Bill

Reputation: 25555

For your first question, you need to call brush.event to get the brush to pick up the new extent.

  brush = d3.svg.brush()
      .x(x)
      .extent([config.start, d3.time.day.offset(config.start, config.range)])
      .on("brush", brushmove)
      .on("brushend", brushend);

  gBrush = svg.select('.brush')
    .call(brush);

  gBrush.call(brush.event);

For your second question, I usually just filter out the data that is outside the brush extent such that I am only drawing the visible data. Here is an example that would be called on your brush move/zoom event:

  // update the data so that only visible points are shown
  var points= svg.selectAll('.point')
      .data(function(d) { return onScreen(d, brush.extent); },
            function(d) { return d.id; });

  // draw the points that are now onscreen
  var pointsEnter = points.enter().append('g').attr('class', 'point');

  // remove any points that are now offscreen
  points.exit().remove();

  // up date the x/y position of your points based on the new axis.
  // ... left up to you

It's helpful to have a unique id for the points so that they can just be translated to their new positions as the brush moves instead of having to destroy them and redraw them.

I have an example that uses these techniques at http://bl.ocks.org/bunkat/1962173.

Upvotes: 3

Related Questions