D3 Mouse Move on Two Graphs at once

血红的双手。 提交于 2021-02-04 18:49:57

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!