-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Array support for trace hoverinfo
#1761
Conversation
- make base hoverinfo `arrayOk: true` - merge array hoverinfo in calcdata while coercing its flags - look for per-point hoverinfo, fallback to trace hoverinfo - add checks string (vs array) hoverinfo values
- which is now handled during defaults, where one-trace graphs have no `'name'` hoverinfo flag by default - this fixes the set `hoverinfo: 'all'` + 1 trace graph hover labels, the name label is now properly displayed.
src/components/fx/calc.js
Outdated
} | ||
|
||
return function(val) { | ||
return Lib.coerce({hoverinfo: val}, {}, attrs, 'hoverinfo', dflt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This here coerces hoverinfo
items with no valid flags to 'all'
if gd.data.length > 1
or all flags but 'name'
on single-trace graphs. For example,
hoveinfo: ['sda', null, 'hello+sup']
// assuming more than one trace,
// => gets coerced to
hoverinfo: ['all', 'all', 'all']
This is debatable I feel. Perhaps, hoverinfo
items with no valid flags should get coerced to 'none'
instead? This would be more in line with e.g. marker.color
where
marker.color: ['red', null, 'blue']
// gets coerced to
marker.color: ['red', '#444' /* i.e. Color.defaultLine */, 'blue']
I'm currently leaning toward the second option, but I have no strong opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think as you have it is probably correct - an invalid entry gets the default behavior, which is 'all'
or all but name. This could come in handy, in as far as the default is really what you want most of the time, for tweaking hoverinfo
of just a few points by providing those in the array and leaving the rest of the array undefined
or null
.
- there's no 'c' array in scattercarpet
- which handles the 1-vs-multi-trace dflt logic for all trace types - call it once (!) during defaults - call it again during fx/calc to coerce arrayOk hoverinfo items
- pie trace array hoverlabel settings were broken before this patch when pie `trace.sort = true` (the default by the way) as this reorders the calcdata items which led to fx/calc filling hoverlabel field in the wrong calcdata items. - to solve the problem, simply don't rely on calcdata for hoverlabel setting in pie traces (as we currently do for gl3d and gl2d).
- To do so, add Fx.castHoverinfo helper and use it during gl3d, gl2d and pie hover where hoverinfo (trace-wide and per-pt array) is extracted from fullData
fontFamily: pt.htf || hoverLabelOpts.font.family, | ||
fontSize: pt.hts || hoverLabelOpts.font.size, | ||
fontColor: pt.htc || hoverLabelOpts.font.color | ||
color: Fx.castHoverOption(trace, pt.i, 'bgcolor') || pt.color, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never realized that pie/calc.js sorts calcdata items meaning the fullData array order isn't preserved. That is, we can't fill in pie calcdata items with hoverlabel field in order. Luckily here, we can simply reuse the gl2d/gl3d machinery.
This commit fixes pie traces with array hoverlabel
values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This commit fixes pie traces with array hoverlabel values.
Good catch! Is this covered in the tests? If it is, it's not obvious to me which one...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... which was previously broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great - I saw that change, just couldn't tell why it tested this behavior - but a failure is a failure, works for me!
* @param {object} layoutOut : full layout object (require _dataLength ref) | ||
* @return {any} : the coerced value | ||
*/ | ||
exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope nobody minds this abstraction. The hoverinfo dflt
is ✨ : special ✨ , so I think it deserves its own coerce
function.
@@ -117,7 +117,7 @@ module.exports = { | |||
textfont: scatterAttrs.textfont, | |||
textposition: scatterAttrs.textposition, | |||
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { | |||
flags: ['a', 'b', 'c', 'text', 'name'] | |||
flags: ['a', 'b', 'text', 'name'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi @rreusser
- e.g. hoverinfo, hoverlabel.bgcolor etc ...
|
||
expect(out[0].x).toEqual([1, 2, 3]); | ||
expect(out[0].y).toEqual([2, 3, 1]); | ||
expect(out[0].hoverinfo).toEqual(['none', 'skip', 'all']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this patch, we had
out[0].hoverinfo
// => ['x', 'y', 'text', 'name', 'none', 'skip', 'all']
@@ -156,6 +156,7 @@ exports.findArrayAttributes = function(trace) { | |||
return stack.join('.'); | |||
} | |||
|
|||
exports.crawl(baseAttributes, callback); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ha nice, that's a lot simpler than I was afraid it might be!
- previously fx attribute arrays with length less than the calcdata trace length were skipped. That is, they did not get filled with their default - this patch here ensures that the defaults values are present in the calcdata so that we don't have to rely on fallbacks in the hover hot code path.
}) | ||
.then(function() { | ||
expect(gd.calcdata[0].map(function(o) { return o.hi; })).toEqual( | ||
['none', 'x+y+z+text', 'x+y+z+text'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before this commit, we had
gd.calcdata[0].map(o => o.hi)
// => // ['none']
and no fallbacks for hoverinfo
in the hover routine. Since, the hoverinfo
default value isn't trivial, I chose add a (another 😑 ) Lib
function fillArray
.
Lib.fillArray
is very similar to Lib.mergeArray
, but instead of looping over the min of cd[i].length
and hoverinfo.length
, it loops over cd[i].length
allowing calcdata items to be filled in with defaults.
src/lib/index.js
Outdated
@@ -350,19 +350,25 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) { | |||
} | |||
}; | |||
|
|||
/** merge data array into calcdata items | |||
lib.mergeArray = function(traceAttr, cd, cdAttr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎎 with a description of the difference between mergeArray
and fillArray
? And perhaps some indication of why one would use merge vs fill? Makes me wonder whether any of our other existing merge usages should be fill...
mergeArray
doesn't need fn
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎎 with a description of the difference between mergeArray and fillArray?
Sure. 👍
Makes me wonder whether any of our other existing merge usages should be fill...
Good point. Many routines in Drawing
have fallbacks in case a required calcdata field is missing. The fallback pattern works well when the fallback value is constant for all cases (e.g. missing items in a marker.color
array get drawn as #444
), but not so well when it is depends on other things (e.g. for hoverinfo
here which depends on gd.data.length
and trace.type
).
That said, replacing all Lib.mergeArray
calls with Lib.fillArray
should not break anything. I could do that if you prefer. It might slow things down a little bit when e.g.
trace.x.length // => 1e5
trace.marker.color.length // => 2
but I'd call that an edge case.
mergeArray doesn't need fn?
Not at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That said, replacing all Lib.mergeArray calls with Lib.fillArray should not break anything. I could do that if you prefer. It might slow things down a little bit when e.g.
I'm not worried about perf - yes, shorter attribute array is an edge case I'm comfortable neglecting. But it does change behavior, even if it's behavior we expect users not to be depending on. I'd leave it as is for the moment, for existing uses, and make an issue about it. We'll probably have to look at each attribute individually to see if we think the change is sufficiently unambiguous as an improvement that it's worth the minor incompatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎎 done in 3c3fda4
src/lib/index.js
Outdated
* | ||
* @param {array} traceAttr : trace attribute | ||
* @param {object} cd : calcdata trace | ||
* @param {string} cdAttr : calcdata key | ||
* @param {function} [fn] : optional function to apply to each array item | ||
*/ | ||
lib.mergeArray = function(traceAttr, cd, cdAttr, fn) { | ||
lib.fillArray = function(traceAttr, cd, cdAttr, fn) { | ||
fn = fn || lib.identity; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🐎 I wonder whether it would be worth avoiding fn
entirely if it's not provided? Either
cd[i][cdAttr] = fn ? fn(traceAttr[i]) : traceAttr[i];
or even
if(fn) {
for(i = 0; i < cd.length; i++) cd[i][cdAttr] = fn(traceAttr[i]);
}
else {
for(i = 0; i < cd.length; i++) cd[i][cdAttr] = traceAttr[i];
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That won't make that much of a difference (at least in Chrome 58)
https://gist.github.com/etpinard/4a5c7983a619efb4c98cb927529b9af6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome, thanks for checking.
tagging this as |
No more comments from me - looks great! 💃 |
... scattergeo, choropleth, scatterternary and scattermapbox - these are all the trace type that rely on `extraText` built in their _module.hoverPoints method - this commit should've been part of PR #1761
resolves #1749
TODO: