简体   繁体   中英

D3 Mouse Move on Two Graphs at once

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.

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 eg 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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM