-
-
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
Pie aggregation and event cleanup #2117
Conversation
apparently I got a lot farther than just defining attributes (deleted in #1152)
@@ -25,3 +25,16 @@ exports.formatPieValue = function formatPieValue(v, separators) { | |||
} | |||
return Lib.numSeparate(vRounded, separators); | |||
}; | |||
|
|||
exports.getFirstFilled = function getFirstFilled(array, indices) { |
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.
For arrayOk
attributes, as well as slice color, I chose to take the first non-empty item corresponding to each label. I suppose in principle we could refine that further to the first valid item for each label?
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.
Picking the first non-empty item is fine 👍
hoverinfo = Fx.castHoverinfo({ | ||
hoverinfo: [helpers.castOption(hoverinfo, pt.pts)], | ||
_module: trace._module | ||
}, fullLayout2, 0); |
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.
@etpinard thoughts? I didn't see a reasonable way to build this into Fx.castHoverInfo
since an array index already has a meaning there...
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.
Maybe something like:
Fx.castHoverinfo = function(trace, fullLayout, pt) {
if(pt.pointNumber) {
// as currently
} else if(pt.pointNumbers) {
// new pie logic
}
}
// instead of
Fx.castHoverinfo = function(trace, fullLayout, ptNumber) {
// ...
}
but your hack (with that comment) is fine 👍
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 I should leave it as is for now, but if we unify this behavior with histograms (see #1984) then it may be worth making this change.
return d + ' part'; | ||
}); | ||
pieParts.exit().remove(); | ||
pieParts.order(); |
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.
In almost 2 years of open-source, no one has ever requested 3D pies. Good riddance! 🎉
src/components/fx/hover.js
Outdated
@@ -155,7 +155,7 @@ exports.loneHover = function loneHover(hoverItem, opts) { | |||
|
|||
// The actual implementation is here: | |||
function _hover(gd, evt, subplot, noHoverEvent) { | |||
if((subplot === 'pie' || subplot === 'sankey') && !noHoverEvent) { | |||
if(subplot === 'sankey' && !noHoverEvent) { |
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 couldn't see any purpose to having pie call Fx.hover
other than throttling - which really shouldn't be an issue for pie
, so I removed it from this special handling. Am I missing anything? Should we do the same for sankey
and 🔪 this block entirely?
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.
simplified sankey -> e08e37a
Also: I notice that the way we had it was only throttling event emission, not the actual item selection which is normally the slow part. Again, throttling shouldn't be an issue for either of these trace types, this is just more confirmation of that.
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.
e08e37a is very nice. 👌
}); | ||
|
||
it('should not emit a hover if you\'re dragging', function() { | ||
gd._dragging = true; |
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.
It occurred to me when fixing this bit of logic, it would perhaps be better to check event.buttons
instead? Seems both cleaner and more powerful, for example it would prevent hover if you drag over a plot from somewhere else on the page. So in principle perhaps all our hover handlers should contain this check?
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 will need a bunch more testing and ideally rolling out to all subplots at once, so I'll defer it -> #2120
it('should not emit a hover if hover is disabled', function() { | ||
Plotly.relayout(gd, 'hovermode', false); | ||
mouseEvent('mouseover', pointPos[0], pointPos[1]); | ||
expect(futureData).toBeUndefined(); |
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 is also different from before, but consistent with other subplot types, I believe.
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.
Yes this is how it should be.
For the record, to get hover event data w/o visible hover labels, one should set hoverinfo: 'none'
in all the traces of interest.
|
||
it('should not emit an unhover if you didn\'t first hover', function() { | ||
mouseEvent('mouseout', pointPos[0], pointPos[1]); | ||
expect(futureData).toBeUndefined(); |
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.
kind of a weird one... but lets say you dragged over the pie from another subplot, so there was no hover event - you shouldn't get an unhover event even if you mouse up and then mouse out of that slice.
} | ||
} | ||
|
||
function handleClick() { | ||
gd._hoverdata = [pt]; | ||
gd._hoverdata.trace = cd0.trace; |
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.
We probably didn't mean to attach trace
as a property of the whole array 🙈
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.
Wow. Brutal. Good catch ⚾
@@ -400,43 +407,24 @@ describe('Test event property of interactions on a pie plot:', function() { | |||
click(pointPos[0], pointPos[1]); | |||
expect(futureData.points.length).toEqual(1); | |||
|
|||
var trace = futureData.points.trace; | |||
expect(typeof trace).toEqual(typeof {}, 'points.trace'); |
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.
We probably didn't mean to attach
trace
as a property of the whole array 🙈
And yet we had a test to verify that that's exactly what we did 🤔
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.
Solid work. I made two blocking comments ⛔
I noticed a few 🌴 opportunities, but since pie
hover will always be a special case, I'm not sure it's worth the effort. Your call. I'm happy to see the castOption
pattern getting more attention though.
var labels = coerce('labels'); | ||
if(!Array.isArray(labels)) { | ||
if(!Array.isArray(vals) || !vals.length) { |
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.
Blocking: Would you mind locking this down in a jasmine test?
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.
sure -> ee1f8c4
It was done in the image test I added, but nice to have it in jasmine too.
return d + ' part'; | ||
}); | ||
pieParts.exit().remove(); | ||
pieParts.order(); |
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.
In almost 2 years of open-source, no one has ever requested 3D pies. Good riddance! 🎉
src/components/fx/helpers.js
Outdated
} | ||
}; | ||
|
||
exports.appendArrayPointValues = function(pointData, trace, pointNumbers) { |
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.
Maybe appendArrayMultiPointValue
or appendArrayMultiPointValues
would more typo-proof.
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.
... and please add jsDOC
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.
good call on appendArrayMultiPointValues
-> with docs in
4ba0249
} | ||
} | ||
}; | ||
|
||
var pointKeyMap = { |
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.
✨ at some point we might want to move this to the attribute declarations.
return pointKeyMap[astr] || astr; | ||
} | ||
|
||
function getPointData(val, pointNumber) { |
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 thing is very similar to Lib.castOption
. I wonder if we could combine them? Note that getPointData
should not fallback to trace-wide properties.
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.
For the multipoint case it's nice to keep the nestedProperty
call out of this function, as that would slow it down dramatically. Between that and the fact that, as you mention, this one does not return trace-wide properties (it would return undefined
but in fact it never gets there as it's iterating over arrayAttrs
) these functions seem fairly distinct - though perhaps Lib.castOption
could call this 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.
Right, it might be nice to move getPointData
to Lib
at some point, but let's keep this as is for now.
expect(point.id).toEqual(['maggie']); | ||
expect(point.customdata).toEqual([{9: 10}]); | ||
|
||
// for backward compat - i/v to be removed at some point? |
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.
Blocking: yeah, we should remove that in v2
- would you mind adding it to #420 ?
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.
commented #420 (comment)
it('should not emit a hover if hover is disabled', function() { | ||
Plotly.relayout(gd, 'hovermode', false); | ||
mouseEvent('mouseover', pointPos[0], pointPos[1]); | ||
expect(futureData).toBeUndefined(); |
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.
Yes this is how it should be.
For the record, to get hover event data w/o visible hover labels, one should set hoverinfo: 'none'
in all the traces of interest.
}); | ||
|
||
afterEach(destroyGraphDiv); | ||
|
||
function checkEventData(data) { |
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.
A long-overdue test abstraction. Thanks!
hoverinfo = Fx.castHoverinfo({ | ||
hoverinfo: [helpers.castOption(hoverinfo, pt.pts)], | ||
_module: trace._module | ||
}, fullLayout2, 0); |
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.
Maybe something like:
Fx.castHoverinfo = function(trace, fullLayout, pt) {
if(pt.pointNumber) {
// as currently
} else if(pt.pointNumbers) {
// new pie logic
}
}
// instead of
Fx.castHoverinfo = function(trace, fullLayout, ptNumber) {
// ...
}
but your hack (with that comment) is fine 👍
@@ -25,3 +25,16 @@ exports.formatPieValue = function formatPieValue(v, separators) { | |||
} | |||
return Lib.numSeparate(vRounded, separators); | |||
}; | |||
|
|||
exports.getFirstFilled = function getFirstFilled(array, indices) { |
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.
Picking the first non-empty item is fine 👍
Implements #2073 and fixes #1456
Previously if we encountered duplicate labels we would just drop all but the first one. This PR just does the natural thing and sums all the values we find for each label. Additionally, you can now make a pie chart with no
values
array at all, in which case we just count occurrences of each label.To support this, event data has been augmented with a
pointNumbers
field, which is an array of indices of points in the input data represented by the slice. ThepointNumber
field remains, but only if the slice came from a single input point. Other fields have been renamed to be more consistent with events on other trace types, but for backward compatibility I kepti
(replaced bypointNumber
and like it, only present for non-aggregated slices) andv
(replaced byvalue
) but they are destined for removal at some point.Since the
trace
field to link to the trace for this pie was both nonstandard and mostly broken (#1456) I replaced it withdata
andfullData
as in other event types, while ensuring it's always available.