Skip to content

Fix unhover event data for gl3d subplots #5954

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

Merged
merged 7 commits into from
Oct 12, 2021
7 changes: 3 additions & 4 deletions src/plots/gl3d/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ proto.render = function() {
return Axes.hoverLabelText(ax, val, hoverformat);
}

var oldEventData;

if(lastPicked !== null) {
var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate);
trace = lastPicked.data;
Expand Down Expand Up @@ -456,10 +454,11 @@ proto.render = function() {
gd.emit('plotly_hover', eventData);
}

oldEventData = eventData;
this.oldEventData = eventData;
Copy link
Contributor

Choose a reason for hiding this comment

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

What if we change this line to

if(eventData) this.oldEventData = eventData;

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think this does the trick, unfortunately. Here's the logging output with this change.

LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', Object{points: [Object{x: ..., y: ..., z: ..., data: ..., fullData: ..., curveNumber: ..., pointNumber: ..., marker.symbol: ..., marker.size: ..., marker.line.color: ..., marker.color: ..., bbox: ...}]}
LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', undefined

this.oldEventData still gets set back to undefined after the plotly_unhover event has been emitted.

gd.emit('plotly_unhover', this.oldEventData);    
this.oldEventData = undefined;

I could remove this.oldEventData = undefined; and then we could be pretty confident that the unhover event will have event data, as long as the user has hovered at least one point since graph creation (we'll still get undefined event data for renders before a point has been hovered). Here's the logging output.

LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', Object{points: [Object{x: ..., y: ..., z: ..., data: ..., fullData: ..., curveNumber: ..., pointNumber: ..., marker.symbol: ..., marker.size: ..., marker.line.color: ..., marker.color: ..., bbox: ...}]}
LOG: 'UNHOVER', Object{points: [Object{x: ..., y: ..., z: ..., data: ..., fullData: ..., curveNumber: ..., pointNumber: ..., marker.symbol: ..., marker.size: ..., marker.line.color: ..., marker.color: ..., bbox: ...}]}
LOG: 'UNHOVER', Object{points: [Object{x: ..., y: ..., z: ..., data: ..., fullData: ..., curveNumber: ..., pointNumber: ..., marker.symbol: ..., marker.size: ..., marker.line.color: ..., marker.color: ..., bbox: ...}]}

So this would solve the issue for the test case, but I think this behavior is misleading.

} else {
Fx.loneUnhover(svgContainer);
gd.emit('plotly_unhover', oldEventData);
gd.emit('plotly_unhover', this.oldEventData);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
gd.emit('plotly_unhover', this.oldEventData);
if(this.oldEventData) gd.emit('plotly_unhover', this.oldEventData);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps we shouldn't fire Fx.loneUnhover either if this.oldEventData is nullish?

if (this.oldEventData) {
    Fx.loneUnhover(svgContainer);
    gd.emit('plotly_unhover', this.oldEventData);
    this.oldEventData = undefined;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe. But that could possibly be done in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

@dwoznicki did you try my suggestion above? #5954 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, and it solves the issue as far as I can tell.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then please commit the suggestion :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I did here 46c5b8f, but I did it in my local copy instead of through the GitHub UI. Is that okay?

this.oldEventData = undefined;
}

scene.drawAnnotations(scene);
Expand Down
71 changes: 71 additions & 0 deletions test/jasmine/tests/gl3d_hover_click_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,77 @@ describe('Test gl3d trace click/hover:', function() {
.then(done, done.fail);
});

it('@gl should emit correct event data on unhover', function(done) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this test to the end of Test gl3d trace click/hover: block and it will solve the race condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, well, I tried moving the test to the bottom of the Test gl3d trace click/hover: block, and the race still appears to be present.

To be clear, I don't think there is a race between this test and other tests; as far as I can tell, they're run sequentially in the browser. I do think there's a race to check ptData before it is reassigned by a pseudo unhover event triggered by the graph re-rendering. For example, if I change

gd.on('plotly_unhover', function(eventData) {
    ptData = eventData.points[0];
});

to

gd.on('plotly_unhover', function(eventData) {
    console.log("UNHOVER", eventData);
    if (eventData) {                 
        ptData = eventData.points[0];
    } else {
        ptData = {};                 
    }                                
});

and run the full gl3d_hover_click test suite, I get logging output

LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', Object{points: [Object{x: ..., y: ..., z: ..., data: ..., fullData: ..., curveNumber: ..., pointNumber: ..., marker.symbol: ..., marker.size: ..., marker.line.color: ..., marker.color: ..., bbox: ...}]}
LOG: 'UNHOVER', undefined
LOG: 'UNHOVER', undefined

I assume the undefined event data is due to Scene.prototype.render being called a bunch of times for reasons unrelated to mouse movement.

Interestingly, I don't get any unhover events with undefined event data when I run this test as a standalone using fit.

var _mock = Lib.extendDeep({}, mock2);
var x = 655;
var y = 221;

function _hover() {
mouseEvent('mouseover', x, y);
}

function _unhover() {
return new Promise(function(resolve) {
var x0 = x;
var y0 = y;
var initialElement = document.elementFromPoint(x0, y0);
var canceler = setInterval(function() {
x0 -= 2;
y0 -= 2;
mouseEvent('mouseover', x0, y0);

var nowElement = document.elementFromPoint(x0, y0);
if(nowElement !== initialElement) {
mouseEvent('mouseout', x0, y0, {element: initialElement});
}
}, 10);

gd.on('plotly_unhover', function(eventData) {
clearInterval(canceler);
resolve(eventData);
});

setTimeout(function() {
clearInterval(canceler);
resolve(null);
}, 350);
});
}

Plotly.newPlot(gd, _mock)
.then(delay(20))
.then(function() {
gd.on('plotly_hover', function(eventData) {
ptData = eventData.points[0];
});
gd.on('plotly_unhover', function(eventData) {
ptData = eventData.points[0];
});
})
.then(delay(20))
.then(_hover)
.then(delay(20))
.then(function() {
assertEventData(100.75, -102.63, -102.63, 0, 0, {
'marker.symbol': 'circle',
'marker.size': 10,
'marker.color': 'blue',
'marker.line.color': 'black'
});
})
.then(_unhover)
.then(delay(20))
.then(function() {
assertEventData(100.75, -102.63, -102.63, 0, 0, {
'marker.symbol': 'circle',
'marker.size': 10,
'marker.color': 'blue',
'marker.line.color': 'black'
});
})
.then(done, done.fail);
});

it('@gl should display correct face colors', function(done) {
var fig = mesh3dcoloringMock;

Expand Down