Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variations on nearest interaction mode #9961

Closed
joshkel opened this issue Dec 6, 2021 · 6 comments · Fixed by #10046
Closed

Variations on nearest interaction mode #9961

joshkel opened this issue Dec 6, 2021 · 6 comments · Fixed by #10046

Comments

@joshkel
Copy link
Contributor

joshkel commented Dec 6, 2021

Feature Proposal

Either add some sort of nearestPerDataset mode and some sort of intersectRadius or maxDistance option, or make it easier to create new interaction modes by exporting more functions from core.interaction.js.

Feature Use Case

I have some dense data with uneven X values and would like for the tooltip to display the nearest value for each dataset. The current Chart.js interaction modes don't do what I want:

  • x displays multiple values per dataset, because the data is dense enough that there's more than one value within range.
  • nearest looks for the single nearest X coordinate. Since the datasets have uneven X values, one or more datasets are often excluded.

See https://codesandbox.io/s/chartjs-interaction-modes-pkf2b?file=/src/index.js for a demo.

To solve this, I'm locally experimenting with a variation of nearest that determines the nearest element per dataset and returns all nearest-per-dataset elements whose distance is close enough to minDistance.

Unrelated to this, I was also experimenting with a variation on {mode: 'nearest', axis: 'xy'} that only shows items if the cursor is relatively close to a data point. I wanted something that's more tolerant than intersect: true but not so broad that it pops up a tooltip at the other end of the chart if you mouse over a large, sparse chart. (In other words, it's identical to core.interaction.js's getNearestItems with !intersect, except that it would also check minDistance against some sort of intersectRadius or maxDistance option.)

I suspect these options are specialized enough that they shouldn't necessarily go into Chart.js, and I'm fine with implementing them myself. However, it feels hard to implement them myself: if I want behavior similar to nearest, I have to reference private members and duplicate a lot of non-exported functionality from core.interaction.js.

So, instead of adding more specialized interaction modes and options, what about exporting more of core.interaction.js?

Possible Implementation

A simple exported function could look like this:

export function evaluateItems(callback, chart, position, axis, intersect?, useFinalPosition?) {
  const minPadding = chart._minPadding;
  if (!_isPointInArea(position, chart.chartArea, minPadding)) {
    return;
  }

  const evaluationFunc = (element, datasetIndex, index) => {
    if (intersect && !element.inRange(position.x, position.y, useFinalPosition)) {
      return;
    }

    const center = element.getCenterPoint(useFinalPosition);
    if (
      !_isPointInArea(center, chart.chartArea, minPadding) &&
      !element.inRange(position.x, position.y, useFinalPosition)
    ) {
      return;
    }
    callback(element, datasetIndex, index);
  };

  optimizedEvaluateItems(chart, axis, position, evaluationFunc);
}

There may also be room to consolidate some of the core.interaction.js modes (so they share more implementation and so their implementation is closer to a hypothetical exported evaluateItems).

I can work on a PR if this seems reasonable / desirable.

@joshkel joshkel changed the title Variations on nearest Variations on nearest interaction mode Dec 6, 2021
@kurkle
Copy link
Member

kurkle commented Dec 6, 2021

Just a quick comment about intersect: true, I think we are considering hitRadius in that case, at least for Point elements.

@kurkle
Copy link
Member

kurkle commented Dec 6, 2021

Another note FYI, we are missing a mode similar to nearest, but considering value instead of coordinates (or something similar), which would hit all the bars in a category + any other elements having the same x and/or y value.

@kurkle
Copy link
Member

kurkle commented Dec 14, 2021

I can work on a PR if this seems reasonable / desirable.

User defined interaction mode(s) are supported and I see no reason not to try making it as easy as possible.
The other side is the exposed public API that essentially freezes the code base on that part. So new exports need to be considered carefully.

As both me and @etimberg are quite busy with other stuff currently, I'd encourage you to draft a PR for consideration. To minimize repeated work, I think it would be best to start with code changes, leaving documentation and types out from the first draft.

@etimberg
Copy link
Member

Yup, I'm happy to look at a PR. I agree with starting with just the code changes since there will probably be some iterations to get to the final design

@joshkel
Copy link
Contributor Author

joshkel commented Jan 7, 2022

Another note FYI, we are missing a mode similar to nearest, but considering value instead of coordinates (or something similar), which would hit all the bars in a category + any other elements having the same x and/or y value.

This is tracked in #9974, if I'm understanding correctly.

FWIW, that's not quite what I was looking for with nearestPerDataset - it would avoid displaying multiple values for dense data (which is what I wanted), but it sounds like it wouldn't accommodate uneven data rates (the ability to display something for each dataset when one dataset is at X coordinates 0, 1, 2, and another at 0.01, 1.01, 2.01).

@etimberg etimberg added this to the Version 3.8.0 milestone Feb 12, 2022
@etimberg etimberg linked a pull request Feb 12, 2022 that will close this issue
@benzvan
Copy link

benzvan commented Mar 31, 2023

FWIW, that's not quite what I was looking for with nearestPerDataset - it would avoid displaying multiple values for dense data (which is what I wanted), but it sounds like it wouldn't accommodate uneven data rates (the ability to display something for each dataset when one dataset is at X coordinates 0, 1, 2, and another at 0.01, 1.01, 2.01).

Oddly, I still seem to be getting multiple matching points from one dataset in my tooltip, which I solved by hiding every other point from interactions. It's hacky but it does the trick.

pointHitRadius: (point) => {
    return point.index % 2 == 0 ? 1 : 0 
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants