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

Improve Tooltip and Hover Interaction #3400

Merged
merged 9 commits into from
Oct 3, 2016
Merged

Improve Tooltip and Hover Interaction #3400

merged 9 commits into from
Oct 3, 2016

Conversation

etimberg
Copy link
Member

@etimberg etimberg commented Oct 1, 2016

Significant improvements to the tooltip and hover modes

New Configuration

In addition to the hover.mode and tooltips.mode settings, hover.intersect and tooltips.intersect have been added. The intersect settings are used to configure certain modes so that when items are shown can be controlled

New Modes

Mode Description
point Returns all items under the point
index renamed 'label' mode
nearest Returns the nearest item. Using in conjunction with intersect set to true works well for points that are hidden behind
x returns items that intersect the x coordinate
y returns items that intersect the y coordinate

Point Mode

point mode

Nearest Mode, Intersect On

nearest with intersect

Nearest Mode, No Intersect

nearest no intersect

Deprecated Modes

The 'single' and 'x-axis' modes are deprecated.
Single mode has been replaced by 'point' which returns all of the items that intersect with the cursor
The X axis mode has been replaced by the index mode coupled with setting intersect to true.

…on.modes in preparation for adding new modes
…dd new intersect, nearest and nearestIntersect modes
…g with an `intersect` boolean.

If the boolean is true, we only base our decisions on the things we intersect with.
If it is false, we use the nearest item.

Implemented the use of the boolean for dataset, index/label, and nearest modes.
…ad of just the intersect boolean for more flexibility
@nakkeeran
Copy link

💯 👍 thanks @etimberg

@@ -212,7 +212,8 @@ Name | Type | Default | Description
--- | --- | --- | ---
enabled | Boolean | true | Are tooltips enabled
custom | Function | null | See [section](#advanced-usage-external-tooltips) below
mode | String | 'single' | Sets which elements appear in the tooltip. Acceptable options are `'single'`, `'label'` or `'x-axis'`. <br>&nbsp;<br>`single` highlights the closest element. <br>&nbsp;<br>`label` highlights elements in all datasets at the same `X` value. <br>&nbsp;<br>`'x-axis'` also highlights elements in all datasets at the same `X` value, but activates when hovering anywhere within the vertical slice of the x-axis representing that `X` value.
mode | String | 'single' | Sets which elements appear in the tooltip. See [Interaction Modes](#interaction-modes) for details
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If single is deprecated, we should replace the default value by its equivalent, which I think is {mode: 'nearest', intersect: true}, right?

@@ -288,8 +289,28 @@ Name | Type | Default | Description
--- | --- | --- | ---
mode | String | 'single' | Sets which elements hover. Acceptable options are `'single'`, `'label'`, `'x-axis'`, or `'dataset'`. <br>&nbsp;<br>`single` highlights the closest element. <br>&nbsp;<br>`label` highlights elements in all datasets at the same `X` value. <br>&nbsp;<br>`'x-axis'` also highlights elements in all datasets at the same `X` value, but activates when hovering anywhere within the vertical slice of the x-axis representing that `X` value. <br>&nbsp;<br>`dataset` highlights the closest dataset.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this option also uses the new modes.

Mode | Behaviour
--- | ---
point | Finds all of the items that intersect the point
nearest | Gets the item that is nearest to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). If 2 or more items are at the same size, the one with the smallest area is used. If `intersect` is true, this is only triggered when the mouse is above an item the graph. This is very useful for combo charts where points are hidden behind bars.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... at the same size, ... should be ... at the same distance, ... (or position)
... is above an item the graph. doesn't sound right :)

--- | ---
point | Finds all of the items that intersect the point
nearest | Gets the item that is nearest to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). If 2 or more items are at the same size, the one with the smallest area is used. If `intersect` is true, this is only triggered when the mouse is above an item the graph. This is very useful for combo charts where points are hidden behind bars.
single (deprecated) | Finds the first item that intersects the point and returns it.
Copy link
Member

@simonbrunel simonbrunel Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could also add something like:

Behaves like 'nearest' mode with intersect = true

nearest | Gets the item that is nearest to the point. The nearest item is determined based on the distance to the center of the chart item (point, bar). If 2 or more items are at the same size, the one with the smallest area is used. If `intersect` is true, this is only triggered when the mouse is above an item the graph. This is very useful for combo charts where points are hidden behind bars.
single (deprecated) | Finds the first item that intersects the point and returns it.
label | Finds item at the same index. If the `intersect` setting is true, the first intersecting item is used to determine the index in the data. If `intersect` false the nearest item is used to determine the index.
index | Same as `'label'` mode but with a more descriptive name
Copy link
Member

