I'm new to D3 and would like to implement a click-drag-zoom similar to what is shown here: http://www.highcharts.com/demo/line-time-series
I already have a line graph I have built, but am confused as to how to implement this.
I guess I need some JS event handlers to find where my mousedown and mouseup happens. But how do I create the shading that occurs on the graph when the user is dragging?
You'll probably want to use a brush to do this in d3.js. You can see an example that I put together at http://bl.ocks.org/1962173 which does something similar.
The relevant code is:
var brush = d3.svg.brush()
.x(x)
.extent([d3.time.monday(now),d3.time.saturday.ceil(now)])
.on("brush", display);
where display is a function that redraws data based on the current extent of brush. This way you don't need to try and hook your own handlers or even worry about resizing the highlighted region at all.
Related
Good day, trying to figure out one last piece with a code rewrite that we've been working on. We have a bubble chart that animates, to essentially simulate a motion chart, since most of the motion chart libraries we relied on previously incorporated flash.
It seems like the mouseover space for the original render sticks around during animation, and causes the bubbles to "reset", I've noticed the tooltips also stick to the original location. Any ideas/suggestions?
I've copied up a mostly complete version here (the loading of additional variables isn't implemented):
https://nl.communityaccounts.ca/motionchart/motion_dev.asp
I'm working on a standalone jsfiddle as well, can put a link to that soon.
Highcharts uses plotX and plotY point's properties to position the tooltip in a default way. In your case, only properties: x, y, z and point's graphic are updated. You need to also update plotX and plotY:
dataObject.plotX = data.x + data.z;
dataObject.plotY = data.y + data.z;
Live demo: https://jsfiddle.net/BlackLabel/9drynwcz/
I have seen examples here and here were a brush is triggered in JavaScript. I want to understand the implementation of the first one.
Background
The first example bundles two D3 line charts in a single svg container; classes focus and context, respectively:
The context chart (marked in light blue, above) is the one containing the brush, which can be triggered by a mouse click:
When we look inside its group container, we find the designated brush parameters; under the extent class:
Question 1.
I don't understand what happens in the last two lines, in particular the last line:
function drawBrush(a, b) {
// define our brush extent
// note that x0 and x1 refer to the lower and upper bound of the brush extent
// while x2 refers to the scale for the second x-axis, for context or brush area.
// unfortunate variable naming :-/
var x0 = x2.invert(a*width)
var x1 = x2.invert(b*width)
console.log("x0", x0)
console.log("x1", x1)
brush.extent([x0, x1])
// now draw the brush to match our extent
// use transition to slow it down so we can see what is happening
// set transition duration to 0 to draw right away
brush(d3.select(".brush").transition().duration(500));
// now fire the brushstart, brushmove, and brushend events
// set transition the delay and duration to 0 to draw right away
brush.event(d3.select(".brush").transition().delay(10duration(500))
}
In brush(d3.select(".brush").transition().duration(500));, the current brush parameters are selected with a transition precondition; which is passed to brush, so it can draw the new brush according to the changed brush.extend values.
In brush.event(d3.select(".brush").transition().delay(10duration(500)), it seems that the previous line sets the parameters, after which brush.event executes with the new brush parameters. Can someone make sense of this? How do the brush events apply to this case?
Question 2.
I also don't see how exactly, this event action gets linked back to the focused chart. If find the mechanisms via callbacks quite cryptic:
var brush = d3.svg.brush()
.x(x2)
.on("brush", brushed);
This snippet seems crystal-clear: the brush is made and linked to the brush event listener. On a brush event, brushed will act as the event handler. Furthermore, the scale of context's x-axis x2 is passed to the brush, as it sits on the context chart.
But I'm not quite sure how brushed works:
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".area").attr("d", area);
focus.select(".x.axis").call(xAxis);
}
Just to be sure, is it correct that a new axis is generated in focus.select(".x.axis").call(xAxis); with the brush parameters set in x.domain(brush.empty() ? x2.domain() : brush.extent());?
First, there is a typo in the last line. In the code it actually is:
brush.event(d3.select(".brush").transition().delay(1000).duration(500))
Back to your question, the confusion you're facing trying to understand what the brush events have to do with it is quite simple: you're reading the D3 v4 docs, while that code uses D3 v3.
This is brush.event in D3 v3:
brush.event(selection)
If selection is a selection, it dispatches a brush gesture to registered listeners as a three event sequence: brushstart, brush and brushend. This can be useful in triggering listeners after setting the brush extent programatically. (emphasis mine)
As you can clearly see, the first line changes the brush itself (the context), while the second one changes the big area chart (the focus).
I've rendered a d3 map that has pan and zoom enabled, but when scrolling down the viewport either on desktop or mobile, the window gets stuck zooming in the map.
Is there a way to temporarily disable d3.zoom, while the window is scrolling?
I've seen ways of toggling the zoom/pan using a button as seen here: http://jsfiddle.net/0xncswrk/, but I wanted to know if it's possible without having to add a button. Here's my current zoom logic.
Thanks!
this.zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', () => {
this.svg.attr('transform', d3.event.transform);
});
this.svg = d3.select(this.el).append('svg')
.attr('width', '100%')
.attr('height', this.height)
.attr('class', 'bubble-map__svg-us')
.call(this.zoom)
.append('g');
EDIT: Wow old answer but never saw your comment. Sorry about that. Yeah sorry I forgot to consider mobile zooming.
In the documentation, perhaps this is new, but they recommend having more granular control of what zoom you allow by using zoom.filter. For touch related events, they also support zoom.touchable.
As specified in the d3-zoom documentation
To disable just wheel-driven zooming (say to not interfere with native scrolling), you can remove the zoom behavior’s wheel event listener after applying the zoom behavior to the selection:
selection
.call(zoom)
.on("wheel.zoom", null);
You can also consider just setting the scaleExtent to be [1,1] especially if it's just temporary so it locks the zoom to only one possible scale but preferably you opt for what the documentation says :P
Got here because I was dealing with a similar problem. Perhaps for anyone coming after this, a simple way to deal with this might be to use the filter() method that a zoom() instance provides. That will allow you to toggle between applying or ignoring zoom events altogether. It works a little better than temporarily setting null to watchers because - at least in my experience - the events then still get recorded and stacked. As a consequence, you would zoom in or out in unexpected leaps once you re-enabled the handler. The filter actually really ignores what's going on, it seems. To implement:
let isZooming = true; // Use controls to set to true or false
zoom()
// Make sure we only apply zoom events if zooming is enabled by the user
.filter(() => isZooming)
.on('zoom', event => {
// Specify whatever you want to do with the event
});
Doc: https://github.com/d3/d3-zoom#zoom_filter
I'm trying to replicate this Focus+Context via Brushing example. I'm including the same layout, but with a scatterplot instead of a line/area plot.
I started working off this example I found which combines the area plot and a scatterplot. However, when I scrap the area plot, I lose the zoom/focus capability.
My last step (thus far unsuccessful) is to make the brush (small focus bar on the bottom) actually respond to the main panel (make it adjust/zoom in when smaller time periods are selected in the brush). The brush adjusts the axis as it should, but I just haven't been able to make the brush actually adjust/zoom the points on the main scatterplot. I'm not trying plot anything in the brush - there will be a lot of points, so keeping the brush with a grey background and no points is fine.
here's my fiddle: http://jsfiddle.net/fuqzp580/3/
Sidenote: I can't quite get the jsfiddle to work with the way I'm using d3.csv, so I coded up a slightly altered version with dummy data in lieu of using d3.csv. However, I included the d3.csv code (commented out), just in case that could be a cause for my problem.
I'm new to d3 so any pointers or ideas welcome!
Here's an updated fiddle with the dots zooming on the points in the main panel: http://jsfiddle.net/henbox/3uwg92f8/1/
You were very close, I just made 3 small changes:
Firstly, uncommented the code you already had in function brushed() for selecting the dots
Secondly, defined mydots globally (since you were only doing it inside initialize() and it needs to be used beyond this scope). Added this on line 55:
var mydots = focus.append("g");
And last (and most importantly), I changed the definition for xMap from
xMap = function(d) { return x2(d.time); }
to
xMap = function(d) { return x(d.time); }
When brushing, it's the x scale that gets updated, not the x2
I'm using nvd3 for a multi-bar chart, and I'd like to make the chart redraw when the user clicks the other html on my page. I tried using jQuery to select the "Stream0" legend circle on the nvd3 homepage (http://nvd3.org/) and click it using this snippet in the console:
$($('g.nv-series')[0]).click()
For reasons that I hope will be immediately obvious to people more knowledgeable about javascript, nothing happens. Is it something to do with event delegation?
http://nvd3.org/
you can try this:
chart.legend.dispatch.legendClick = function(d, i){
//redraw
};
It will append your own method to the legend; it works for pie chart not sure if works for the line chart;
Maybe there is some help in this. Two charts, one pie, one stack but only showing legends on pie.
The data is not identical but the legends are..
Want to update both on clicking pie legends.
chart.legend.dispatch.on('stateChange.pie', function(d,i){
setTimeout(function() {
stackedAreaChart.dispatch.changeState(d,i);
stackedAreaChart.update();
}, 100);
});
Note: using the ".pie" will extend the (library) stateChange event (not overwrite it)
The other chart stackedAreaChart has to be in the scope.
Note there is a changeState event and a stateChange, best is to look at the un-minified nvd3 js file..