Skip to content

Treemap new trace type #4185

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

Merged
merged 63 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
ec6a9de
bar textpad const
archmoj Aug 28, 2019
52ded31
refactor base_plot files
archmoj Aug 29, 2019
320f81a
changes to sunburst attributes - add percentages - add colorscale and…
archmoj Aug 29, 2019
06d93ae
prep to add treemap to the lib
archmoj Aug 29, 2019
6cc8590
treemap mocks
archmoj Aug 28, 2019
6a595b6
treemap jasmine tests
archmoj Aug 28, 2019
671e864
treemap code
archmoj Sep 11, 2019
648167d
correct current path when there is no parent
archmoj Sep 11, 2019
6dad958
various attribute revisions
archmoj Sep 13, 2019
cceee49
drop stroke-linejoin
archmoj Sep 13, 2019
f3d38b8
revisit pathbar draw as well as transition
archmoj Sep 13, 2019
088b0e0
revert sunburst hoverinfo defaults
archmoj Sep 13, 2019
e23a577
move attachFxHandlers to fx.js
archmoj Sep 13, 2019
eb54f24
pass hover and transition options to fx
archmoj Sep 13, 2019
0e3185f
expand setSliceCursor logic to handle treemap and pathbar cases
archmoj Sep 13, 2019
fdd69e7
drop stroke-linejoin again
archmoj Sep 13, 2019
a7b6074
use constant _hoverd.marker.line.width - improve a treemap mock and u…
archmoj Sep 14, 2019
4c85360
correct transition of text inside pathbar namely when side is bottom
archmoj Sep 14, 2019
65c0e02
display percent of root on leaf when it is an entry
archmoj Sep 14, 2019
ca4b385
some refRect clean up and rename interpolator functions in sunburst a…
archmoj Sep 14, 2019
0012fa1
make the opacity logic slightly more user-friendly by applying leaf.o…
archmoj Sep 14, 2019
544977e
thanks to a typo leading to apply a better easing option suitable for…
archmoj Sep 14, 2019
13e0449
dont use sunburst naming in treemap
archmoj Sep 14, 2019
57dec38
move getTransform function to Lib and use it in bar and treemap
archmoj Sep 14, 2019
65f0213
correct exit case of zoom out when maxdepth is set - avoid overlaps a…
archmoj Sep 15, 2019
ad24987
when exiting with maxdepth try to use parent if that is visible on th…
archmoj Sep 16, 2019
36fb510
early exit out of maxdepth elements for better transitions and avoid …
archmoj Sep 16, 2019
284bf6d
add constant zero fillet - could expose that in next version
archmoj Sep 16, 2019
4e2c328
add default tests of treemap and sunburst
archmoj Sep 16, 2019
0cf5bf6
remove the case that never happens now
archmoj Sep 16, 2019
2405ba4
revisit pathbar divider default
archmoj Sep 16, 2019
19c7a61
[wip] add test for sunburst tween edge cases
etpinard Sep 17, 2019
4e3b7f3
address various treemap transition case e.g. maxdepth, tweening and p…
archmoj Sep 19, 2019
e5181ac
texttemplate and other fixes for treemap and sunburst
archmoj Sep 19, 2019
e9c0202
add calc test for count branches and leaves
archmoj Sep 19, 2019
e4bc91e
treemap drop leaf.opacity
archmoj Sep 19, 2019
a54ca9c
better description regarding the position of pathbar also move the ga…
archmoj Sep 19, 2019
d7b06a1
change divider to edgeshape
archmoj Sep 19, 2019
c91f9db
revisit ancestors draw - avoid overlaps - end pathbar with edgeshape
archmoj Sep 19, 2019
ceb13f7
tween and animate text for new points on pathbar from their parent
archmoj Sep 19, 2019
1270d67
draw new points of pathbar below parents (to avoid text overlay) - im…
archmoj Sep 20, 2019
35ea4b3
correct current path function - reuse listPath in helpers i.e. to hav…
archmoj Sep 20, 2019
1de80d9
add mock to test with and without values - and pathbar fade with depth
archmoj Sep 20, 2019
e7e22c6
texttemplate and hovertemplate fixes
archmoj Sep 20, 2019
6130f96
rename key to
archmoj Sep 20, 2019
e3a8e91
check for falsy 0 numbers when searching for level and ids
archmoj Sep 20, 2019
14674d6
handle hover edge cases
archmoj Sep 20, 2019
01c5f68
use onPathbar everywhere referring to points on pathbar - this make l…
archmoj Sep 20, 2019
4c7a092
revisit marker opacities: no opacity with colorscale, now having
archmoj Sep 21, 2019
1860826
improve sunburst texttemplate tests - drop the comment for a fixed tr…
archmoj Sep 22, 2019
d2b07b5
improve mocks to show various percentage values and colorscale using …
archmoj Sep 22, 2019
1fdf574
correct displaying percentages
archmoj Sep 23, 2019
3004a9a
fix more percentage edge cases - handle branchvalues remainder
archmoj Sep 23, 2019
4d790a1
correct texttemplate and hover
archmoj Sep 23, 2019
1c6e47b
dont touch pt.parent
archmoj Sep 23, 2019
e339bf5
Avoid using random ids in recording position of empty root
archmoj Sep 23, 2019
1309bfd
improve treemap jasmine tests
archmoj Sep 23, 2019
217c0f4
revisit sunburst and treemap percentages
archmoj Sep 24, 2019
8416883
rework working with parent references in hover and plot
archmoj Sep 24, 2019
c918da9
Reference fixups
archmoj Sep 25, 2019
7d67c7a
try to make parent getter more consistent by using pt.data.data.pid i…
archmoj Sep 25, 2019
f8cef49
add tolerance to tween tests
etpinard Sep 25, 2019
3c12bf6
remove unnecessary check for NaNs
archmoj Sep 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
324 changes: 324 additions & 0 deletions src/traces/treemap/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs;
var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs;

var colorScaleAttrs = require('../../components/colorscale/attributes');
var domainAttrs = require('../../plots/domain').attributes;
var pieAttrs = require('../pie/attributes');
var sunburstAttrs = require('../sunburst/attributes');
var constants = require('./constants');
var extendFlat = require('../../lib/extend').extendFlat;

module.exports = {
labels: sunburstAttrs.labels,
parents: sunburstAttrs.parents,

values: sunburstAttrs.values,
branchvalues: sunburstAttrs.branchvalues,
count: sunburstAttrs.count,

level: sunburstAttrs.level,
maxdepth: sunburstAttrs.maxdepth,

tiling: {
packing: {
valType: 'enumerated',
values: [
'squarify',
'binary',
'dice',
'slice',
'slice-dice',
'dice-slice'
],
dflt: 'squarify',
role: 'info',
editType: 'plot',
description: [
'Determines d3 treemap solver.',
'For more info please refer to https://github.com/d3/d3-hierarchy#treemap-tiling'
].join(' ')
},

squarifyratio: {
valType: 'number',
role: 'info',
min: 1,
dflt: 1,
editType: 'plot',
description: [
'When using *squarify* `packing` algorithm, according to https://github.com/d3/d3-hierarchy/blob/master/README.md#squarify_ratio',
'this option specifies the desired aspect ratio of the generated rectangles.',
'The ratio must be specified as a number greater than or equal to one.',
'Note that the orientation of the generated rectangles (tall or wide)',
'is not implied by the ratio; for example, a ratio of two will attempt',
'to produce a mixture of rectangles whose width:height ratio is either 2:1 or 1:2.',
'When using *squarify*, unlike d3 which uses the Golden Ratio i.e. 1.618034,',
'Plotly applies 1 to increase squares in treemap layouts.'
].join(' ')
},

mirror: {
valType: 'flaglist',
role: 'info',
flags: [
'x',
'y'
],
dflt: '',
editType: 'plot',
description: [
'Determines if the positions obtained from solver are mirrored on each axis.'
].join(' ')
},

pad: {
valType: 'number',
role: 'style',
min: 0,
dflt: 3,
editType: 'plot',
description: [
'Sets the inner padding (in px).'
].join(' ')
},

editType: 'calc',
},

marker: extendFlat({
pad: {
top: {
valType: 'number',
role: 'style',
min: 0,
dflt: 'auto',
editType: 'plot',
description: [
'Sets the padding form the top (in px).'
].join(' ')
},
left: {
valType: 'number',
role: 'style',
min: 0,
dflt: 'auto',
editType: 'plot',
description: [
'Sets the padding form the left (in px).'
].join(' ')
},
right: {
valType: 'number',
role: 'style',
min: 0,
dflt: 'auto',
editType: 'plot',
description: [
'Sets the padding form the right (in px).'
].join(' ')
},
bottom: {
valType: 'number',
role: 'style',
min: 0,
dflt: 'auto',
editType: 'plot',
description: [
'Sets the padding form the bottom (in px).'
].join(' ')
},

editType: 'calc'
},

colors: {
valType: 'data_array',
editType: 'calc',
description: [
'Sets the color of each sector of this treemap chart.',
'If not specified, the default trace color set is used',
'to pick the sector colors.'
].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 👌

valType: 'number',
editType: 'style',
role: 'style',
min: 0,
max: 1,
dflt: 1,
description: [
'Sets the opacity for the sectors. With colorscale',
'it is defaulted to 1; otherwise it is defaulted to 0.5'
].join(' ')
},

line: {
color: extendFlat({}, pieAttrs.marker.line.color, {
dflt: null,
description: [
'Sets the color of the line enclosing each sector.',
'Defaults to the `paper_bgcolor` value.'
].join(' ')
}),
width: extendFlat({}, pieAttrs.marker.line.width, {dflt: 1}),
editType: 'calc'
},

editType: 'calc'
},
colorScaleAttrs('marker', {
colorAttr: 'colors',
anim: false // TODO: set to anim: true?
})
),

branch: {
opacity: {
valType: 'number',
editType: 'style',
role: 'style',
min: 0,
max: 1,
description: [
'Sets the opacity of the leaves i.e. based on the hierarchy height.',
'With colorscale it is defaulted to 1; otherwise it is defaulted to 0.7'
].join(' ')
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this solution, especially this part:

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

which I think gives a nice visual effect while keeping all sectors opaque enough.

As this implementation is a little different than the one proposed in #1038 (comment) and I don't remember us talking about this in a meeting, I'll cc @nicolaskruchten @alexcjohnson @antoinerg

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

For a small number of levels I think it looks good. For a large number like treemap_pad_mirror has, seems like the bottom layer is too transparent. Maybe limit the power to something like 4 max? If that looks weird as you cross the max-height threshold, we could smoothly asymptote with something like:
hEffective = (h^(-p) + max^(-p))^(-1/p)
(and insert hEffective as the power instead of pt.height)
p=3, max=4 would be the first one I'd try.

Copy link
Contributor

Choose a reason for hiding this comment

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

The latest implementation is in #4185 (comment)

},
editType: 'plot'
},

pathbar: {
visible: {
valType: 'boolean',
dflt: true,
role: 'info',
editType: 'plot',
description: [
'Determines if the path bar is drawn.'
].join(' ')
},

position: {
valType: 'enumerated',
values: [
'top',
'bottom'
],
dflt: 'top',
role: 'info',
editType: 'plot',
description: [
'Determines on which side of the the treemap the',
'`pathbar` should be presented.'
].join(' ')
},

divider: {
valType: 'enumerated',
values: [
'>',
'<',
'|',
'/',
'\\'
],
dflt: 'auto',
role: 'style',
editType: 'plot',
description: [
'Determines which divider is used between labels.',
'With *top* `pathbar.position` it is defaulted to */*; and',
'with *bottom* `pathbar.position` it is defaulted to *\\*.'
].join(' ')
},

height: {
valType: 'number',
dflt: 'auto',
min: 12,
role: 'info',
editType: 'plot',
description: [
'Sets the height (in px). If not specified the `parbath.textfont.size` is used',
'with 3 pixles extra padding on each side.'
].join(' ')
},

textfont: extendFlat({}, pieAttrs.textfont, {
description: 'Sets the font used inside `pathbar`.'
}),

editType: 'calc'
},

text: pieAttrs.text,
textinfo: sunburstAttrs.textinfo,
// TODO: incorporate `label` and `value` in the eventData
texttemplate: texttemplateAttrs({editType: 'plot'}, {
keys: constants.eventDataKeys.concat(['label', 'value'])
}),

hovertext: pieAttrs.hovertext,
hoverinfo: sunburstAttrs.hoverinfo,
hovertemplate: hovertemplateAttrs({}, {
keys: constants.eventDataKeys
}),

textfont: pieAttrs.textfont,
insidetextfont: pieAttrs.insidetextfont,
outsidetextfont: pieAttrs.outsidetextfont,

textposition: {
valType: 'enumerated',
values: [
'top left', 'top center', 'top right',
'middle left', 'middle center', 'middle right',
'bottom left', 'bottom center', 'bottom right'
],
dflt: 'top left',
role: 'style',
editType: 'plot',
description: [
'Sets the positions of the `text` elements.'
].join(' ')
},

domain: domainAttrs({name: 'treemap', trace: true, editType: 'calc'}),

_hovered: {
marker: {
line: {
color: extendFlat({}, pieAttrs.marker.line.color, {
dflt: 'auto',
description: [
'Sets the color of the line',
'enclosing each sector when it is hovered'
].join(' ')
}),
width: extendFlat({}, pieAttrs.marker.line.width, {
dflt: 'auto',
description: [
'Sets the width (in px) of the line',
'enclosing each sector when it is hovered.'
].join(' ')
}),
editType: 'style'
},

editType: 'style'
},
editType: 'style'
}
};
21 changes: 21 additions & 0 deletions src/traces/treemap/base_plot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var plots = require('../../plots/plots');

exports.name = 'treemap';

exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
plots.plotBasePlot(exports.name, gd, traces, transitionOpts, makeOnCompleteCallback);
};

exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
plots.cleanBasePlot(exports.name, newFullData, newFullLayout, oldFullData, oldFullLayout);
};
19 changes: 19 additions & 0 deletions src/traces/treemap/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var sunburstCalc = require('../sunburst/calc');

exports.calc = function(gd, trace) {
return sunburstCalc.calc(gd, trace);
};

exports.crossTraceCalc = function(gd) {
return sunburstCalc._runCrossTraceCalc('treemap', gd);
};
23 changes: 23 additions & 0 deletions src/traces/treemap/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = {
CLICK_TRANSITION_TIME: 750,
CLICK_TRANSITION_EASING: 'cubic',
eventDataKeys: [
'currentPath',
'percentRoot',
'percentVisible',
'percentParent',
'rootLabel',
'visibleLabel',
'parentLabel'
]
};
Loading