Reputation: 702
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/
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);
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:
I'm new to d3.js
, so all tips & advice is welcome and appreciated!
Upvotes: 4
Views: 4853
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
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