@simonbrunel simonbrunel Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we deprecate label as well? And then have the detailed description on index, and label (deprecated) above.

*/
point: function(chartInstance, e) {
var eventPosition = helpers.getRelativePosition(e, chartInstance.chart);
var elementsArray = getIntersectItems(chartInstance, eventPosition);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return getIntersectItems(chartInstance, eventPosition);

@@ -31,6 +31,7 @@ module.exports = function() {
hover: {
onHover: null,
mode: 'single',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use nearest as default?

@@ -8,6 +8,7 @@ module.exports = function(Chart) {
enabled: true,
custom: null,
mode: 'single',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use nearest as default?

},
inLabelRange: function(mouseX, mouseY) {
var me = this;
var inRange = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!me._view) {
    return false;
}

var inRange = false;
var bounds = getBarBounds(me);
...

(mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y)) :
false;
var center;
if (isVertical(this)) {
Copy link
Member

@simonbrunel simonbrunel Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var x ,y;

if (isVertical(this)) {
   x = ...
   y = ...
} else {
   x = ...
   y = ...
}

return {x: x, y: y};

@simonbrunel
Copy link
Member

Awesome work, this will make chart interactions much better :) My main concern is about interactions performance, I don't think we should rely on any callback based iterations (see this comment).

Adding a minor cosmetic feedback, which is really a personal preference: when there is no confusion possible, I prefer short variable names which make code easier to read. For example, I would not use chartInstance but simply chart (even in tests since all other tests now use chart). Same for elementsArray -> elements, eventPosition -> position, etc. It would be also more consistent since you don't use elementInstance :) What do you think?

@etimberg
Copy link
Member Author

etimberg commented Oct 2, 2016

Sounds good. I will make these changes. 😄

@etimberg
Copy link
Member Author

etimberg commented Oct 2, 2016

@simonbrunel I updated per your code review comments. I agree that we should avoid helpers.each where possible. Since we no longer support IE8 we can even replace a lot of the helpers.each calls with Array.prototype.forEach since it is available on all our platforms.

Edit: I also updated all of the samples to use the new modes. (Samples in general need some updates to have consistent colours, etc but that can be a different PR)

@simonbrunel
Copy link
Member

Agree, helpers.each is only useful when we don't know if it's an array or object. When iterating over the datasets/data, we should use a for loop to avoid the callback cost.

* @param position {Point} the point to be nearest to
* @return {ChartElement[]} the nearest items
*/
function getNearestItemsFromChart(chart, position) {
Copy link
Member

@simonbrunel simonbrunel Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can also refactor these two similar getNearestItems* methods by adding intersect as parameter:

function getNearestItems(chart, position, intersect) {
    // ...
   parseVisibleItems(chart, function(element) {
        if (intersect && !element.inRange(position.x, position.y)) {
            return;
        }

        var distance = Math.round(helpers.distanceBetweenPoints(position, element.getCenterPoint()));
    // ...
}

*/
dataset: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItemsFromChart(chart, position);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);


function indexMode(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItemsFromChart(chart, position);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false);

*/
nearest: function(chart, e, options) {
var position = helpers.getRelativePosition(e, chart.chart);
var nearestItems = options.intersect ? getNearestItems(getIntersectItems(chart, position), position) : getNearestItemsFromChart(chart, position);
Copy link
Member

@simonbrunel simonbrunel Oct 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var items = getNearestItems(chart, position, options.intersect);

@etimberg
Copy link
Member Author

etimberg commented Oct 2, 2016

Sure, i will do that refactoring.

@etimberg
Copy link
Member Author

etimberg commented Oct 3, 2016

@simonbrunel I made the last changes and squashed the refactoring commits together.

@etimberg
Copy link
Member Author

etimberg commented Oct 3, 2016

Are we good to merge this?

@etimberg etimberg deleted the v2.4-tooltips branch November 1, 2016 01:32
exwm pushed a commit to exwm/Chart.js that referenced this pull request Apr 30, 2021
Refactored interaction modes to use lookup functions in Chart.Interaction.modes and added new modes for 'point', 'index', 'nearest', 'x', and 'y'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants