c3js / c3

:bar_chart: A D3-based reusable chart library
http://c3js.org
MIT License
9.34k stars 1.39k forks source link

Tooltip Synchronization between multiple graphs #2769

Open nagarajanceg opened 4 years ago

nagarajanceg commented 4 years ago

Spline type C3 - working as my expectation: codepen link: spline graph

Screen Shot 2020-03-03 at 3 44 50 PM

Timeseries type C3- Doesn't work as above: codepen link: Timeseries graph

Screen Shot 2020-03-03 at 3 44 06 PM

I am not pretty sure about the whether this is a proper way of tooltip synchronization. It would be very grateful if it's possible to identify why it's happening properly at spline and not happening at timeseries one. Please let me know if there are any alternative better ideas to do this. I would like to appreciate any help on this.

nagarajanceg commented 4 years ago

On looking closely to the c3 library. It's possible to figure out a way to address programmatically show tooltip over timeseries chart. But it need a slight modification in the library.

Based on my understanding , the reason for spline type chart having programmatic tooltip visible based on the x-axis index. So format function of tooltip can take index value and show tooltip to corresponding x-axis index. The following code have done the magic.

tooltip:{
        grouped: true,
        format: {
          value: function(x, ratio, id, ind){
               Chart.tooltip.show({x: ind}) 
          }
}

C3 library code which address the tooltip show

Chart.prototype.tooltip.show = function (args) {
    var $$ = this.internal,
        targets,
        data,
        mouse = {}; // determine mouse position on the chart
    if (args.mouse) {
      mouse = args.mouse;
    } else {
      // determine focus data
      if (args.data) {
        data = args.data;
      } else if (typeof args.x !== 'undefined') {
        if (args.id) {
          targets = $$.data.targets.filter(function (t) {
            return t.id === args.id;
          });
        } else {
          targets = $$.data.targets;
        }
        data = $$.filterByX(targets, args.x).slice(0, 1)[0];
      }
      mouse = data ? $$.getMousePosition(data) : null;
    } // emulate mouse events to show
    $$.dispatchEvent('mousemove', mouse);
    $$.config.tooltip_onshow.call($$, data);
  };

FilterByX method:

 ChartInternal.prototype.filterByX = function (targets, x) {
    return this.d3.merge(targets.map(function (t) {
      return t.values;
    })).filter(function (v) {
      return v.x - x === 0;
    });
  };

This FilterByX method filters the Integer value and provide the tooltip to show the content. But in case of Timeseries Chart, the passing X value is date format (actual string)

{
  "x": "2013-01-01T08:00:00.000Z",
  "value": 30,
  "id": "data1",
  "index": 0
}

Calling tooltip show method in the Timeseries chart as same way of Spline chart doesn't work. Because 'x' is a date string rather than the integer index and filterByX method failed in comparison. So tooltip over the timeseries not worked as expected from this example

tooltip:{
        grouped: true,
        format: {
          value: function(x, ratio, id, ind){
               Chart.tooltip.show({x: ind}) 
          }
}

Even the call can change like this way(Chart.tooltip.show({index: ind}) ) doesn't work because tooltip.show method have support FilterByX method which expects 'X' value alone not depends on index. For Spline or any other charts 'X' value hold index.

I tweak a little bit to the tooltip.show method which actually works well for timeseries programmatic tooltip. Even the timeseries data has index value properly. I have given a try to extend the functionality of tooltip.show method by add the existing method filterByIndex. Modified tooltip.show method:

Chart.prototype.tooltip.show = function (args) {
    var $$ = this.internal,
        targets,
        data,
        mouse = {}; // determine mouse position on the chart
    if (args.mouse) {
      mouse = args.mouse;
    } else {
      // determine focus data
      if (args.data) {
        data = args.data;
      } else if (typeof args.x !== 'undefined') {
        if (args.id) {
          targets = $$.data.targets.filter(function (t) {
            return t.id === args.id;
          });
        } else {
          targets = $$.data.targets;
        }
        data = $$.filterByX(targets, args.x).slice(0, 1)[0];
      } else if(args.index !== 'undefined'){
        targets = $$.data.targets;
        data = $$.filterByIndex(targets, args.index).slice(0,1)[0];
      }
      mouse = data ? $$.getMousePosition(data) : null;
    } // emulate mouse events to show
    $$.dispatchEvent('mousemove', mouse);
    $$.config.tooltip_onshow.call($$, data);
  };

Modified the tooltip.show usage in user code:

tooltip:{
        grouped: true,
        format: {
          value: function(x, ratio, id, ind){
               Chart.tooltip.show({index: ind})
          }
}

In short: $$.filterByIndex method added to the tootip.show in addition to the $$.filterByX and calling the tooltip changed from Chart.tooltip.show({x: ind}) to Chart.tooltip.show({index: ind})

I hope this changes can be added to library and it can help to do programmatically show tooltip at Timeseries Chart. I really appreciate if someone let me know any other alternative solution that's doesn't require to change the library and make it simpler way.