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

Fix geo plot hover interaction for Firefox #5607

Merged

Conversation

LucaVazz
Copy link
Contributor

Related to #5525.

This PR fixes the hover interaction for geo plots (i.e. choropleth) in Firefox and extends the test suite for choropleths to also test for translate3d transforms.

Background:

@LucaVazz
Copy link
Contributor Author

LucaVazz commented Apr 23, 2021

Demo of the fix:
before (v1.58.4): https://codepen.io/lucavazz/pen/mdOpgMY
after fix: https://codepen.io/lucavazz/pen/KKabQpL

Tested with

  • Firefox 88.0 on Windows
  • Firefox Developer Edition 89.0b3 on Windows
  • Chrome 90.0.4430.85 on Windows
  • Chrome Dev 92.0.4484.7 on Windows
  • Edge 90.0.818.46 on Windows
  • Safari 14.0.1 on Mac
  • Safari on iPad 14.4.2
  • Firefox 88.1.1 on Android
  • Firefox Klar 8.15.1 on Android
  • Firefox (Fennec) 87.0.0-rc.1 on Android
  • Firefox 88.0.0-beta.6 on Android
  • Chrome 90.0.4430.82 on Android
  • Chrome Dev 92.0.4484.6 on Android

@LucaVazz LucaVazz marked this pull request as ready for review April 23, 2021 10:03
@archmoj archmoj added status: reviewable bug something broken community community contribution labels Apr 23, 2021
@archmoj
Copy link
Contributor

archmoj commented Apr 29, 2021

Thanks very much for the information, investigation and PR.
A couple of questions:

  1. Did you investigate why the proposed fix does not work (and appear to break hover) when using Firefox 85 and lower?
  2. d3.mouse is used in few other traces and components namely parcats, parcoords and sliders. Could you please investigate if a similar issue existed there? If so, we need to make a centralize function e.g. in src/lib to handle the rest.

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 3, 2021

Hey, thanks for having a look!

1.

Indeed, I can sadly reproduce it not working for Firefox v <= 85 or the current Firefox ESR (78.10.0esr).
This seems to be due to this bug that was fixed for Firefox 86: https://bugzilla.mozilla.org/show_bug.cgi?id=1684973
The description matches what I observed in testing: Firefox <= 85 sets offsetX & offsetY relative to the hovered SVG element, while >= 86 sets it relative to the SVG root. Chrome and Safari behave like Firefox >= 86.

What would be the preferred way to proceed? We could test the user agent string and fallback to d3.mouse for Firefox <= 85 to have at least non-translated maps working for it.
Alternatively we could use layerX & layerY, which are even more non-standard but seem to work fine from a quick test in Firefox ESR. (see https://developer.mozilla.org/en-US/docs/Web/API/UIEvent/layerX )
What's Plotly's general stance on adding code based on the user agent string? Personally I try to avoid it, but it seems like its our only option if we want to get Firefox <= 85 to work with it.

2.

I'll have a look how well the change works with parcats, parcoords and sliders in a bit

@archmoj
Copy link
Contributor

archmoj commented May 3, 2021

Given the fact that FireFox >=86 is pretty new, I think we still need to handle versions <=85 as well.
Here are pointers to few locations in plotly.js code that userAgent is used to detect certain fallbacks.

plotly.js/src/lib/index.js

Lines 708 to 716 in a5a5de9

var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/;
lib.isSafari = function() {
return IS_SAFARI_REGEX.test(window.navigator.userAgent);
};
var IS_IOS_REGEX = /iPad|iPhone|iPod/;
lib.isIOS = function() {
return IS_IOS_REGEX.test(window.navigator.userAgent);
};

function getUserAgent() {
// similar to https://github.com/juliangruber/is-mobile/blob/91ca39ccdd4cfc5edfb5391e2515b923a730fbea/index.js#L14-L17
var ua;
if(typeof navigator !== 'undefined') {
ua = navigator.userAgent;
}
if(
ua &&
ua.headers &&
typeof ua.headers['user-agent'] === 'string'
) {
ua = ua.headers['user-agent'];
}
return ua;
}

@archmoj archmoj requested a review from alexcjohnson May 3, 2021 13:50
src/lib/index.js Outdated Show resolved Hide resolved
@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 3, 2021

Added functions for both steps into lib - at first just in the index.js, since I'd argue its not complicated enough to warrant its own file.

Updated the pen and retested including Firefox ESR (78.10.0esr) and works fine for all now.

However, I can't really make sense out of the test failures, when trying it locally it seems all to work.

@archmoj
Copy link
Contributor

archmoj commented May 3, 2021

However, I can't really make sense out of the test failures, when trying it locally it seems all to work.

Some tests are flaky. I re-run the CircleCI and they should pass.

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 3, 2021

Some tests are flaky. I re-run the CircleCI and they should pass.

Thanks, that seems to have helped with the jasmine tests.

What about the other tests / the build? Is this bad character also due to a flaky step...?
image

@archmoj
Copy link
Contributor

archmoj commented May 3, 2021

Some tests are flaky. I re-run the CircleCI and they should pass.

Thanks, that seems to have helped with the jasmine tests.

What about the other tests / the build? Is this bad character also due to a flaky step...?
image

The bad character problem is really NOT related to your PR. So please do not worry about that.
In fact, the problem appears to be related to non-ASCII characters in d3-v3 geo or a memory issue with node or browserify.
Once we could merge #5112 and cleanup geo code from our d3 fork it should disappear 🤞

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 3, 2021

Regarding the other trace types:

  • parcats: The fixed function enables the hover on the bands to work properly in a current Firefox version. In the ESR it fails / shows the hovertext in the top left corner still.
    unfixed example: https://codepen.io/lucavazz/pen/NWdQeaV
  • parcoords: For this trace I'm unable to produce any hover interaction. Could you please provide an example for it?

I'll have a closer look at the parcats problem in Firefox ESR and the sliders in the coming days.
Would you prefer to land it all together in one PR?

@archmoj
Copy link
Contributor

archmoj commented May 3, 2021

Regarding the other trace types:

  • parcats: The fixed function enables the hover on the bands to work properly in a current Firefox version. In the ESR it fails / shows the hovertext in the top left corner still.
    unfixed example: https://codepen.io/lucavazz/pen/NWdQeaV
  • parcoords: For this trace I'm unable to produce any hover interaction. Could you please provide an example for it?

I'll have a closer look at the parcats problem in Firefox ESR and the sliders in the coming days.
Would you prefer to land it all together in one PR?

parcoords hover is incomplete. But I noticed a drag issue when trying to move the pink selector on the bottom plots on [this codepen].
If one makes a d3Mouse using your patch and use it instead of d3.mouse calls, I think that should fix that issue as well. Right?

@archmoj
Copy link
Contributor

archmoj commented May 3, 2021

On another note. It appears there are similar bugs related to geo handling zoom here:

mouse0 = d3.mouse(this);

(and if you search for d3.mouse) which again could possibly be addressed using your fix around d3.mouse.

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 5, 2021

  • parcats: Works reliably in current Firefox (and all other browsers), unfortunately not in Firefox ESR. After a day of investigating I didn't find any indication what is causing this further problem in Firefox ESR.
    Example before fix: https://codepen.io/lucavazz/pen/NWdQeaV
    Example after fix: https://codepen.io/lucavazz/pen/qBrWwZN
  • geo zoom: Unfortunately replacing all d3.mouse calls in the zoom handlers of geo does not fix the problem in Firefox.
    As far as I can tell the code paths using the mouse position behave correctly when replacing d3.mouse with Lib.getPositionFromD3Event. However I don't understand the zoom logic enough to debug the problem.
  • parcoords and sliders: I wan't able to reliably reproduce a problem in Firefox with them. Furthermore, they are out of scope of the original problem.

Unfortunately I'm already over-stretching the time I've allocated for this issue. So I'd appreciate to focus this PR on the geo hover, take on the parcats hover as best effort and record the problems with parcoords and/or sliders into new tickets to be tackled in the future.

@LucaVazz LucaVazz requested a review from archmoj May 5, 2021 14:55
@archmoj
Copy link
Contributor

archmoj commented May 5, 2021

@LucaVazz thanks very much for the hard work and great contribution.
There is now a conflict in src/lib/index.js with upstream/master.
Could you please resolve it?

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 6, 2021

Thank you as well @archmoj for your great help :)

I've merged master into the fix branch, hopefully its good to go now.

@LucaVazz
Copy link
Contributor Author

@archmoj / @alexcjohnson Would this change by the way be a candidate for 2.0.0 or would it likely land in a following release?

