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

Treemap new trace type #4185

Merged
merged 63 commits into from
Sep 25, 2019
Merged

Treemap new trace type #4185

merged 63 commits into from
Sep 25, 2019

Conversation

archmoj
Copy link
Contributor

@archmoj archmoj commented Sep 11, 2019

Fix #1038.
This PR adds treemap traces to plotly.js library.
This new trace inherits various logic from the plotly.js code base and is quite similar to its brother sunburst.
Since colorscale and displaying current path & percentages were within the requirements for our treemap, those features are also added to sunburst. Also a count attribute is added to both traces which allows counting the numbers of leaves, branches or both when values array is not provided.

One main extra feature of treemap is the barpath i.e. for displaying the current path of the visible portion of the hierarchical map. It may also be useful for zooming out of the graph.

Codepens in addition to new JSON mocks added which could be displayed here!

A
B
C
D

Please also note that commit 52ded31 touches on refactoring base_plot files of traces namely pie, funnelarea, indicator and 'sunburst`.

List of TODOs:

  • texttemplate additions appear to be incomplete for percentages
  • hovertemplate may require improvement (or bypassing) when hovered on pathbar elements.

@plotly/plotly_js
@nicolaskruchten
@Mahdis-z

@etpinard etpinard added this to the v1.50.0 milestone Sep 11, 2019
now no need to include empty string to hide last label on the barpath
src/traces/treemap/plot.js Outdated Show resolved Hide resolved
].join(' ')
},

opacity: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we reuse sunburstAttrs.marker.opacity here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sunburst does not have a marker.opacity attribute. But we can reuse marker.line and marker.colors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 6dad958.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, thanks for the info.

Now I see that marker.opacity can multiply branch.opacity:

opacity = trace.marker.opacity * Math.pow(trace.branch.opacity, pt.height);

That's interesting, but wouldn't having marker.opacity (when set) override branch.opacity be more user friendly in your mind? In particular, if we allow marker.opacity to be arrayOk, this could allow users to set the opacity of a sector pretty easily.

What do you think @archmoj ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A very good call!
It helped me rethink a couple of things in 0012fa1.
Now the API is more similar to sunburst where both have a leaf.opacity option.

Copy link
Collaborator

Choose a reason for hiding this comment

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

marker.depthfade?

It also occurs to me that opacity might not actually be quite the effect we want - perhaps it would be better to use the opaque color that would result from blending the marker color with the background color in the same proportion? For the bottom-most layer the effect is equivalent, but anything stacked above it currently effectively is more opaque (if the two colors are the same) or blended to a different color (if using a colorscale or explicit colors). Both of these I think are problematic:

  • if the color has meaning, a faded version of that color is probably still easy to mentally connect with the original, keeping its interpretation the same, but a version blended with a different color could shift the implied meaning of that color.
  • making stacked layers more opaque kind of defeats the purpose - it means that the final effective opacity drops off too slowly at first, then more quickly toward the back of the stack.

Anyway if we extend the analogy I mentioned this morning of "looking at layers of hills in the distance" - those hills are not transparent, they are faded by the air in front of them - which is exactly the same effect as blending with the background color.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@alexcjohnson Thanks very much for the feedback. Is there a place in plotly.js code that a similar colour blending is applied?

Copy link
Collaborator

Choose a reason for hiding this comment

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

look for color.combine

color.combine = function(front, back) {
var fc = tinycolor(front).toRgb();
if(fc.a === 1) return tinycolor(front).toRgbString();
var bc = tinycolor(back || background).toRgb();
var bcflat = bc.a === 1 ? bc : {
r: 255 * (1 - bc.a) + bc.r * bc.a,
g: 255 * (1 - bc.a) + bc.g * bc.a,
b: 255 * (1 - bc.a) + bc.b * bc.a
};
var fcflat = {
r: bcflat.r * (1 - fc.a) + fc.r * fc.a,
g: bcflat.g * (1 - fc.a) + fc.g * fc.a,
b: bcflat.b * (1 - fc.a) + fc.b * fc.a
};
return tinycolor(fcflat).toRgbString();
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome!

Copy link
Contributor

Choose a reason for hiding this comment

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

New attempt in 4c7a092 - which I'm a fan of 👌

.catch(failTest)
.then(done);
});
});
Copy link
Contributor

@etpinard etpinard Sep 12, 2019

Choose a reason for hiding this comment

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

TODO:

Copy link
Contributor

Choose a reason for hiding this comment

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

still TODO.

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 the texttemplate part is now done.

