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

Pie aggregation and event cleanup #2117

Merged
merged 8 commits into from
Oct 26, 2017
Merged

Pie aggregation and event cleanup #2117

merged 8 commits into from
Oct 26, 2017

Conversation

alexcjohnson
Copy link
Collaborator

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. The pointNumber 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 kept i (replaced by pointNumber and like it, only present for non-aggregated slices) and v (replaced by value) 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 with data and fullData as in other event types, while ensuring it's always available.

@@ -25,3 +25,16 @@ exports.formatPieValue = function formatPieValue(v, separators) {
}
return Lib.numSeparate(vRounded, separators);
};

exports.getFirstFilled = function getFirstFilled(array, indices) {
Copy link
Collaborator Author

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?

Copy link
Contributor

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);
Copy link
Collaborator Author

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...

Copy link
Contributor

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 👍

Copy link
Collaborator Author

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();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Man, I totally forgot that I had written all this crap for 3D pies, I thought #1152 was all of it - but now between this commit and 679ed9c I think it's finally all gone.

Copy link
Contributor

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! 🎉

@@ -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) {
Copy link
Collaborator Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like sankey does Fx.hover here and here, but if you feel adventurous, sure, go ahead and patch sankey/plot.js to 🔪 this block.

Copy link
Collaborator Author

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.

Copy link
Contributor

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;
Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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();
Copy link
Collaborator Author

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.

Copy link
Contributor

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();
Copy link
Collaborator Author

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.

@alexcjohnson alexcjohnson added status: reviewable bug something broken feature something new labels Oct 25, 2017
}
}

function handleClick() {
gd._hoverdata = [pt];
gd._hoverdata.trace = cd0.trace;
Copy link
Collaborator Author

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 🙈

Copy link
Contributor

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');
Copy link
Collaborator Author

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 🤔

Copy link
Contributor

@etpinard etpinard left a 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) {
Copy link
Contributor

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?

Copy link
Collaborator Author

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();
Copy link
Contributor

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! 🎉

}
};

exports.appendArrayPointValues = function(pointData, trace, pointNumbers) {
Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

... and please add jsDOC ⤴️

Copy link
Collaborator Author

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 = {
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Collaborator Author

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?

Copy link
Contributor

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?
Copy link
Contributor

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 ?

Copy link
Collaborator Author

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();
Copy link
Contributor

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) {
Copy link
Contributor

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);
Copy link
Contributor

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) {
Copy link
Contributor

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 👍

@etpinard
Copy link
Contributor

I should reference #1963 in this PR as now pie is locked into a hover pattern outside the realm of Fx.hover. Maybe a better solution to #1963 is persistent hover point list as discussed in #1848

@etpinard etpinard added this to the v1.32.0 milestone Oct 26, 2017
@etpinard
Copy link
Contributor

Lovely feature and solid clean up: looking great 💃

Make sure to close #2073, #1456 and #2048

@alexcjohnson alexcjohnson merged commit 77d0858 into master Oct 26, 2017
@alexcjohnson alexcjohnson deleted the pie-agg branch October 26, 2017 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken feature something new
Projects
None yet
Development

Successfully merging this pull request may close these issues.

onClick for pies slices doesn't return any trace info
2 participants