Skip to content

Improvements to hoveranywhere / clickanywhere feature - #7966

Open
emilykl wants to merge 6 commits into
v4.0from
add-xpixel-ypixel-unhover
Open

Improvements to hoveranywhere / clickanywhere feature#7966
emilykl wants to merge 6 commits into
v4.0from
add-xpixel-ypixel-unhover

Conversation

@emilykl

@emilykl emilykl commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Closes #7886

This PR adds two enhancements to the hoveranywhere / clickanywhere feature, as suggested in #7886:

  • Add top-level xPixel and yPixel keys to hover and click event data, corresponding to the cursor's pixel position relative to the top-left corner of the graph div
    • This PR adds xPixel and yPixel to all hover and click events, regardless of whether hoveranywhere or clickanywhere are enabled
    • Note: The xPixel and yPixel keys for individual points in the points array correspond to the pixel position of the point itself, while top-level xPixel and yPixel correspond to the pixel position of the cursor
  • If hoveranywhere is enabled, emit a plotly_unhover event when the cursor leaves the plot area

The PR also adds and updates Jasmine tests for the above behavior.

Steps for testing

  • Check out this branch
  • Run npm ci && npm run build
  • Create the following HTML file in the repo root. This HTML page creates a plotly.js plot where the x-axis is a date axis and the y-axis is a category axis:
Details
<!doctype html>
<meta charset="utf-8">
<title>xPixel / yPixel and unhover</title>
<!-- <script src="dist/plotly.js"></script> -->
<script src="https://cdn.plot.ly/plotly-4.0.0-rc.0.min.js" charset="utf-8"></script>

<div id="gd" style="width:700px;height:400px"></div>
<button id="hoverBtn"></button>
<button id="clickBtn"></button>

<script>
    var gd = document.getElementById('gd');

    Plotly.newPlot(gd, [{
        x: [1, 2, 3],
        y: [1, 3, 2],
        mode: 'markers',
        marker: {size: 12}
    }], {
        hoveranywhere: true,
        clickanywhere: true,
        title: {text: 'Demo: xPixel, yPixel, plotly_unhover'}
    });

    function makeToggle(id, attr) {
        var button = document.getElementById(id);

        function render() {
            button.textContent = attr + ' is ' + String(gd.layout[attr]).toUpperCase() + '. Click to set to ' + String(!gd.layout[attr]).toUpperCase() + '.';
        }

        button.onclick = function() {
            Plotly.relayout(gd, attr, !gd.layout[attr]).then(render);
        };

        render();
    }

    makeToggle('hoverBtn', 'hoveranywhere');
    makeToggle('clickBtn', 'clickanywhere');

    function show(pixel) {
        return pixel === undefined ? '(not present)' : pixel;
    }

    function logPixels(name) {
        return function(d) {
            console.log(name + ': xPixel = ' + show(d.xPixel) + ', yPixel = ' + show(d.yPixel));
        };
    }

    gd.on('plotly_hover', logPixels('PLOTLY_HOVER'));
    gd.on('plotly_click', logPixels('PLOTLY_CLICK'));
    gd.on('plotly_unhover', function() {
        console.log('PLOTLY_UNHOVER');
    });
</script>
  • Open the HTML file in the browser and open the console. A log statement will appear for every plotly_click, plotly_hover, and plotly_unhover event. For plotly_click and plotly_hover, the xPixel and yPixel values will also be logged.
    • Confirm that xPixel and yPixel are always present and that their values seem accurate, regardless of whether hoveranywhere or clickanywhere are enabled
    • Confirm that a single plotly_unhover event is emitted when the cursor leaves the plot area when hoveranywhere is enabled
  • Check out v4.0 and run npm ci && npm run build (or edit the script tag to link to the plotly.js 4.0.0 RC on the CDN)
  • Refresh the browser and note that xPixel and yPixel are never present in the event data, and also that a plotly_unhover event is never emitted when the cursor leaves the plot area

@emilykl
emilykl requested a review from camdecoster August 14, 2026 16:45
Comment thread draftlogs/7966_add.md
@@ -0,0 +1 @@
- Add top-level `xPixel` and `yPixel` keys to hover and click event data, corresponding to the pixel position of the cursor relative to the top-left corner of the graph div. Also, when `hoveranywhere` is enabled, emit a `plotly_unhover` event when the cursor leaves the plot area. [[#7966](https://github.com/plotly/plotly.js/pull/7966)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
- Add top-level `xPixel` and `yPixel` keys to hover and click event data, corresponding to the pixel position of the cursor relative to the top-left corner of the graph div. Also, when `hoveranywhere` is enabled, emit a `plotly_unhover` event when the cursor leaves the plot area. [[#7966](https://github.com/plotly/plotly.js/pull/7966)]
- Add top-level `xPixel` and `yPixel` keys to hover and click event data, corresponding to the pixel position of the cursor relative to the top-left corner of the graph div [[#7966](https://github.com/plotly/plotly.js/pull/7966)]
- When `hoveranywhere` is enabled, a `plotly_unhover` event will be emitted when the cursor leaves the plot area

if(gd._hoverAnywhereActive) {
gd._hoverAnywhereActive = false;

if(evt && evt.target && !oldhoverdata) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if(evt && evt.target && !oldhoverdata) {
if(evt?.target && !oldhoverdata) {

it('does not emit unhover on leaving empty space when hoveranywhere is false', (done) => {
var events = [];

makePlot(gd)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It might be good to make this explicit:

Suggested change
makePlot(gd)
makePlot(gd, { hoveranywhere: false })

Comment thread test/plot-schema.json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you run npm run schema to regenerate the TS types with these new descriptions? You might also need to update the hand written types.

Comment on lines +486 to +487
gd._hoverPointerX = evt.pointerX;
gd._hoverPointerY = evt.pointerY;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do these need to be moved out of this block to ensure that they're available without clickanywhere?

// Special handling for `hoveranywhere`, to ensure we emit exactly one unhover event
// when the cursor leaves the plot area.
// gd._hoverAnywhereActive is set in fx/hover.js when we emit an empty-space hover event.
if(gd._hoverAnywhereActive) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we update the guard to also check if hoveranywhere is active? Someone trying to break things could turn off hoveranywhere with relayout and then _hoverAnywhereActive would still be true.

Suggested change
if(gd._hoverAnywhereActive) {
if (gd._fullLayout?.hoveranywhere && gd._hoverAnywhereActive) {

Comment on lines +30 to +33
gd.emit('plotly_unhover', {
event: evt,
points: []
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This event doesn't check to see if plotly_beforehover is false, but it probably should to preserve that behavior. The current check happens inside raw. You could save that result on gd (or somewhere else) and use it in the conditional check.

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.

2 participants