@archmoj
Copy link
Contributor

archmoj commented May 12, 2021

@archmoj / @alexcjohnson Would this change by the way be a candidate for 2.0.0 or would it likely land in a following release?

That's a bug we could fix either in 2.0.0 or 2.0.1.
After ff60f21 commit a number of tests start failing.
@LucaVazz would you please fix the problem?
Also it would be nice if you could merge upstream/master one more time into this branch to have more robust tests running on the CI.
Thanks!

@archmoj
Copy link
Contributor

archmoj commented May 12, 2021

The failing tests are in parcats and legend.

npm run test-jasmine parcats legend 

and using FireFox

npm run test-jasmine parcats legend -- --FF

both should pass.

@LucaVazz
Copy link
Contributor Author

LucaVazz commented May 21, 2021

@archmoj I've merged master and reverted the parcats change.
Unfortunately I wasn't able to resolve the failures for parcats, so I'd vote for keeping this PR focused on the fix for geo and to leave parcats in Firefox as-is for now - there seems to be another constraint that I'm unable to fix atm.

@archmoj
Copy link
Contributor

archmoj commented May 21, 2021

@archmoj I've merged master and reverted the parcats change.
Unfortunately I wasn't able to resolve the failures for parcats, so I'd vote for keeping this PR focused on the fix for geo and to leave parcats in Firefox as-is for now - there seems to be another constraint that I'm unable to fix atm.

@LucaVazz, OK thanks very much.
I'll have a look :)

@@ -715,6 +715,18 @@ lib.isIOS = function() {
return IS_IOS_REGEX.test(window.navigator.userAgent);
};

var FIREFOX_VERSION_REGEX = /Firefox\/(\d+)\.\d+/;
lib.getFirefoxVersion = function() {
var match = FIREFOX_VERSION_REGEX.exec(window.navigator.userAgent);
Copy link
Contributor

Choose a reason for hiding this comment

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

It appears regex.exec was not used in any other places of our code.
Wondering is it is possible to convert this regex.exec to a regex.test?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately not, as regex.test does only return true or false, but we need to evaluate the exact version of Firefox in the user agent string.
However, exec has the same compatibility as test: see mdn

src/lib/index.js Outdated Show resolved Hide resolved
src/lib/index.js Outdated Show resolved Hide resolved
@LucaVazz LucaVazz requested a review from archmoj May 26, 2021 12:47
* @returns An array with two numbers, representing the x and y coordinates of the mouse pointer
* at the event relative to the targeted node.
*/
lib.getPositionFromD3Event = function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocking suggestion to avoid if statement inside the function:

lib.getPositionFromD3Event = isProblematicFirefox ? 
    function() {
        // layerX and layerY are non-standard, so we only fallback to them when we have to:
        return [
            d3.event.layerX,
            d3.event.layerY
        ];
    } : function() {
        return [
            d3.event.offsetX,
            d3.event.offsetY
        ];
    };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Personally I find ternary operators way harder to read - but I'd say that's just personal preference in the end :D

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the point of @archmoj's comment was not so much about whether to use if or a ternary operator, but pulling the conditional out of the function and into the initial script evaluation, for better performance. I agree that ternaries can be confusing when used on such large expressions, but we can do something like:

if (isProblematicFirefox) {
    lib.getPositionFromD3Event = function() {...}
} else {
    lib.getPositionFromD3Event = function() {...}
}

@archmoj
Copy link
Contributor

archmoj commented May 26, 2021

LGTM.
@alexcjohnson could you please provide a review for this PR as well?

Copy link
Collaborator

@alexcjohnson alexcjohnson left a comment

Choose a reason for hiding this comment

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

💃 LGTM, with or without the one comment about inverting the function/conditional.

@archmoj archmoj merged commit d36b491 into plotly:master Jun 1, 2021
@LucaVazz
Copy link
Contributor Author

LucaVazz commented Jun 3, 2021

Thanks for the review @alexcjohnson and thanks again for all the help in getting it integrated properly @archmoj

@LucaVazz LucaVazz deleted the fix-choropleth-interactions-for-firefox branch June 3, 2021 14:52
@archmoj
Copy link
Contributor

archmoj commented Jun 3, 2021

Thanks @LucaVazz for the contribution.
Could you please provide an update on #5525 or if we could close it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken community community contribution
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants