Multiple cursor tooltips on scatter chart

Normally, XYCursor, if added to a chart will try to show a tooltip for each series for the hovered date or category. This does not work for charts that have both X and Y axes as ValueAxis. This tutorial will show how we can fix that.

The problem

Say, we have an XYChart with three series plotting on numeric values for both X and Y.

This means we are using ValueAxis for both X and Y.

Now we want to display a tooltip for all series over same hovered X value.

That would be easy if our X axis was DateAxis or CategoryAxis: just add an XYCursor and set tooltipText for all series:

let lineSeries = chart.series.push(new am4charts.LineSeries());
lineSeries.dataFields.valueY = "ay";
lineSeries.dataFields.valueX = "ax";
lineSeries.tooltipText = "X: {valueX} / Y: {valueY}";

let lineSeries2 = chart.series.push(new am4charts.LineSeries());
lineSeries2.dataFields.valueY = "by";
lineSeries2.dataFields.valueX = "bx";
lineSeries2.tooltipText = "X: {valueX} / Y: {valueY}";
var lineSeries = chart.series.push(new am4charts.LineSeries());
lineSeries.dataFields.valueY = "ay";
lineSeries.dataFields.valueX = "ax";
lineSeries.tooltipText = "X: {valueX} / Y: {valueY}";

var lineSeries2 = chart.series.push(new am4charts.LineSeries());
lineSeries2.dataFields.valueY = "by";
lineSeries2.dataFields.valueX = "bx";
lineSeries2.tooltipText = "X: {valueX} / Y: {valueY}";
{
  // ...
  "series": [{
    "type": "LineSeries",
    "tooltipText": "X: {valueX} / Y: {valueY}",
    "dataFields": {
      "valueY": "ay",
      "valueX": "ax"
    }
  }, {
    "type": "LineSeries",
    "tooltipText": "X: {valueX} / Y: {valueY}",
    "dataFields": {
      "valueY": "by",
      "valueX": "bx"
    }
  }]
}

This won't work with a all-ValueAxis scenario, though.

The solution

The solution is to give ValueAxis an ability to find the closest data item to cursor's position.

To do that all we need to do is to create a method that does it. It needs to be called getSeriesDataItem().

If we want to enable this for all charts on the page, we can override ValueAxis class prototype:

am4charts.ValueAxis.prototype.getSeriesDataItem = function(series: am4charts.XYSeries, position: number): am4charts.XYSeriesDataItem {
  const key: string = this.axisFieldName + this.axisLetter;
  const value: number = this.positionToValue(position);
  const dataItem = series.dataItems.getIndex(series.dataItems.findClosestIndex(value, (x: any) => {
    return x[key] ? <number>x[key] : undefined;
  }, "any"));
  return dataItem;
}
am4charts.ValueAxis.prototype.getSeriesDataItem = function(series, position) {
  var key = this.axisFieldName + this.axisLetter;
  var value = this.positionToValue(position);
  var dataItem = series.dataItems.getIndex(series.dataItems.findClosestIndex(value, function(x) {
    return x[key] ? x[key] : undefined;
  }, "any"));
  return dataItem;
}

Or, if we want to enable it just for a single ValueAxis instance:

valueAxisX.getSeriesDataItem = function(series: am4charts.XYSeries, position: number): am4charts.XYSeriesDataItem {
  const key: string = this.axisFieldName + this.axisLetter;
  const value: number = this.positionToValue(position);
  const dataItem = series.dataItems.getIndex(series.dataItems.findClosestIndex(value, (x: any) => {
    return x[key] ? <number>x[key] : undefined;
  }, "any"));
  return dataItem;
}
valueAxisX.getSeriesDataItem = function(series, position) {
  var key = this.axisFieldName + this.axisLetter;
  var value = this.positionToValue(position);
  var dataItem = series.dataItems.getIndex(series.dataItems.findClosestIndex(value, function(x) {
    return x[key] ? x[key] : undefined;
  }, "any"));
  return dataItem;
}

Examples

All ValueAxis

See the Pen Scatter Chart with cursor and tooltips for all series by amCharts team (@amcharts) on CodePen.24419

Single ValueAxis

See the Pen Scatter Chart with cursor and tooltips for all series by amCharts team (@amcharts) on CodePen.24419