问题
How can I capture the mouse over events of two graphs at once. I need to do something like shown in the image below:
Can anyone guide me as to how should I approach this ?. So far I was able to get the simple mouseover working for a single graph.
回答1:
I'm the author of function-plot which is able to dispatch events to multiple graphs one of them being mouseover
, for example
var width = 300
var height = 180
var a = functionPlot({
target: '#a',
height: height,
width: width,
data: [{ fn: 'x^2' }]
})
var b = functionPlot({
target: '#b',
height: height,
width: width,
data: [{ fn: 'x' }]
})
a.addLink(b)
b.addLink(a)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/function-plot/1.16.5/function-plot.js"></script>
<span id="a" />
<span id="b" />
The solution involves making each of your graphs do something when a certain event is fired, for example d3's way to dispatch events is
// create a dispatcher with the events you will fire in the future
var dispatch = d3.dispatch('mycustomevent');
// add some callbacks (note the namespace)
dispatcher.on('mycustomevent.graph1, function (str) {
// when called str === 'hello world'
})
dispatcher.on('mycustomevent.graph2, function (str) {
// when called str === 'hello world'
})
// fire the event from the dispatcher
// the two callbacks attached are called in the same order
dispatch.mycustomevent('hello world')
In practice, whenever you do mouseover on the graph instead of performing the action right away you fire a custom event and let each graph do whatever it needs to do on mouseover
// create a dispatcher
var dispatch = d3.dispatch('mymouseover');
function graphWrapper(graph) {
return function (xCoord) {
// do something with `xCoord` in `graph`
}
}
dispatcher.on('mymouseover.graph1, graphWrapper(graph1))
dispatcher.on('mymouseover.graph2, graphWrapper(graph2))
// graph1 and graph2 need to fire the custom event
function dispatchMouseOver() {
var xCoord = x.invert(d3.mouse(this)[0])
dispatch.mymouseover(xCoord)
}
graph1.on('mousemove', dispatchMouseOver)
graph2.on('mousemove', dispatchMouseOver)
For the implementation I modified an example made by d3's author cited by @In code veritas with a reusable chart
a reusable graph with independent mouseover
As you see each graph is independent of each other, after the implementation of the pub-sub pattern it looks like this
linked graphs
As a side note I implemented this featuer in function-plot using node's event module basically because in d3 you add a callback using a different name under the same namespace e.g. mymouseover.1
, mymouseover.2
and so on but in node's event module you just do graph.on('event', callback)
multiple times
Useful articles/demos about this topic
- https://bost.ocks.org/mike/chart/
- https://bl.ocks.org/mbostock/5872848
- https://bl.ocks.org/mbostock/3902569
回答2:
It depends on how you create the charts.
If you are using the nested enter pattern (bind data, enter the svgs, then enter each chart from nested data), then it's slightly different than if you have two separately created charts.
But generally, look at this example for an example to follow.
You'll first create undisplayed text and circle overlays:
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
In your case, create them for each chart.
Then set an overlay and capture mouseovers:
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
}
In your case, since your charts have the same widths, you can use the same x transform from the x(d.date) scale return for each chart.
Things are a little tricker for the y value.
You'd probably have something like if you use different datasets. You'll need to use the key index differently if you are nesting off a single dataset:
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
var d02 = data2[i - 1],
d12 = data2[i],
d2 = x0 - d02.date > d12.date - x0 ? d12 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
focuslowerchart.attr("transform", "translate(" + x(d.date) + "," + (yLower(d2.close) + ")");
focuslowerchart.select("text").text(formatCurrencyLower(d.close));
}
The above assumes the same i
indexing between charts. You'll need to bisect differently if the datasets are ordered differently.
来源:https://stackoverflow.com/questions/36992475/d3-mouse-move-on-two-graphs-at-once