- do not display the edges of the empty root unless hovered
- replace empty string with space to have better transitions
- add restyle tests for treemap opacities - fix style.js
- add texttemplate jasmine test for treemap
- add treemap tween interaction tests
@@ -507,7 +507,7 @@ describe('Test sunburst hover:', function() {
curveNumber: 0,
pointNumber: 0,
label: 'Eve',
parent: ''
parent: '"root"'
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't look right. parents[0] is '' in the input trace, so I think we should honour that in the event data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK if we return an empty string here?

return str === '' ? '"root"' : str;

Copy link
Contributor

Choose a reason for hiding this comment

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

What are the side-effects?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simply getting blank string (nothing) referring to the dummy root label in hover; which I think is fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 8416883.

};

var getVal = function(d) {
if(d.hasOwnProperty('hierarchy')) return d.hierarchy.value;
Copy link
Contributor

Choose a reason for hiding this comment

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

Where does d get a hierarchy key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here:

entry._parent = (numAncestors ?
ancestorNodes[numAncestors - 1] :
hierarchy // parent of root is itself
).data;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 8416883.

Registry.call('animate', gd, frame, animOpts);
});
}
if(trace.type === 'treemap' && helpers.isHeader(pt, trace)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this if block. Header nodes don't appear to call formatSliceLabel from:

if(isHeader && noRoomForHeader) return;

Copy link
Contributor

Choose a reason for hiding this comment

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

... with the formatSliceLabel below:

if(isHeader && noRoomForHeader) return;
var tx = formatSliceLabel(pt, entry, trace, cd, fullLayout) || ' ';

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. Moved to treemap/draw_descendants.js in 8416883.

@etpinard
Copy link
Contributor

The diff in sunburst/plot.js is pretty difficult to read, so I'll make comments from the file tree off 217c0f4


Why can we just use helpers.getPtId here:

var getLabel = function(d) {
var id = d.id;
if(d.data) {
id = d.data.id;
if(d.data.data) {
id = d.data.data.id;
}
}
if(!id) return '';
for(var q = 0; q < cd.length; q++) {
if(cd[q].label === id) {
return id;
}
}
return '';
};

juggling between d.data.id and d.data.data.id looks pretty dangerous to my eyes.


We should try to find a better condition than d.hasOwnProperty('hierarchy') here:

if(d.hasOwnProperty('hierarchy')) return d.hierarchy.value;

We never set hierarchy on sliceData items, so this is very confusing.


It's probably better to check that the denominator is greater than 0 before computing the fraction here:

var calcPercent = function() {
var result = (trace.branchvalues ? cdi.v : cdi.value) / getVal(ref);
return isFinite(result) ? result : 1;
};

instead of checking if the fraction is finite.


I don't understand some of the differences between the logic in formatSliceLabel and in sunburst/fx.js. For example,

obj.entry = helpers.getLabelString(getLabel(ref));

vs

hoverPt.entry = pt.entry = helpers.getLabelString(ref1.data.data.label);

@archmoj can you explain why the "text" part appears more complicated than the "hover" part?

- move treemap header checks from sunburst plot to treemap/draw_ancestors
- cleanup dirty _parents and hierarchy links for pt
- display blank label as blank
@archmoj
Copy link
Contributor Author

archmoj commented Sep 24, 2019

@etpinard thanks very much for the review.
Reworked in
8416883.
Now the hover and plot code look pretty much the same.

@etpinard
Copy link
Contributor

Reworked in
8416883.
Now the hover and plot code look pretty much the same.

Thanks @archmoj - this looks much (much) better 😄

I have a few questions about this block:

var parent;
var parentLabel;
var parentValue;
if(isEntry) {
parent = pt.data.parent;
parentLabel = parent ? parent.data.label : helpers.getPtLabel(hierarchy);
parentValue = parent ? helpers.getVal(parent.data) : helpers.getVal(hierarchy);
} else {
parent = pt.parent;
parentLabel = helpers.getPtLabel(parent);
parentValue = helpers.getVal(parent);
}

(and it's equivalent in formatSliceLabel)

  • what's the difference between pt.data.parent and pt.parent?
  • why does helpers.getVal(parent); not return the same result as helpers.getVal(parent.data)?

@archmoj
Copy link
Contributor Author

archmoj commented Sep 24, 2019

  • what's the difference between pt.data.parent and pt.parent?

@etpinard
E.g. with level being set when hovering on or plotting the entry node, we need to know the parent of the entry (to compute percentages from). But that is not available on the entry itself where pt.parent is equal to null. But the parent of the entry is accessible via pt.data.parent.

@etpinard
Copy link
Contributor

But that is not available on the entry itself where pt.parent is equal to null. But the parent of the entry is accessible via pt.data.parent.

Ok, this sounds right. Thanks!

I think you could also use pt.data.data.parent. If so, this would be even clearer as pt.data.data always correspond to gd.calcdata[i][j] item. Elsewhere in the sunburst code, you should be able to find plenty of var cdi = pt.data.data; statements.

- add functions to get parents info above entry node
- use computed d.value everywhere
- add more jasmine tests to test percentgaes and current path at root and desired level
return x + ',' + y;
}

function noNaN(path) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@etpinard it looks we don't need this extra check anymore.

@etpinard
Copy link
Contributor

Time to merge this thing. Amazing work @archmoj - sorry for being a little picky there that last two days.

💃 💃 💃 💃 💃 💃


To sum up, this PR:

  • adds a new treemap trace type (Treemap trace #1038) with:
    • click transition support
    • on-hover effect
    • 6 different packing algorithms
    • pathbar (that can be optionally removed)
    • several padding attributes
    • support for per-sector values, branchvalues, colorscale, hover/text info/template, colorways
    • a level-to-level sector opacity gradient
    • ... all this while generalising and reusing much of the sunburst logic
  • adds helpers Plots.plotBasePlot and Plots.cleanBasePlot
  • adds attribute count to sunburst traces Display counted number of leaves/branches on sunburst/treemap using textinfo, texttemplate, hoverinfo and hovertemplate #4174
  • adds colorscale support to sunburst traces
  • adds a bunch more hoverinfo and textinfo flags to sunburst traces

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

Successfully merging this pull request may close these issues.

Treemap trace
4 participants