chartjs / Chart.js

Simple HTML5 Charts using the <canvas> tag
https://www.chartjs.org/
MIT License
64.57k stars 11.9k forks source link

Tooltip mode="index" with only one dataset item #11181

Closed alex-statsig closed 1 year ago

alex-statsig commented 1 year ago

Feature Proposal

I would like to be able to only show one dataset in my tooltip: the one closest to the cursor for the hovered x value. This seems possible by using mode="nearest" axis="xy", but this results in cases where despite hovering the 2nd index, it will snap between the 1st/3rd indices because they are closer:

Screen Shot 2023-03-08 at 10 33 12 PM

This seems fixable by using mode="nearest" axis="x", but this results in showing both datapoints for the x value hovered.

I'm even using a custom tooltip renderer, but don't see any way to access the event triggering the tooltip to filter the results.

Possible Implementation

An additional mode could be added for "nearest-y-within-nearest-x"

stockiNail commented 1 year ago

@alex-statsig just an hint. Have you tried to implement your interaction mode by yourself? See doc https://www.chartjs.org/docs/latest/configuration/interactions.html#custom-interaction-modes

alex-statsig commented 1 year ago

@alex-statsig just an hint. Have you tried to implement your interaction mode by yourself? See doc https://www.chartjs.org/docs/latest/configuration/interactions.html#custom-interaction-modes

I hadn't realized that was possible, this should solve my problem!

stockiNail commented 1 year ago

Should be something like that (if I have understood well your logic):

Interaction.modes.myMode = function(chart, e, options, useFinalPosition) {
  const indexItems = Interaction.modes.index(chart, e, options, useFinalPosition);
  return [indexItems.reduce(function(pre, cur) {
    if (pre) {
      return pre.element.y > cur.element.y ? pre : cur;
    }
    return cur;
  })];
};

And set tooltip.mode = 'myMode' (or what you want as name).

EDIT: it's to using the index instead of nearest. Code changed

alex-statsig commented 1 year ago

Should be something like that (if I have understood well your logic):

Interaction.modes.myMode = function(chart, e, options, useFinalPosition) {
  const indexItems = Interaction.modes.index(chart, e, options, useFinalPosition);
  return [indexItems.reduce(function(pre, cur) {
    if (pre) {
      return pre.element.y > cur.element.y ? pre : cur;
    }
    return cur;
  })];
};

And set tooltip.mode = 'myMode' (or what you want as name).

EDIT: it's to using the index instead of nearest. Code changed

Thanks, yeah I got something working using this, with some slight fixes (handling no items and fixing to find the closest to the mouse, not the highest y):

const nearestWithinNearestX: InteractionModeFunction = function (
  chart,
  e,
  options,
  useFinalPosition,
) {
  const indexItems = Interaction.modes.index(
    chart,
    e,
    options,
    useFinalPosition,
  );
  if (indexItems.length === 0) {
    return [];
  }
  return [
    indexItems.reduce(function (pre, cur) {
      if (pre) {
        return Math.abs(pre.element.y - (e.y ?? 0)) <
          Math.abs(cur.element.y - (e.y ?? 0))
          ? pre
          : cur;
      }
      return cur;
    }),
  ];
};
Interaction.modes.nearestWithinNearestX = nearestWithinNearestX;