diff --git a/lib/icicle.js b/lib/icicle.js new file mode 100644 index 00000000000..2fb9ed0df7c --- /dev/null +++ b/lib/icicle.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('../src/traces/icicle'); diff --git a/lib/index-strict.js b/lib/index-strict.js index aa541fd970c..e57d825d435 100644 --- a/lib/index-strict.js +++ b/lib/index-strict.js @@ -19,6 +19,7 @@ Plotly.register([ require('./pie'), require('./sunburst'), require('./treemap'), + require('./icicle'), require('./funnelarea'), require('./scattergeo'), require('./choropleth'), diff --git a/lib/index.js b/lib/index.js index a447211d9ab..77d07357c88 100644 --- a/lib/index.js +++ b/lib/index.js @@ -19,6 +19,7 @@ Plotly.register([ require('./pie'), require('./sunburst'), require('./treemap'), + require('./icicle'), require('./funnelarea'), require('./scatter3d'), require('./surface'), diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 03397d85096..a31e5189d06 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2374,7 +2374,7 @@ var traceUIControlPatterns = [ {pattern: /(^|value\.)visible$/, attr: 'legend.uirevision'}, {pattern: /^dimensions\[\d+\]\.constraintrange/}, {pattern: /^node\.(x|y|groups)/}, // for Sankey nodes - {pattern: /^level$/}, // for Sunburst & Treemap traces + {pattern: /^level$/}, // for Sunburst, Treemap and Icicle traces // below this you must be in editable: true mode // TODO: I still put name and title with `trace.uirevision` @@ -3738,6 +3738,9 @@ function makePlotFramework(gd) { // single pie layer for the whole plot fullLayout._pielayer = fullLayout._paper.append('g').classed('pielayer', true); + // single treemap layer for the whole plot + fullLayout._iciclelayer = fullLayout._paper.append('g').classed('iciclelayer', true); + // single treemap layer for the whole plot fullLayout._treemaplayer = fullLayout._paper.append('g').classed('treemaplayer', true); diff --git a/src/plots/plots.js b/src/plots/plots.js index e350ccc75ef..fffa7e90735 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2927,10 +2927,11 @@ plots.doCalcdata = function(gd, traces) { gd._hmpixcount = 0; gd._hmlumcount = 0; - // for sharing colors across pies / sunbursts / treemap / funnelarea (and for legend) + // for sharing colors across pies / sunbursts / treemap / icicle / funnelarea (and for legend) fullLayout._piecolormap = {}; fullLayout._sunburstcolormap = {}; fullLayout._treemapcolormap = {}; + fullLayout._iciclecolormap = {}; fullLayout._funnelareacolormap = {}; // If traces were specified and this trace was not included, diff --git a/src/traces/bar/uniform_text.js b/src/traces/bar/uniform_text.js index b40ab37c9e1..b7b2390d8b5 100644 --- a/src/traces/bar/uniform_text.js +++ b/src/traces/bar/uniform_text.js @@ -17,6 +17,7 @@ function resizeText(gd, gTrace, traceType) { selector = 'g.slice'; break; case 'treemap' : + case 'icicle' : selector = 'g.slice, g.pathbar'; break; default : diff --git a/src/traces/icicle/attributes.js b/src/traces/icicle/attributes.js new file mode 100644 index 00000000000..abb5f117d85 --- /dev/null +++ b/src/traces/icicle/attributes.js @@ -0,0 +1,98 @@ +'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 treemapAttrs = require('../treemap/attributes'); +var constants = require('../treemap/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: { + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'h', + editType: 'plot', + description: [ + 'When set in conjunction with `tiling.flip`, determines on', + 'which side the root nodes are drawn in the chart. If', + '`tiling.orientation` is *v* and `tiling.flip` is **, the root', + 'nodes appear at the top. If `tiling.orientation` is *v* and', + '`tiling.flip` is *y*, the root nodes appear at the bottom. If', + '`tiling.orientation` is *h* and `tiling.flip` is **, the', + 'root nodes appear at the left. If `tiling.orientation` is *h*', + 'and `tiling.flip` is *x*, the root nodes appear at the right.', + ].join(' ') + }, + + flip: treemapAttrs.tiling.flip, + + pad: { + valType: 'number', + min: 0, + dflt: 0, + editType: 'plot', + description: [ + 'Sets the inner padding (in px).' + ].join(' ') + }, + + + editType: 'calc', + }, + + marker: extendFlat({ + colors: sunburstAttrs.marker.colors, + + line: sunburstAttrs.marker.line, + + editType: 'calc' + }, + colorScaleAttrs('marker', { + colorAttr: 'colors', + anim: false // TODO: set to anim: true? + }) + ), + + leaf: sunburstAttrs.leaf, + + pathbar: treemapAttrs.pathbar, + + 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: treemapAttrs.outsidetextfont, + + textposition: treemapAttrs.textposition, + sort: pieAttrs.sort, + root: sunburstAttrs.root, + + domain: domainAttrs({name: 'icicle', trace: true, editType: 'calc'}), +}; diff --git a/src/traces/icicle/base_plot.js b/src/traces/icicle/base_plot.js new file mode 100644 index 00000000000..d33b024f695 --- /dev/null +++ b/src/traces/icicle/base_plot.js @@ -0,0 +1,13 @@ +'use strict'; + +var plots = require('../../plots/plots'); + +exports.name = 'icicle'; + +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); +}; diff --git a/src/traces/icicle/calc.js b/src/traces/icicle/calc.js new file mode 100644 index 00000000000..2bf57bd16da --- /dev/null +++ b/src/traces/icicle/calc.js @@ -0,0 +1,11 @@ +'use strict'; + +var calc = require('../sunburst/calc'); + +exports.calc = function(gd, trace) { + return calc.calc(gd, trace); +}; + +exports.crossTraceCalc = function(gd) { + return calc._runCrossTraceCalc('icicle', gd); +}; diff --git a/src/traces/icicle/defaults.js b/src/traces/icicle/defaults.js new file mode 100644 index 00000000000..a549f1eac6e --- /dev/null +++ b/src/traces/icicle/defaults.js @@ -0,0 +1,101 @@ +'use strict'; + +var Lib = require('../../lib'); +var attributes = require('./attributes'); +var Color = require('../../components/color'); +var handleDomainDefaults = require('../../plots/domain').defaults; +var handleText = require('../bar/defaults').handleText; +var TEXTPAD = require('../bar/constants').TEXTPAD; + +var Colorscale = require('../../components/colorscale'); +var hasColorscale = Colorscale.hasColorscale; +var colorscaleDefaults = Colorscale.handleDefaults; + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var labels = coerce('labels'); + var parents = coerce('parents'); + + if(!labels || !labels.length || !parents || !parents.length) { + traceOut.visible = false; + return; + } + + var vals = coerce('values'); + if(vals && vals.length) { + coerce('branchvalues'); + } else { + coerce('count'); + } + + coerce('level'); + coerce('maxdepth'); + + coerce('tiling.orientation'); + coerce('tiling.flip'); + coerce('tiling.pad'); + + var text = coerce('text'); + coerce('texttemplate'); + if(!traceOut.texttemplate) coerce('textinfo', Array.isArray(text) ? 'text+label' : 'label'); + + coerce('hovertext'); + coerce('hovertemplate'); + + var hasPathbar = coerce('pathbar.visible'); + + var textposition = 'auto'; + handleText(traceIn, traceOut, layout, coerce, textposition, { + hasPathbar: hasPathbar, + moduleHasSelected: false, + moduleHasUnselected: false, + moduleHasConstrain: false, + moduleHasCliponaxis: false, + moduleHasTextangle: false, + moduleHasInsideanchor: false + }); + coerce('textposition'); + + var lineWidth = coerce('marker.line.width'); + if(lineWidth) coerce('marker.line.color', layout.paper_bgcolor); + + coerce('marker.colors'); + var withColorscale = traceOut._hasColorscale = ( + hasColorscale(traceIn, 'marker', 'colors') || + (traceIn.marker || {}).coloraxis // N.B. special logic to consider "values" colorscales + ); + if(withColorscale) { + colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'marker.', cLetter: 'c'}); + } + + coerce('leaf.opacity', withColorscale ? 1 : 0.7); + + traceOut._hovered = { + marker: { + line: { + width: 2, + color: Color.contrast(layout.paper_bgcolor) + } + } + }; + + if(hasPathbar) { + // This works even for multi-line labels as icicle pathbar trim out line breaks + coerce('pathbar.thickness', traceOut.pathbar.textfont.size + 2 * TEXTPAD); + + coerce('pathbar.side'); + coerce('pathbar.edgeshape'); + } + + coerce('sort'); + + coerce('root.color'); + + handleDomainDefaults(traceOut, layout, coerce); + + // do not support transforms for now + traceOut._length = null; +}; diff --git a/src/traces/icicle/draw_descendants.js b/src/traces/icicle/draw_descendants.js new file mode 100644 index 00000000000..b742438ea24 --- /dev/null +++ b/src/traces/icicle/draw_descendants.js @@ -0,0 +1,200 @@ +'use strict'; + +var d3 = require('@plotly/d3'); +var Lib = require('../../lib'); +var Drawing = require('../../components/drawing'); +var svgTextUtils = require('../../lib/svg_text_utils'); + +var partition = require('./partition'); +var styleOne = require('./style').styleOne; +var constants = require('../treemap/constants'); +var helpers = require('../sunburst/helpers'); +var attachFxHandlers = require('../sunburst/fx'); +var formatSliceLabel = require('../sunburst/plot').formatSliceLabel; + +var onPathbar = false; // for Descendants + +module.exports = function drawDescendants(gd, cd, entry, slices, opts) { + var width = opts.width; + var height = opts.height; + var viewX = opts.viewX; + var viewY = opts.viewY; + var pathSlice = opts.pathSlice; + var toMoveInsideSlice = opts.toMoveInsideSlice; + var strTransform = opts.strTransform; + var hasTransition = opts.hasTransition; + var handleSlicesExit = opts.handleSlicesExit; + var makeUpdateSliceInterpolator = opts.makeUpdateSliceInterpolator; + var makeUpdateTextInterpolator = opts.makeUpdateTextInterpolator; + var prevEntry = opts.prevEntry; + var refRect = {}; + + var fullLayout = gd._fullLayout; + var cd0 = cd[0]; + var trace = cd0.trace; + + var hasLeft = trace.textposition.indexOf('left') !== -1; + var hasRight = trace.textposition.indexOf('right') !== -1; + var hasBottom = trace.textposition.indexOf('bottom') !== -1; + + // N.B. slice data isn't the calcdata, + // grab corresponding calcdata item in sliceData[i].data.data + var allData = partition(entry, [width, height], { + flipX: trace.tiling.flip.indexOf('x') > -1, + flipY: trace.tiling.flip.indexOf('y') > -1, + orientation: trace.tiling.orientation, + pad: { + inner: trace.tiling.pad + }, + maxDepth: trace._maxDepth + }); + + var sliceData = allData.descendants(); + + var minVisibleDepth = Infinity; + var maxVisibleDepth = -Infinity; + sliceData.forEach(function(pt) { + var depth = pt.depth; + if(depth >= trace._maxDepth) { + // hide slices that won't show up on graph + pt.x0 = pt.x1 = (pt.x0 + pt.x1) / 2; + pt.y0 = pt.y1 = (pt.y0 + pt.y1) / 2; + } else { + minVisibleDepth = Math.min(minVisibleDepth, depth); + maxVisibleDepth = Math.max(maxVisibleDepth, depth); + } + }); + + slices = slices.data(sliceData, helpers.getPtId); + + trace._maxVisibleLayers = isFinite(maxVisibleDepth) ? maxVisibleDepth - minVisibleDepth + 1 : 0; + + slices.enter().append('g') + .classed('slice', true); + + handleSlicesExit(slices, onPathbar, refRect, [width, height], pathSlice); + + slices.order(); + + // next coords of previous entry + var nextOfPrevEntry = null; + if(hasTransition && prevEntry) { + var prevEntryId = helpers.getPtId(prevEntry); + slices.each(function(pt) { + if(nextOfPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) { + nextOfPrevEntry = { + x0: pt.x0, + x1: pt.x1, + y0: pt.y0, + y1: pt.y1 + }; + } + }); + } + + var getRefRect = function() { + return nextOfPrevEntry || { + x0: 0, + x1: width, + y0: 0, + y1: height + }; + }; + + var updateSlices = slices; + if(hasTransition) { + updateSlices = updateSlices.transition().each('end', function() { + // N.B. gd._transitioning is (still) *true* by the time + // transition updates get here + var sliceTop = d3.select(this); + helpers.setSliceCursor(sliceTop, gd, { + hideOnRoot: true, + hideOnLeaves: false, + isTransitioning: false + }); + }); + } + + updateSlices.each(function(pt) { + pt._hoverX = viewX(pt.x1 - trace.tiling.pad), + pt._hoverY = hasBottom ? + viewY(pt.y1 - trace.tiling.pad / 2) : + viewY(pt.y0 + trace.tiling.pad / 2); + + var sliceTop = d3.select(this); + + var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) { + s.style('pointer-events', 'all'); + }); + + if(hasTransition) { + slicePath.transition().attrTween('d', function(pt2) { + var interp = makeUpdateSliceInterpolator( + pt2, + onPathbar, + getRefRect(), + [width, height], + { + orientation: trace.tiling.orientation, + flipX: trace.tiling.flip.indexOf('x') > -1, + flipY: trace.tiling.flip.indexOf('y') > -1, + } + ); + return function(t) { return pathSlice(interp(t)); }; + }); + } else { + slicePath.attr('d', pathSlice); + } + + sliceTop + .call(attachFxHandlers, entry, gd, cd, { + styleOne: styleOne, + eventDataKeys: constants.eventDataKeys, + transitionTime: constants.CLICK_TRANSITION_TIME, + transitionEasing: constants.CLICK_TRANSITION_EASING + }) + .call(helpers.setSliceCursor, gd, { isTransitioning: gd._transitioning }); + + slicePath.call(styleOne, pt, trace, { + hovered: false + }); + + if(pt.x0 === pt.x1 || pt.y0 === pt.y1) { + pt._text = ''; + } else { + pt._text = formatSliceLabel(pt, entry, trace, cd, fullLayout) || ''; + } + + var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext'); + var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) { + // prohibit tex interpretation until we can handle + // tex and regular text together + s.attr('data-notex', 1); + }); + + var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font)); + + sliceText.text(pt._text || ' ') // use one space character instead of a blank string to avoid jumps during transition + .classed('slicetext', true) + .attr('text-anchor', hasRight ? 'end' : hasLeft ? 'start' : 'middle') + .call(Drawing.font, font) + .call(svgTextUtils.convertToTspans, gd); + + pt.textBB = Drawing.bBox(sliceText.node()); + pt.transform = toMoveInsideSlice(pt, { + fontSize: font.size + }); + pt.transform.fontSize = font.size; + + if(hasTransition) { + sliceText.transition().attrTween('transform', function(pt2) { + var interp = makeUpdateTextInterpolator(pt2, onPathbar, getRefRect(), [width, height]); + return function(t) { return strTransform(interp(t)); }; + }); + } else { + sliceText.attr('transform', strTransform(pt)); + } + }); + + return nextOfPrevEntry; +}; diff --git a/src/traces/icicle/index.js b/src/traces/icicle/index.js new file mode 100644 index 00000000000..45c513cc51d --- /dev/null +++ b/src/traces/icicle/index.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports = { + moduleType: 'trace', + name: 'icicle', + basePlotModule: require('./base_plot'), + categories: [], + animatable: true, + + attributes: require('./attributes'), + layoutAttributes: require('./layout_attributes'), + supplyDefaults: require('./defaults'), + supplyLayoutDefaults: require('./layout_defaults'), + + calc: require('./calc').calc, + crossTraceCalc: require('./calc').crossTraceCalc, + + plot: require('./plot'), + style: require('./style').style, + + colorbar: require('../scatter/marker_colorbar'), + + meta: { + description: [ + 'Visualize hierarchal data from leaves (and/or outer branches) towards root', + 'with rectangles. The icicle sectors are determined by the entries in', + '*labels* or *ids* and in *parents*.' + ].join(' ') + } +}; diff --git a/src/traces/icicle/layout_attributes.js b/src/traces/icicle/layout_attributes.js new file mode 100644 index 00000000000..dc6989c067a --- /dev/null +++ b/src/traces/icicle/layout_attributes.js @@ -0,0 +1,29 @@ +'use strict'; + +module.exports = { + iciclecolorway: { + valType: 'colorlist', + editType: 'calc', + description: [ + 'Sets the default icicle slice colors. Defaults to the main', + '`colorway` used for trace colors. If you specify a new', + 'list here it can still be extended with lighter and darker', + 'colors, see `extendiciclecolors`.' + ].join(' ') + }, + extendiciclecolors: { + valType: 'boolean', + dflt: true, + editType: 'calc', + description: [ + 'If `true`, the icicle slice colors (whether given by `iciclecolorway` or', + 'inherited from `colorway`) will be extended to three times its', + 'original length by first repeating every color 20% lighter then', + 'each color 20% darker. This is intended to reduce the likelihood', + 'of reusing the same color when you have many slices, but you can', + 'set `false` to disable.', + 'Colors provided in the trace, using `marker.colors`, are never', + 'extended.' + ].join(' ') + } +}; diff --git a/src/traces/icicle/layout_defaults.js b/src/traces/icicle/layout_defaults.js new file mode 100644 index 00000000000..1e123933605 --- /dev/null +++ b/src/traces/icicle/layout_defaults.js @@ -0,0 +1,12 @@ +'use strict'; + +var Lib = require('../../lib'); +var layoutAttributes = require('./layout_attributes'); + +module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { + function coerce(attr, dflt) { + return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt); + } + coerce('iciclecolorway', layoutOut.colorway); + coerce('extendiciclecolors'); +}; diff --git a/src/traces/icicle/partition.js b/src/traces/icicle/partition.js new file mode 100644 index 00000000000..4e67b400bfd --- /dev/null +++ b/src/traces/icicle/partition.js @@ -0,0 +1,34 @@ +'use strict'; + +var d3Hierarchy = require('d3-hierarchy'); +var flipTree = require('../treemap/flip_tree'); + +module.exports = function partition(entry, size, opts) { + var flipX = opts.flipX; + var flipY = opts.flipY; + var swapXY = opts.orientation === 'h'; + var maxDepth = opts.maxDepth; + + var newWidth = size[0]; + var newHeight = size[1]; + if(maxDepth) { + newWidth = (entry.height + 1) * size[0] / Math.min(entry.height + 1, maxDepth); + newHeight = (entry.height + 1) * size[1] / Math.min(entry.height + 1, maxDepth); + } + + var result = d3Hierarchy + .partition() + .padding(opts.pad.inner) + .size( + swapXY ? [size[1], newWidth] : [size[0], newHeight] + )(entry); + + if(swapXY || flipX || flipY) { + flipTree(result, size, { + swapXY: swapXY, + flipX: flipX, + flipY: flipY + }); + } + return result; +}; diff --git a/src/traces/icicle/plot.js b/src/traces/icicle/plot.js new file mode 100644 index 00000000000..d87b205f5ad --- /dev/null +++ b/src/traces/icicle/plot.js @@ -0,0 +1,11 @@ +'use strict'; + +var draw = require('../treemap/draw'); +var drawDescendants = require('./draw_descendants'); + +module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback) { + return draw(gd, cdmodule, transitionOpts, makeOnCompleteCallback, { + type: 'icicle', + drawDescendants: drawDescendants + }); +}; diff --git a/src/traces/icicle/style.js b/src/traces/icicle/style.js new file mode 100644 index 00000000000..d4c429df953 --- /dev/null +++ b/src/traces/icicle/style.js @@ -0,0 +1,41 @@ +'use strict'; + +var d3 = require('@plotly/d3'); +var Color = require('../../components/color'); +var Lib = require('../../lib'); +var resizeText = require('../bar/uniform_text').resizeText; + +function style(gd) { + var s = gd._fullLayout._iciclelayer.selectAll('.trace'); + resizeText(gd, s, 'icicle'); + + s.each(function(cd) { + var gTrace = d3.select(this); + var cd0 = cd[0]; + var trace = cd0.trace; + + gTrace.style('opacity', trace.opacity); + + gTrace.selectAll('path.surface').each(function(pt) { + d3.select(this).call(styleOne, pt, trace); + }); + }); +} + +function styleOne(s, pt, trace) { + var cdi = pt.data.data; + var isLeaf = !pt.children; + var ptNumber = cdi.i; + var lineColor = Lib.castOption(trace, ptNumber, 'marker.line.color') || Color.defaultLine; + var lineWidth = Lib.castOption(trace, ptNumber, 'marker.line.width') || 0; + + s.style('stroke-width', lineWidth) + .call(Color.fill, cdi.color) + .call(Color.stroke, lineColor) + .style('opacity', isLeaf ? trace.leaf.opacity : null); +} + +module.exports = { + style: style, + styleOne: styleOne +}; diff --git a/src/traces/sunburst/attributes.js b/src/traces/sunburst/attributes.js index 01f7be1b0a2..a2835e70c25 100644 --- a/src/traces/sunburst/attributes.js +++ b/src/traces/sunburst/attributes.js @@ -206,7 +206,7 @@ module.exports = { editType: 'calc', dflt: 'rgba(0,0,0,0)', description: [ - 'sets the color of the root node for a sunburst or a treemap trace.', + 'sets the color of the root node for a sunburst/treemap/icicle trace.', 'this has no effect when a colorscale is used to set the markers.' ].join(' ') }, diff --git a/src/traces/sunburst/calc.js b/src/traces/sunburst/calc.js index 097656a2ec5..c05e4a39f1e 100644 --- a/src/traces/sunburst/calc.js +++ b/src/traces/sunburst/calc.js @@ -13,6 +13,7 @@ var ALMOST_EQUAL = require('../../constants/numerical').ALMOST_EQUAL; var sunburstExtendedColorWays = {}; var treemapExtendedColorWays = {}; +var icicleExtendedColorWays = {}; exports.calc = function(gd, trace) { var fullLayout = gd._fullLayout; @@ -245,7 +246,9 @@ exports._runCrossTraceCalc = function(desiredType, gd) { if(fullLayout['extend' + desiredType + 'colors']) { colorWay = generateExtendedColors(colorWay, - desiredType === 'treemap' ? treemapExtendedColorWays : sunburstExtendedColorWays + desiredType === 'icicle' ? icicleExtendedColorWays : + desiredType === 'treemap' ? treemapExtendedColorWays : + sunburstExtendedColorWays ); } var dfltColorCount = 0; diff --git a/src/traces/sunburst/fx.js b/src/traces/sunburst/fx.js index a12a3f55ea8..66f2cec6d27 100644 --- a/src/traces/sunburst/fx.js +++ b/src/traces/sunburst/fx.js @@ -18,7 +18,9 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) { var hierarchy = cd0.hierarchy; var isSunburst = trace.type === 'sunburst'; - var isTreemap = trace.type === 'treemap'; + var isTreemapOrIcicle = + trace.type === 'treemap' || + trace.type === 'icicle'; // hover state vars // have we drawn a hover label, so it should be cleared later @@ -57,7 +59,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) { hoverCenterX = cd0.cx + pt.pxmid[0] * (1 - pt.rInscribed); hoverCenterY = cd0.cy + pt.pxmid[1] * (1 - pt.rInscribed); } - if(isTreemap) { + if(isTreemapOrIcicle) { hoverCenterX = pt._hoverX; hoverCenterY = pt._hoverY; } @@ -145,7 +147,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) { hoverItems.x1 = hoverCenterX + pt.rInscribed * pt.rpx1; hoverItems.idealAlign = pt.pxmid[0] < 0 ? 'left' : 'right'; } - if(isTreemap) { + if(isTreemapOrIcicle) { hoverItems.x = hoverCenterX; hoverItems.idealAlign = hoverCenterX < 0 ? 'left' : 'right'; } @@ -159,7 +161,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) { trace._hasHoverLabel = true; } - if(isTreemap) { + if(isTreemapOrIcicle) { var slice = sliceTop.select('path.surface'); opts.styleOne(slice, pt, traceNow, { hovered: true @@ -192,7 +194,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) { trace._hasHoverLabel = false; } - if(isTreemap) { + if(isTreemapOrIcicle) { var slice = sliceTop.select('path.surface'); opts.styleOne(slice, pt, traceNow, { hovered: false diff --git a/src/traces/sunburst/helpers.js b/src/traces/sunburst/helpers.js index 8249d6fa9a6..9c90feac35c 100644 --- a/src/traces/sunburst/helpers.js +++ b/src/traces/sunburst/helpers.js @@ -134,7 +134,7 @@ exports.determineTextFont = function(trace, pt, layoutFont, opts) { exports.hasTransition = function(transitionOpts) { // We could optimize hasTransition per trace, - // as sunburst & treemap have no cross-trace logic! + // as sunburst, treemap & icicle have no cross-trace logic! return !!(transitionOpts && transitionOpts.duration > 0); }; diff --git a/src/traces/treemap/draw.js b/src/traces/treemap/draw.js new file mode 100644 index 00000000000..9fa5a7126c2 --- /dev/null +++ b/src/traces/treemap/draw.js @@ -0,0 +1,69 @@ +'use strict'; + +var d3 = require('@plotly/d3'); + +var helpers = require('../sunburst/helpers'); +var uniformText = require('../bar/uniform_text'); +var clearMinTextSize = uniformText.clearMinTextSize; +var resizeText = require('../bar/style').resizeText; + +var plotOne = require('./plot_one'); + +module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback, opts) { + var type = opts.type; + var drawDescendants = opts.drawDescendants; + + var fullLayout = gd._fullLayout; + var layer = fullLayout['_' + type + 'layer']; + var join, onComplete; + + // If transition config is provided, then it is only a partial replot and traces not + // updated are removed. + var isFullReplot = !transitionOpts; + + clearMinTextSize(type, fullLayout); + + join = layer.selectAll('g.trace.' + type) + .data(cdmodule, function(cd) { return cd[0].trace.uid; }); + + join.enter().append('g') + .classed('trace', true) + .classed(type, true); + + join.order(); + + if(!fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts)) { + if(makeOnCompleteCallback) { + // If it was passed a callback to register completion, make a callback. If + // this is created, then it must be executed on completion, otherwise the + // pos-transition redraw will not execute: + onComplete = makeOnCompleteCallback(); + } + + var transition = d3.transition() + .duration(transitionOpts.duration) + .ease(transitionOpts.easing) + .each('end', function() { onComplete && onComplete(); }) + .each('interrupt', function() { onComplete && onComplete(); }); + + transition.each(function() { + // Must run the selection again since otherwise enters/updates get grouped together + // and these get executed out of order. Except we need them in order! + layer.selectAll('g.trace').each(function(cd) { + plotOne(gd, cd, this, transitionOpts, drawDescendants); + }); + }); + } else { + join.each(function(cd) { + plotOne(gd, cd, this, transitionOpts, drawDescendants); + }); + + if(fullLayout.uniformtext.mode) { + resizeText(gd, layer.selectAll('.trace'), type); + } + } + + if(isFullReplot) { + join.exit().remove(); + } +}; diff --git a/src/traces/treemap/flip_tree.js b/src/traces/treemap/flip_tree.js new file mode 100644 index 00000000000..4f8350d7daa --- /dev/null +++ b/src/traces/treemap/flip_tree.js @@ -0,0 +1,36 @@ +'use strict'; + +module.exports = function flipTree(node, size, opts) { + var tmp; + + if(opts.swapXY) { + // swap x0 and y0 + tmp = node.x0; + node.x0 = node.y0; + node.y0 = tmp; + + // swap x1 and y1 + tmp = node.x1; + node.x1 = node.y1; + node.y1 = tmp; + } + + if(opts.flipX) { + tmp = node.x0; + node.x0 = size[0] - node.x1; + node.x1 = size[0] - tmp; + } + + if(opts.flipY) { + tmp = node.y0; + node.y0 = size[1] - node.y1; + node.y1 = size[1] - tmp; + } + + var children = node.children; + if(children) { + for(var i = 0; i < children.length; i++) { + flipTree(children[i], size, opts); + } + } +}; diff --git a/src/traces/treemap/partition.js b/src/traces/treemap/partition.js index 36f12d3697a..0b9af74c6a7 100644 --- a/src/traces/treemap/partition.js +++ b/src/traces/treemap/partition.js @@ -1,6 +1,7 @@ 'use strict'; var d3Hierarchy = require('d3-hierarchy'); +var flipTree = require('./flip_tree'); module.exports = function partition(entry, size, opts) { var flipX = opts.flipX; @@ -59,38 +60,3 @@ function getTilingMethod(key, squarifyratio) { return d3Hierarchy.treemapSliceDice; } } - -function flipTree(node, size, opts) { - var tmp; - - if(opts.swapXY) { - // swap x0 and y0 - tmp = node.x0; - node.x0 = node.y0; - node.y0 = tmp; - - // swap x1 and y1 - tmp = node.x1; - node.x1 = node.y1; - node.y1 = tmp; - } - - if(opts.flipX) { - tmp = node.x0; - node.x0 = size[0] - node.x1; - node.x1 = size[0] - tmp; - } - - if(opts.flipY) { - tmp = node.y0; - node.y0 = size[1] - node.y1; - node.y1 = size[1] - tmp; - } - - var children = node.children; - if(children) { - for(var i = 0; i < children.length; i++) { - flipTree(children[i], size, opts); - } - } -} diff --git a/src/traces/treemap/plot.js b/src/traces/treemap/plot.js index b61aa9921b3..20076b5403b 100644 --- a/src/traces/treemap/plot.js +++ b/src/traces/treemap/plot.js @@ -1,625 +1,11 @@ 'use strict'; -var d3 = require('@plotly/d3'); - -var helpers = require('../sunburst/helpers'); - -var Lib = require('../../lib'); -var TEXTPAD = require('../bar/constants').TEXTPAD; -var barPlot = require('../bar/plot'); -var toMoveInsideBar = barPlot.toMoveInsideBar; -var uniformText = require('../bar/uniform_text'); -var recordMinTextSize = uniformText.recordMinTextSize; -var clearMinTextSize = uniformText.clearMinTextSize; -var resizeText = require('../bar/style').resizeText; -var constants = require('./constants'); +var draw = require('./draw'); var drawDescendants = require('./draw_descendants'); -var drawAncestors = require('./draw_ancestors'); - -module.exports = function(gd, cdmodule, transitionOpts, makeOnCompleteCallback) { - var fullLayout = gd._fullLayout; - var layer = fullLayout._treemaplayer; - var join, onComplete; - - // If transition config is provided, then it is only a partial replot and traces not - // updated are removed. - var isFullReplot = !transitionOpts; - - clearMinTextSize('treemap', fullLayout); - - join = layer.selectAll('g.trace.treemap') - .data(cdmodule, function(cd) { return cd[0].trace.uid; }); - - join.enter().append('g') - .classed('trace', true) - .classed('treemap', true); - - join.order(); - - if(!fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts)) { - if(makeOnCompleteCallback) { - // If it was passed a callback to register completion, make a callback. If - // this is created, then it must be executed on completion, otherwise the - // pos-transition redraw will not execute: - onComplete = makeOnCompleteCallback(); - } - - var transition = d3.transition() - .duration(transitionOpts.duration) - .ease(transitionOpts.easing) - .each('end', function() { onComplete && onComplete(); }) - .each('interrupt', function() { onComplete && onComplete(); }); - - transition.each(function() { - // Must run the selection again since otherwise enters/updates get grouped together - // and these get executed out of order. Except we need them in order! - layer.selectAll('g.trace').each(function(cd) { - plotOne(gd, cd, this, transitionOpts); - }); - }); - } else { - join.each(function(cd) { - plotOne(gd, cd, this, transitionOpts); - }); - - if(fullLayout.uniformtext.mode) { - resizeText(gd, fullLayout._treemaplayer.selectAll('.trace'), 'treemap'); - } - } - - if(isFullReplot) { - join.exit().remove(); - } -}; - -function getKey(pt) { - return helpers.isHierarchyRoot(pt) ? - '' : // don't use the dummyId - helpers.getPtId(pt); -} - -function plotOne(gd, cd, element, transitionOpts) { - var fullLayout = gd._fullLayout; - var cd0 = cd[0]; - var trace = cd0.trace; - var hierarchy = cd0.hierarchy; - var entry = helpers.findEntryWithLevel(hierarchy, trace.level); - - var gTrace = d3.select(element); - var selAncestors = gTrace.selectAll('g.pathbar'); - var selDescendants = gTrace.selectAll('g.slice'); - - if(!entry) { - selAncestors.remove(); - selDescendants.remove(); - return; - } - - var isRoot = helpers.isHierarchyRoot(entry); - var hasTransition = !fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts); - - var maxDepth = helpers.getMaxDepth(trace); - var hasVisibleDepth = function(pt) { - return pt.data.depth - entry.data.depth < maxDepth; - }; - - var gs = fullLayout._size; - var domain = trace.domain; - - var vpw = gs.w * (domain.x[1] - domain.x[0]); - var vph = gs.h * (domain.y[1] - domain.y[0]); - var barW = vpw; - var barH = trace.pathbar.thickness; - var barPad = trace.marker.line.width + constants.gapWithPathbar; - var barDifY = !trace.pathbar.visible ? 0 : - trace.pathbar.side.indexOf('bottom') > -1 ? vph + barPad : -(barH + barPad); - - var pathbarOrigin = { - x0: barW, // slide to the right - x1: barW, - y0: barDifY, - y1: barDifY + barH - }; - - var findClosestEdge = function(pt, ref, size) { - var e = trace.tiling.pad; - var isLeftOfRect = function(x) { return x - e <= ref.x0; }; - var isRightOfRect = function(x) { return x + e >= ref.x1; }; - var isBottomOfRect = function(y) { return y - e <= ref.y0; }; - var isTopOfRect = function(y) { return y + e >= ref.y1; }; - - return { - x0: isLeftOfRect(pt.x0 - e) ? 0 : isRightOfRect(pt.x0 - e) ? size[0] : pt.x0, - x1: isLeftOfRect(pt.x1 + e) ? 0 : isRightOfRect(pt.x1 + e) ? size[0] : pt.x1, - y0: isBottomOfRect(pt.y0 - e) ? 0 : isTopOfRect(pt.y0 - e) ? size[1] : pt.y0, - y1: isBottomOfRect(pt.y1 + e) ? 0 : isTopOfRect(pt.y1 + e) ? size[1] : pt.y1 - }; - }; - - // stash of 'previous' position data used by tweening functions - var prevEntry = null; - var prevLookupPathbar = {}; - var prevLookupSlices = {}; - var nextOfPrevEntry = null; - var getPrev = function(pt, onPathbar) { - return onPathbar ? - prevLookupPathbar[getKey(pt)] : - prevLookupSlices[getKey(pt)]; - }; - - var getOrigin = function(pt, onPathbar, refRect, size) { - if(onPathbar) { - return prevLookupPathbar[getKey(hierarchy)] || pathbarOrigin; - } else { - var ref = prevLookupSlices[trace.level] || refRect; - - if(hasVisibleDepth(pt)) { // case of an empty object - happens when maxdepth is set - return findClosestEdge(pt, ref, size); - } - } - return {}; - }; - - // N.B. handle multiple-root special case - if(cd0.hasMultipleRoots && isRoot) { - maxDepth++; - } - - trace._maxDepth = maxDepth; - trace._backgroundColor = fullLayout.paper_bgcolor; - trace._entryDepth = entry.data.depth; - trace._atRootLevel = isRoot; - - var cenX = -vpw / 2 + gs.l + gs.w * (domain.x[1] + domain.x[0]) / 2; - var cenY = -vph / 2 + gs.t + gs.h * (1 - (domain.y[1] + domain.y[0]) / 2); - - var viewMapX = function(x) { return cenX + x; }; - var viewMapY = function(y) { return cenY + y; }; - - var barY0 = viewMapY(0); - var barX0 = viewMapX(0); - - var viewBarX = function(x) { return barX0 + x; }; - var viewBarY = function(y) { return barY0 + y; }; - - function pos(x, y) { - return x + ',' + y; - } - - var xStart = viewBarX(0); - var limitX0 = function(p) { p.x = Math.max(xStart, p.x); }; - - var edgeshape = trace.pathbar.edgeshape; - - // pathbar(directory) path generation fn - var pathAncestor = function(d) { - var _x0 = viewBarX(Math.max(Math.min(d.x0, d.x0), 0)); - var _x1 = viewBarX(Math.min(Math.max(d.x1, d.x1), barW)); - var _y0 = viewBarY(d.y0); - var _y1 = viewBarY(d.y1); - - var halfH = barH / 2; - - var pL = {}; - var pR = {}; - - pL.x = _x0; - pR.x = _x1; - - pL.y = pR.y = (_y0 + _y1) / 2; - - var pA = {x: _x0, y: _y0}; - var pB = {x: _x1, y: _y0}; - var pC = {x: _x1, y: _y1}; - var pD = {x: _x0, y: _y1}; - - if(edgeshape === '>') { - pA.x -= halfH; - pB.x -= halfH; - pC.x -= halfH; - pD.x -= halfH; - } else if(edgeshape === '/') { - pC.x -= halfH; - pD.x -= halfH; - pL.x -= halfH / 2; - pR.x -= halfH / 2; - } else if(edgeshape === '\\') { - pA.x -= halfH; - pB.x -= halfH; - pL.x -= halfH / 2; - pR.x -= halfH / 2; - } else if(edgeshape === '<') { - pL.x -= halfH; - pR.x -= halfH; - } - - limitX0(pA); - limitX0(pD); - limitX0(pL); - - limitX0(pB); - limitX0(pC); - limitX0(pR); - - return ( - 'M' + pos(pA.x, pA.y) + - 'L' + pos(pB.x, pB.y) + - 'L' + pos(pR.x, pR.y) + - 'L' + pos(pC.x, pC.y) + - 'L' + pos(pD.x, pD.y) + - 'L' + pos(pL.x, pL.y) + - 'Z' - ); - }; - - // slice path generation fn - var pathDescendant = function(d) { - var _x0 = viewMapX(d.x0); - var _x1 = viewMapX(d.x1); - var _y0 = viewMapY(d.y0); - var _y1 = viewMapY(d.y1); - - var dx = _x1 - _x0; - var dy = _y1 - _y0; - if(!dx || !dy) return ''; - - var FILLET = 0; // TODO: may expose this constant - - var r = ( - dx > 2 * FILLET && - dy > 2 * FILLET - ) ? FILLET : 0; - var arc = function(rx, ry) { return r ? 'a' + pos(r, r) + ' 0 0 1 ' + pos(rx, ry) : ''; }; - - return ( - 'M' + pos(_x0, _y0 + r) + - arc(r, -r) + - 'L' + pos(_x1 - r, _y0) + - arc(r, r) + - 'L' + pos(_x1, _y1 - r) + - arc(-r, r) + - 'L' + pos(_x0 + r, _y1) + - arc(-r, -r) + 'Z' - ); - }; - - var toMoveInsideSlice = function(pt, opts) { - var x0 = pt.x0; - var x1 = pt.x1; - var y0 = pt.y0; - var y1 = pt.y1; - var textBB = pt.textBB; - - var hasFlag = function(f) { return trace.textposition.indexOf(f) !== -1; }; - - var hasBottom = hasFlag('bottom'); - var hasTop = hasFlag('top') || (opts.isHeader && !hasBottom); - - var anchor = - hasTop ? 'start' : - hasBottom ? 'end' : 'middle'; - - var hasRight = hasFlag('right'); - var hasLeft = hasFlag('left') || opts.onPathbar; - - var leftToRight = - hasLeft ? -1 : - hasRight ? 1 : 0; - - var pad = trace.marker.pad; - if(opts.isHeader) { - x0 += pad.l - TEXTPAD; - x1 -= pad.r - TEXTPAD; - if(x0 >= x1) { - var mid = (x0 + x1) / 2; - x0 = mid; - x1 = mid; - } - - // limit the drawing area for headers - var limY; - if(hasBottom) { - limY = y1 - pad.b; - if(y0 < limY && limY < y1) y0 = limY; - } else { - limY = y0 + pad.t; - if(y0 < limY && limY < y1) y1 = limY; - } - } - - // position the text relative to the slice - var transform = toMoveInsideBar(x0, x1, y0, y1, textBB, { - isHorizontal: false, - constrained: true, - angle: 0, - anchor: anchor, - leftToRight: leftToRight - }); - transform.fontSize = opts.fontSize; - - transform.targetX = viewMapX(transform.targetX); - transform.targetY = viewMapY(transform.targetY); - - if(isNaN(transform.targetX) || isNaN(transform.targetY)) { - return {}; - } - - if(x0 !== x1 && y0 !== y1) { - recordMinTextSize(trace.type, transform, fullLayout); - } - - return { - scale: transform.scale, - rotate: transform.rotate, - textX: transform.textX, - textY: transform.textY, - anchorX: transform.anchorX, - anchorY: transform.anchorY, - targetX: transform.targetX, - targetY: transform.targetY - }; - }; - - var interpFromParent = function(pt, onPathbar) { - var parentPrev; - var i = 0; - var Q = pt; - while(!parentPrev && i < maxDepth) { // loop to find a parent/grandParent on the previous graph - i++; - Q = Q.parent; - if(Q) { - parentPrev = getPrev(Q, onPathbar); - } else i = maxDepth; - } - return parentPrev || {}; - }; - - var makeExitSliceInterpolator = function(pt, onPathbar, refRect, size) { - var prev = getPrev(pt, onPathbar); - var next; - - if(onPathbar) { - next = pathbarOrigin; - } else { - var entryPrev = getPrev(entry, onPathbar); - if(entryPrev) { - // 'entryPrev' is here has the previous coordinates of the entry - // node, which corresponds to the last "clicked" node when zooming in - next = findClosestEdge(pt, entryPrev, size); - } else { - // this happens when maxdepth is set, when leaves must - // be removed and the entry is new (i.e. does not have a 'prev' object) - next = {}; - } - } - - return d3.interpolate(prev, next); - }; - - var makeUpdateSliceInterpolator = function(pt, onPathbar, refRect, size) { - var prev0 = getPrev(pt, onPathbar); - var prev; - - if(prev0) { - // if pt already on graph, this is easy - prev = prev0; - } else { - // for new pts: - if(onPathbar) { - prev = pathbarOrigin; - } else { - if(prevEntry) { - // if trace was visible before - if(pt.parent) { - var ref = nextOfPrevEntry || refRect; - - if(ref && !onPathbar) { - prev = findClosestEdge(pt, ref, size); - } else { - // if new leaf (when maxdepth is set), - // grow it from its parent node - prev = {}; - Lib.extendFlat(prev, interpFromParent(pt, onPathbar)); - } - } else { - prev = pt; - } - } else { - prev = {}; - } - } - } - - return d3.interpolate(prev, { - x0: pt.x0, - x1: pt.x1, - y0: pt.y0, - y1: pt.y1 - }); - }; - - var makeUpdateTextInterpolator = function(pt, onPathbar, refRect, size) { - var prev0 = getPrev(pt, onPathbar); - var prev = {}; - var origin = getOrigin(pt, onPathbar, refRect, size); - - Lib.extendFlat(prev, { - transform: toMoveInsideSlice({ - x0: origin.x0, - x1: origin.x1, - y0: origin.y0, - y1: origin.y1, - textBB: pt.textBB, - _text: pt._text - }, { - isHeader: helpers.isHeader(pt, trace) - }) - }); - - if(prev0) { - // if pt already on graph, this is easy - prev = prev0; - } else { - // for new pts: - if(pt.parent) { - Lib.extendFlat(prev, interpFromParent(pt, onPathbar)); - } - } - - var transform = pt.transform; - if(pt.x0 !== pt.x1 && pt.y0 !== pt.y1) { - recordMinTextSize(trace.type, transform, fullLayout); - } - - return d3.interpolate(prev, { - transform: { - scale: transform.scale, - rotate: transform.rotate, - textX: transform.textX, - textY: transform.textY, - anchorX: transform.anchorX, - anchorY: transform.anchorY, - targetX: transform.targetX, - targetY: transform.targetY - } - }); - }; - - var handleSlicesExit = function(slices, onPathbar, refRect, size, pathSlice) { - var width = size[0]; - var height = size[1]; - - if(hasTransition) { - slices.exit().transition() - .each(function() { - var sliceTop = d3.select(this); - - var slicePath = sliceTop.select('path.surface'); - slicePath.transition().attrTween('d', function(pt2) { - var interp = makeExitSliceInterpolator(pt2, onPathbar, refRect, [width, height]); - return function(t) { return pathSlice(interp(t)); }; - }); - - var sliceTextGroup = sliceTop.select('g.slicetext'); - sliceTextGroup.attr('opacity', 0); - }) - .remove(); - } else { - slices.exit().remove(); - } - }; - - var strTransform = function(d) { - var transform = d.transform; - - if(d.x0 !== d.x1 && d.y0 !== d.y1) { - recordMinTextSize(trace.type, transform, fullLayout); - } - - return Lib.getTextTransform({ - textX: transform.textX, - textY: transform.textY, - anchorX: transform.anchorX, - anchorY: transform.anchorY, - targetX: transform.targetX, - targetY: transform.targetY, - scale: transform.scale, - rotate: transform.rotate - }); - }; - - if(hasTransition) { - // Important: do this before binding new sliceData! - - selAncestors.each(function(pt) { - prevLookupPathbar[getKey(pt)] = { - x0: pt.x0, - x1: pt.x1, - y0: pt.y0, - y1: pt.y1 - }; - - if(pt.transform) { - prevLookupPathbar[getKey(pt)].transform = { - textX: pt.transform.textX, - textY: pt.transform.textY, - anchorX: pt.transform.anchorX, - anchorY: pt.transform.anchorY, - targetX: pt.transform.targetX, - targetY: pt.transform.targetY, - scale: pt.transform.scale, - rotate: pt.transform.rotate - }; - } - }); - - selDescendants.each(function(pt) { - prevLookupSlices[getKey(pt)] = { - x0: pt.x0, - x1: pt.x1, - y0: pt.y0, - y1: pt.y1 - }; - - if(pt.transform) { - prevLookupSlices[getKey(pt)].transform = { - textX: pt.transform.textX, - textY: pt.transform.textY, - anchorX: pt.transform.anchorX, - anchorY: pt.transform.anchorY, - targetX: pt.transform.targetX, - targetY: pt.transform.targetY, - scale: pt.transform.scale, - rotate: pt.transform.rotate - }; - } - - if(!prevEntry && helpers.isEntry(pt)) { - prevEntry = pt; - } - }); - } - - nextOfPrevEntry = drawDescendants(gd, cd, entry, selDescendants, { - width: vpw, - height: vph, - - viewX: viewMapX, - viewY: viewMapY, - - pathSlice: pathDescendant, - toMoveInsideSlice: toMoveInsideSlice, - - prevEntry: prevEntry, - makeUpdateSliceInterpolator: makeUpdateSliceInterpolator, - makeUpdateTextInterpolator: makeUpdateTextInterpolator, - - handleSlicesExit: handleSlicesExit, - hasTransition: hasTransition, - strTransform: strTransform +module.exports = function _plot(gd, cdmodule, transitionOpts, makeOnCompleteCallback) { + return draw(gd, cdmodule, transitionOpts, makeOnCompleteCallback, { + type: 'treemap', + drawDescendants: drawDescendants }); - - if(trace.pathbar.visible) { - drawAncestors(gd, cd, entry, selAncestors, { - barDifY: barDifY, - width: barW, - height: barH, - - viewX: viewBarX, - viewY: viewBarY, - - pathSlice: pathAncestor, - toMoveInsideSlice: toMoveInsideSlice, - - makeUpdateSliceInterpolator: makeUpdateSliceInterpolator, - makeUpdateTextInterpolator: makeUpdateTextInterpolator, - - handleSlicesExit: handleSlicesExit, - hasTransition: hasTransition, - strTransform: strTransform - }); - } else { - selAncestors.remove(); - } -} +}; diff --git a/src/traces/treemap/plot_one.js b/src/traces/treemap/plot_one.js new file mode 100644 index 00000000000..55de51c8302 --- /dev/null +++ b/src/traces/treemap/plot_one.js @@ -0,0 +1,589 @@ +'use strict'; + +var d3 = require('@plotly/d3'); + +var helpers = require('../sunburst/helpers'); + +var Lib = require('../../lib'); +var TEXTPAD = require('../bar/constants').TEXTPAD; +var barPlot = require('../bar/plot'); +var toMoveInsideBar = barPlot.toMoveInsideBar; +var uniformText = require('../bar/uniform_text'); +var recordMinTextSize = uniformText.recordMinTextSize; +var constants = require('./constants'); +var drawAncestors = require('./draw_ancestors'); + +function getKey(pt) { + return helpers.isHierarchyRoot(pt) ? + '' : // don't use the dummyId + helpers.getPtId(pt); +} + +module.exports = function plotOne(gd, cd, element, transitionOpts, drawDescendants) { + var fullLayout = gd._fullLayout; + var cd0 = cd[0]; + var trace = cd0.trace; + var type = trace.type; + var isIcicle = type === 'icicle'; + + var hierarchy = cd0.hierarchy; + var entry = helpers.findEntryWithLevel(hierarchy, trace.level); + + var gTrace = d3.select(element); + var selAncestors = gTrace.selectAll('g.pathbar'); + var selDescendants = gTrace.selectAll('g.slice'); + + if(!entry) { + selAncestors.remove(); + selDescendants.remove(); + return; + } + + var isRoot = helpers.isHierarchyRoot(entry); + var hasTransition = !fullLayout.uniformtext.mode && helpers.hasTransition(transitionOpts); + + var maxDepth = helpers.getMaxDepth(trace); + var hasVisibleDepth = function(pt) { + return pt.data.depth - entry.data.depth < maxDepth; + }; + + var gs = fullLayout._size; + var domain = trace.domain; + + var vpw = gs.w * (domain.x[1] - domain.x[0]); + var vph = gs.h * (domain.y[1] - domain.y[0]); + var barW = vpw; + var barH = trace.pathbar.thickness; + var barPad = trace.marker.line.width + constants.gapWithPathbar; + var barDifY = !trace.pathbar.visible ? 0 : + trace.pathbar.side.indexOf('bottom') > -1 ? vph + barPad : -(barH + barPad); + + var pathbarOrigin = { + x0: barW, // slide to the right + x1: barW, + y0: barDifY, + y1: barDifY + barH + }; + + var findClosestEdge = function(pt, ref, size) { + var e = trace.tiling.pad; + var isLeftOfRect = function(x) { return x - e <= ref.x0; }; + var isRightOfRect = function(x) { return x + e >= ref.x1; }; + var isBottomOfRect = function(y) { return y - e <= ref.y0; }; + var isTopOfRect = function(y) { return y + e >= ref.y1; }; + + if(pt.x0 === ref.x0 && pt.x1 === ref.x1 && pt.y0 === ref.y0 && pt.y1 === ref.y1) { + return { + x0: pt.x0, + x1: pt.x1, + y0: pt.y0, + y1: pt.y1 + }; + } + + return { + x0: isLeftOfRect(pt.x0 - e) ? 0 : isRightOfRect(pt.x0 - e) ? size[0] : pt.x0, + x1: isLeftOfRect(pt.x1 + e) ? 0 : isRightOfRect(pt.x1 + e) ? size[0] : pt.x1, + y0: isBottomOfRect(pt.y0 - e) ? 0 : isTopOfRect(pt.y0 - e) ? size[1] : pt.y0, + y1: isBottomOfRect(pt.y1 + e) ? 0 : isTopOfRect(pt.y1 + e) ? size[1] : pt.y1 + }; + }; + + // stash of 'previous' position data used by tweening functions + var prevEntry = null; + var prevLookupPathbar = {}; + var prevLookupSlices = {}; + var nextOfPrevEntry = null; + var getPrev = function(pt, onPathbar) { + return onPathbar ? + prevLookupPathbar[getKey(pt)] : + prevLookupSlices[getKey(pt)]; + }; + + var getOrigin = function(pt, onPathbar, refRect, size) { + if(onPathbar) { + return prevLookupPathbar[getKey(hierarchy)] || pathbarOrigin; + } else { + var ref = prevLookupSlices[trace.level] || refRect; + + if(hasVisibleDepth(pt)) { // case of an empty object - happens when maxdepth is set + return findClosestEdge(pt, ref, size); + } + } + return {}; + }; + + // N.B. handle multiple-root special case + if(cd0.hasMultipleRoots && isRoot) { + maxDepth++; + } + + trace._maxDepth = maxDepth; + trace._backgroundColor = fullLayout.paper_bgcolor; + trace._entryDepth = entry.data.depth; + trace._atRootLevel = isRoot; + + var cenX = -vpw / 2 + gs.l + gs.w * (domain.x[1] + domain.x[0]) / 2; + var cenY = -vph / 2 + gs.t + gs.h * (1 - (domain.y[1] + domain.y[0]) / 2); + + var viewMapX = function(x) { return cenX + x; }; + var viewMapY = function(y) { return cenY + y; }; + + var barY0 = viewMapY(0); + var barX0 = viewMapX(0); + + var viewBarX = function(x) { return barX0 + x; }; + var viewBarY = function(y) { return barY0 + y; }; + + function pos(x, y) { + return x + ',' + y; + } + + var xStart = viewBarX(0); + var limitX0 = function(p) { p.x = Math.max(xStart, p.x); }; + + var edgeshape = trace.pathbar.edgeshape; + + // pathbar(directory) path generation fn + var pathAncestor = function(d) { + var _x0 = viewBarX(Math.max(Math.min(d.x0, d.x0), 0)); + var _x1 = viewBarX(Math.min(Math.max(d.x1, d.x1), barW)); + var _y0 = viewBarY(d.y0); + var _y1 = viewBarY(d.y1); + + var halfH = barH / 2; + + var pL = {}; + var pR = {}; + + pL.x = _x0; + pR.x = _x1; + + pL.y = pR.y = (_y0 + _y1) / 2; + + var pA = {x: _x0, y: _y0}; + var pB = {x: _x1, y: _y0}; + var pC = {x: _x1, y: _y1}; + var pD = {x: _x0, y: _y1}; + + if(edgeshape === '>') { + pA.x -= halfH; + pB.x -= halfH; + pC.x -= halfH; + pD.x -= halfH; + } else if(edgeshape === '/') { + pC.x -= halfH; + pD.x -= halfH; + pL.x -= halfH / 2; + pR.x -= halfH / 2; + } else if(edgeshape === '\\') { + pA.x -= halfH; + pB.x -= halfH; + pL.x -= halfH / 2; + pR.x -= halfH / 2; + } else if(edgeshape === '<') { + pL.x -= halfH; + pR.x -= halfH; + } + + limitX0(pA); + limitX0(pD); + limitX0(pL); + + limitX0(pB); + limitX0(pC); + limitX0(pR); + + return ( + 'M' + pos(pA.x, pA.y) + + 'L' + pos(pB.x, pB.y) + + 'L' + pos(pR.x, pR.y) + + 'L' + pos(pC.x, pC.y) + + 'L' + pos(pD.x, pD.y) + + 'L' + pos(pL.x, pL.y) + + 'Z' + ); + }; + + // slice path generation fn + var pathDescendant = function(d) { + var _x0 = viewMapX(d.x0); + var _x1 = viewMapX(d.x1); + var _y0 = viewMapY(d.y0); + var _y1 = viewMapY(d.y1); + + var dx = _x1 - _x0; + var dy = _y1 - _y0; + if(!dx || !dy) return ''; + + var FILLET = 0; // TODO: may expose this constant + + var r = ( + dx > 2 * FILLET && + dy > 2 * FILLET + ) ? FILLET : 0; + + var arc = function(rx, ry) { return r ? 'a' + pos(r, r) + ' 0 0 1 ' + pos(rx, ry) : ''; }; + + return ( + 'M' + pos(_x0, _y0 + r) + + arc(r, -r) + + 'L' + pos(_x1 - r, _y0) + + arc(r, r) + + 'L' + pos(_x1, _y1 - r) + + arc(-r, r) + + 'L' + pos(_x0 + r, _y1) + + arc(-r, -r) + 'Z' + ); + }; + + var toMoveInsideSlice = function(pt, opts) { + var x0 = pt.x0; + var x1 = pt.x1; + var y0 = pt.y0; + var y1 = pt.y1; + var textBB = pt.textBB; + + var hasFlag = function(f) { return trace.textposition.indexOf(f) !== -1; }; + + var hasBottom = hasFlag('bottom'); + var hasTop = hasFlag('top') || (opts.isHeader && !hasBottom); + + var anchor = + hasTop ? 'start' : + hasBottom ? 'end' : 'middle'; + + var hasRight = hasFlag('right'); + var hasLeft = hasFlag('left') || opts.onPathbar; + + var leftToRight = + hasLeft ? -1 : + hasRight ? 1 : 0; + + // Note that `pad` is just an integer for `icicle`` traces where + // `pad` is a hashmap for treemap: pad.t, pad.b, pad.l, and pad.r + var pad = trace[isIcicle ? 'tiling' : 'marker'].pad; + if(opts.isHeader) { + x0 += (isIcicle ? pad : pad.l) - TEXTPAD; + x1 -= (isIcicle ? pad : pad.r) - TEXTPAD; + if(x0 >= x1) { + var mid = (x0 + x1) / 2; + x0 = mid; + x1 = mid; + } + + // limit the drawing area for headers + var limY; + if(hasBottom) { + limY = y1 - (isIcicle ? pad : pad.b); + if(y0 < limY && limY < y1) y0 = limY; + } else { + limY = y0 + (isIcicle ? pad : pad.t); + if(y0 < limY && limY < y1) y1 = limY; + } + } + + // position the text relative to the slice + var transform = toMoveInsideBar(x0, x1, y0, y1, textBB, { + isHorizontal: false, + constrained: true, + angle: 0, + anchor: anchor, + leftToRight: leftToRight + }); + transform.fontSize = opts.fontSize; + + transform.targetX = viewMapX(transform.targetX); + transform.targetY = viewMapY(transform.targetY); + + if(isNaN(transform.targetX) || isNaN(transform.targetY)) { + return {}; + } + + if(x0 !== x1 && y0 !== y1) { + recordMinTextSize(trace.type, transform, fullLayout); + } + + return { + scale: transform.scale, + rotate: transform.rotate, + textX: transform.textX, + textY: transform.textY, + anchorX: transform.anchorX, + anchorY: transform.anchorY, + targetX: transform.targetX, + targetY: transform.targetY + }; + }; + + var interpFromParent = function(pt, onPathbar) { + var parentPrev; + var i = 0; + var Q = pt; + while(!parentPrev && i < maxDepth) { // loop to find a parent/grandParent on the previous graph + i++; + Q = Q.parent; + if(Q) { + parentPrev = getPrev(Q, onPathbar); + } else i = maxDepth; + } + return parentPrev || {}; + }; + + var makeExitSliceInterpolator = function(pt, onPathbar, refRect, size) { + var prev = getPrev(pt, onPathbar); + var next; + + if(onPathbar) { + next = pathbarOrigin; + } else { + var entryPrev = getPrev(entry, onPathbar); + if(entryPrev) { + // 'entryPrev' is here has the previous coordinates of the entry + // node, which corresponds to the last "clicked" node when zooming in + next = findClosestEdge(pt, entryPrev, size); + } else { + // this happens when maxdepth is set, when leaves must + // be removed and the entry is new (i.e. does not have a 'prev' object) + next = {}; + } + } + + return d3.interpolate(prev, next); + }; + + var makeUpdateSliceInterpolator = function(pt, onPathbar, refRect, size, opts) { + var prev0 = getPrev(pt, onPathbar); + var prev; + + if(prev0) { + // if pt already on graph, this is easy + prev = prev0; + } else { + // for new pts: + if(onPathbar) { + prev = pathbarOrigin; + } else { + if(prevEntry) { + // if trace was visible before + if(pt.parent) { + var ref = nextOfPrevEntry || refRect; + + if(ref && !onPathbar) { + prev = findClosestEdge(pt, ref, size); + } else { + // if new leaf (when maxdepth is set), + // grow it from its parent node + prev = {}; + Lib.extendFlat(prev, interpFromParent(pt, onPathbar)); + } + } else { + prev = Lib.extendFlat({}, pt); + if(isIcicle) { + if(opts.orientation === 'h') { + if(opts.flipX) prev.x0 = pt.x1; + else prev.x1 = 0; + } else { + if(opts.flipY) prev.y0 = pt.y1; + else prev.y1 = 0; + } + } + } + } else { + prev = {}; + } + } + } + + return d3.interpolate(prev, { + x0: pt.x0, + x1: pt.x1, + y0: pt.y0, + y1: pt.y1 + }); + }; + + var makeUpdateTextInterpolator = function(pt, onPathbar, refRect, size) { + var prev0 = getPrev(pt, onPathbar); + var prev = {}; + var origin = getOrigin(pt, onPathbar, refRect, size); + + Lib.extendFlat(prev, { + transform: toMoveInsideSlice({ + x0: origin.x0, + x1: origin.x1, + y0: origin.y0, + y1: origin.y1, + textBB: pt.textBB, + _text: pt._text + }, { + isHeader: helpers.isHeader(pt, trace) + }) + }); + + if(prev0) { + // if pt already on graph, this is easy + prev = prev0; + } else { + // for new pts: + if(pt.parent) { + Lib.extendFlat(prev, interpFromParent(pt, onPathbar)); + } + } + + var transform = pt.transform; + if(pt.x0 !== pt.x1 && pt.y0 !== pt.y1) { + recordMinTextSize(trace.type, transform, fullLayout); + } + + return d3.interpolate(prev, { + transform: { + scale: transform.scale, + rotate: transform.rotate, + textX: transform.textX, + textY: transform.textY, + anchorX: transform.anchorX, + anchorY: transform.anchorY, + targetX: transform.targetX, + targetY: transform.targetY + } + }); + }; + + var handleSlicesExit = function(slices, onPathbar, refRect, size, pathSlice) { + var width = size[0]; + var height = size[1]; + + if(hasTransition) { + slices.exit().transition() + .each(function() { + var sliceTop = d3.select(this); + + var slicePath = sliceTop.select('path.surface'); + slicePath.transition().attrTween('d', function(pt2) { + var interp = makeExitSliceInterpolator(pt2, onPathbar, refRect, [width, height]); + return function(t) { return pathSlice(interp(t)); }; + }); + + var sliceTextGroup = sliceTop.select('g.slicetext'); + sliceTextGroup.attr('opacity', 0); + }) + .remove(); + } else { + slices.exit().remove(); + } + }; + + var strTransform = function(d) { + var transform = d.transform; + + if(d.x0 !== d.x1 && d.y0 !== d.y1) { + recordMinTextSize(trace.type, transform, fullLayout); + } + + return Lib.getTextTransform({ + textX: transform.textX, + textY: transform.textY, + anchorX: transform.anchorX, + anchorY: transform.anchorY, + targetX: transform.targetX, + targetY: transform.targetY, + scale: transform.scale, + rotate: transform.rotate + }); + }; + + if(hasTransition) { + // Important: do this before binding new sliceData! + + selAncestors.each(function(pt) { + prevLookupPathbar[getKey(pt)] = { + x0: pt.x0, + x1: pt.x1, + y0: pt.y0, + y1: pt.y1 + }; + + if(pt.transform) { + prevLookupPathbar[getKey(pt)].transform = { + textX: pt.transform.textX, + textY: pt.transform.textY, + anchorX: pt.transform.anchorX, + anchorY: pt.transform.anchorY, + targetX: pt.transform.targetX, + targetY: pt.transform.targetY, + scale: pt.transform.scale, + rotate: pt.transform.rotate + }; + } + }); + + selDescendants.each(function(pt) { + prevLookupSlices[getKey(pt)] = { + x0: pt.x0, + x1: pt.x1, + y0: pt.y0, + y1: pt.y1 + }; + + if(pt.transform) { + prevLookupSlices[getKey(pt)].transform = { + textX: pt.transform.textX, + textY: pt.transform.textY, + anchorX: pt.transform.anchorX, + anchorY: pt.transform.anchorY, + targetX: pt.transform.targetX, + targetY: pt.transform.targetY, + scale: pt.transform.scale, + rotate: pt.transform.rotate + }; + } + + if(!prevEntry && helpers.isEntry(pt)) { + prevEntry = pt; + } + }); + } + + nextOfPrevEntry = drawDescendants(gd, cd, entry, selDescendants, { + width: vpw, + height: vph, + + viewX: viewMapX, + viewY: viewMapY, + + pathSlice: pathDescendant, + toMoveInsideSlice: toMoveInsideSlice, + + prevEntry: prevEntry, + makeUpdateSliceInterpolator: makeUpdateSliceInterpolator, + makeUpdateTextInterpolator: makeUpdateTextInterpolator, + + handleSlicesExit: handleSlicesExit, + hasTransition: hasTransition, + strTransform: strTransform + }); + + if(trace.pathbar.visible) { + drawAncestors(gd, cd, entry, selAncestors, { + barDifY: barDifY, + width: barW, + height: barH, + + viewX: viewBarX, + viewY: viewBarY, + + pathSlice: pathAncestor, + toMoveInsideSlice: toMoveInsideSlice, + + makeUpdateSliceInterpolator: makeUpdateSliceInterpolator, + makeUpdateTextInterpolator: makeUpdateTextInterpolator, + + handleSlicesExit: handleSlicesExit, + hasTransition: hasTransition, + strTransform: strTransform + }); + } else { + selAncestors.remove(); + } +}; diff --git a/tasks/test_syntax.js b/tasks/test_syntax.js index f6983905f30..e028ef944d6 100644 --- a/tasks/test_syntax.js +++ b/tasks/test_syntax.js @@ -132,15 +132,16 @@ function assertSrcContents() { } else if(IE_BLACK_LIST.indexOf(lastPart) !== -1) { logs.push(file + ' : contains .' + lastPart + ' (IE failure)'); } else if(IE_SVG_BLACK_LIST.indexOf(lastPart) !== -1) { - // add special case for sunburst and treemap where we use 'children' + // add special case for sunburst, icicle and treemap where we use 'children' // off the d3-hierarchy output var dirParts = path.dirname(file).split(path.sep); var filename = dirParts[dirParts.length - 1]; - var isSunburstOrTreemap = + var isSunburstOrIcicleOrTreemap = filename === 'sunburst' || + filename === 'icicle' || filename === 'treemap'; var isLinkedToObject = ['pt', 'd', 'parent', 'node'].indexOf(parts[parts.length - 2]) !== -1; - if(!(isSunburstOrTreemap && isLinkedToObject)) { + if(!(isSunburstOrIcicleOrTreemap && isLinkedToObject)) { logs.push(file + ' : contains .' + lastPart + ' (IE failure in SVG)'); } } diff --git a/tasks/util/constants.js b/tasks/util/constants.js index 45326680e25..7f149e2fbca 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -121,6 +121,7 @@ var partialBundleTraces = { 'histogram', 'histogram2d', 'histogram2dcontour', + 'icicle', 'image', 'indicator', 'ohlc', diff --git a/test/image/baselines/icicle_coffee-maxdepth3-all-directions.png b/test/image/baselines/icicle_coffee-maxdepth3-all-directions.png new file mode 100644 index 00000000000..dbe66b5aae6 Binary files /dev/null and b/test/image/baselines/icicle_coffee-maxdepth3-all-directions.png differ diff --git a/test/image/baselines/icicle_coffee-maxdepth3.png b/test/image/baselines/icicle_coffee-maxdepth3.png new file mode 100644 index 00000000000..3034d92e5fe Binary files /dev/null and b/test/image/baselines/icicle_coffee-maxdepth3.png differ diff --git a/test/image/baselines/icicle_coffee.png b/test/image/baselines/icicle_coffee.png new file mode 100644 index 00000000000..f8eb965d8ec Binary files /dev/null and b/test/image/baselines/icicle_coffee.png differ diff --git a/test/image/baselines/icicle_count_branches.png b/test/image/baselines/icicle_count_branches.png new file mode 100644 index 00000000000..a2e55960540 Binary files /dev/null and b/test/image/baselines/icicle_count_branches.png differ diff --git a/test/image/baselines/icicle_first.png b/test/image/baselines/icicle_first.png new file mode 100644 index 00000000000..8266e9d834e Binary files /dev/null and b/test/image/baselines/icicle_first.png differ diff --git a/test/image/baselines/icicle_flare.png b/test/image/baselines/icicle_flare.png new file mode 100644 index 00000000000..44985bebd14 Binary files /dev/null and b/test/image/baselines/icicle_flare.png differ diff --git a/test/image/baselines/icicle_leaf-opacity-level.png b/test/image/baselines/icicle_leaf-opacity-level.png new file mode 100644 index 00000000000..d1bae3b10da Binary files /dev/null and b/test/image/baselines/icicle_leaf-opacity-level.png differ diff --git a/test/image/baselines/icicle_packages_colorscale_novalue.png b/test/image/baselines/icicle_packages_colorscale_novalue.png new file mode 100644 index 00000000000..91af8476999 Binary files /dev/null and b/test/image/baselines/icicle_packages_colorscale_novalue.png differ diff --git a/test/image/baselines/icicle_root-sort.png b/test/image/baselines/icicle_root-sort.png new file mode 100644 index 00000000000..b8e3ba74e4c Binary files /dev/null and b/test/image/baselines/icicle_root-sort.png differ diff --git a/test/image/baselines/icicle_textposition.png b/test/image/baselines/icicle_textposition.png new file mode 100644 index 00000000000..14b613c2110 Binary files /dev/null and b/test/image/baselines/icicle_textposition.png differ diff --git a/test/image/baselines/icicle_values_colorscale.png b/test/image/baselines/icicle_values_colorscale.png new file mode 100644 index 00000000000..cd176a8c029 Binary files /dev/null and b/test/image/baselines/icicle_values_colorscale.png differ diff --git a/test/image/baselines/icicle_with-without_values.png b/test/image/baselines/icicle_with-without_values.png new file mode 100644 index 00000000000..d2fc7073c5c Binary files /dev/null and b/test/image/baselines/icicle_with-without_values.png differ diff --git a/test/image/baselines/icicle_with-without_values_template.png b/test/image/baselines/icicle_with-without_values_template.png new file mode 100644 index 00000000000..30440a17e69 Binary files /dev/null and b/test/image/baselines/icicle_with-without_values_template.png differ diff --git a/test/image/baselines/uniformtext_icicle.png b/test/image/baselines/uniformtext_icicle.png new file mode 100644 index 00000000000..bd985e605a9 Binary files /dev/null and b/test/image/baselines/uniformtext_icicle.png differ diff --git a/test/image/mocks/icicle_coffee-maxdepth3-all-directions.json b/test/image/mocks/icicle_coffee-maxdepth3-all-directions.json new file mode 100644 index 00000000000..3c1c10b7452 --- /dev/null +++ b/test/image/mocks/icicle_coffee-maxdepth3-all-directions.json @@ -0,0 +1,1320 @@ +{ + "data": [ +{ + "type": "icicle", + "pathbar": { + "visible": false + }, + "tiling": { + "orientation": "h", + "flip": "x" + }, + "maxdepth": 3, + "domain": { + "x": [ + 0, + 0.5 + ], + "y": [ + 0.05, + 0.45 + ] + }, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + }, + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "tiling": { + "orientation": "h" + }, + "maxdepth": 3, + "domain": { + "x": [ + 0.5, + 1 + ], + "y": [ + 0.05, + 0.45 + ] + }, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + }, + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "tiling": { + "orientation": "v", + "flip": "y" + }, + "maxdepth": 3, + "domain": { + "x": [ + 0, + 0.5 + ], + "y": [ + 0.55, + 0.95 + ] + }, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + }, + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "tiling": { + "orientation": "v" + }, + "maxdepth": 3, + "domain": { + "x": [ + 0.5, + 1 + ], + "y": [ + 0.55, + 0.95 + ] + }, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + } + ], + "layout": { + "margin": {"l": 0, "r": 0, "b": 0, "t": 0}, + "width": 1200, + "height": 1200, + "shapes": [ + { "type": "rect", "layer": "below", "x0": 0, "x1": 1, "y0": 0, "y1": 1 } + ], + "annotations": [ + { + "showarrow": false, + "text": "orientation: 'h', flip: 'x'", + "font": { + "size": 16 + }, + "x": 0.25, + "xanchor": "center", + "y": 0.5, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "orientation: 'h', flip: ''", + "font": { + "size": 16 + }, + "x": 0.75, + "xanchor": "center", + "y": 0.5, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "orientation: 'v', flip: 'y'", + "font": { + "size": 16 + }, + "x": 0.25, + "xanchor": "center", + "y": 0.95, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "orientation: 'v', flip: ''", + "font": { + "size": 16 + }, + "x": 0.75, + "xanchor": "center", + "y": 0.95, + "yanchor": "bottom" + } + ] + } +} diff --git a/test/image/mocks/icicle_coffee-maxdepth3.json b/test/image/mocks/icicle_coffee-maxdepth3.json new file mode 100644 index 00000000000..bd3a41e3986 --- /dev/null +++ b/test/image/mocks/icicle_coffee-maxdepth3.json @@ -0,0 +1,314 @@ +{ + "data": [ + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "maxdepth": 3, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + } + ], + "layout": { + "margin": {"l": 0, "r": 0, "b": 0, "t": 0}, + "width": 500, + "height": 500, + "shapes": [ + { "type": "rect", "layer": "below", "x0": 0, "x1": 1, "y0": 0, "y1": 1 } + ] + } +} diff --git a/test/image/mocks/icicle_coffee.json b/test/image/mocks/icicle_coffee.json new file mode 100644 index 00000000000..cbbbd8e00e0 --- /dev/null +++ b/test/image/mocks/icicle_coffee.json @@ -0,0 +1,313 @@ +{ + "data": [ + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "textinfo": "label+percent parent", + "ids": [ + "Aromas", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Ashy", + "Pungent-Creosol", + "Pungent-Phenolic", + "Harsh-Caustic", + "Harsh-Alkaline", + "Sharp-Astringent", + "Sharp-Rough", + "Bland-Neutral", + "Bland-Soft", + "Mellow-Delicate", + "Mellow-Mild", + "Acidy-Nippy", + "Acidy-Piquant", + "Winey-Tangy", + "Winey-Tart", + "Soury-Hard", + "Soury-Acrid", + "Floral-Coffee Blossom", + "Floral-Tea Rose", + "Fragrant-Cardamon Caraway", + "Fragrant-Coriander Seeds", + "Citrus-Lemon", + "Citrus-Apple", + "Berry-like-Apricot", + "Berry-like-Blackberry", + "Alliaceous-Onion", + "Alliaceous-Garlic", + "Leguminous-Cucumber", + "Leguminous-Garden Peas", + "Nut-like-Roasted Peanuts", + "Nut-like-Walnuts", + "Malt-like-Balsamic Rice", + "Malt-like-Toast", + "Candy-like-Roasted Hazelnut", + "Candy-like-Roasted Almond", + "Syrup-like-Honey", + "Syrup-like-Maple Syrup", + "Chocolate-like-Bakers", + "Chocolate-like-Dark Chocolate", + "Vanilla-like-Swiss", + "Vanilla-like-Butter", + "Turpeny-Piney", + "Turpeny-Blackcurrant-like", + "Medicinal-Camphoric", + "Medicinal-Cineolic", + "Warming-Cedar", + "Warming-Pepper", + "Pungent-Clove", + "Pungent-Thyme", + "Smokey-Tarry", + "Smokey-Pipe Tobacco", + "Ashy-Burnt", + "Ashy-Charred" + ], + "labels": [ + "Aromas", + "Tastes", + "Enzymatic", + "Sugar Browning", + "Dry Distillation", + "Bitter", + "Salt", + "Sweet", + "Sour", + "Flowery", + "Fruity", + "Herby", + "Nutty", + "Carmelly", + "Chocolatey", + "Resinous", + "Spicy", + "Carbony", + "Pungent", + "Harsh", + "Sharp", + "Bland", + "Mellow", + "Acidy", + "Winey", + "Soury", + "Floral", + "Fragrant", + "Citrus", + "Berry-like", + "Alliaceous", + "Leguminous", + "Nut-like", + "Malt-like", + "Candy-like", + "Syrup-like", + "Chocolate-like", + "Vanilla-like", + "Turpeny", + "Medicinal", + "Warming", + "Pungent", + "Smokey", + "Ashy", + "Creosol", + "Phenolic", + "Caustic", + "Alkaline", + "Astringent", + "Rough", + "Neutral", + "Soft", + "Delicate", + "Mild", + "Nippy", + "Piquant", + "Tangy", + "Tart", + "Hard", + "Acrid", + "Coffee Blossom", + "Tea Rose", + "Cardamon Caraway", + "Coriander Seeds", + "Lemon", + "Apple", + "Apricot", + "Blackberry", + "Onion", + "Garlic", + "Cucumber", + "Garden Peas", + "Roasted Peanuts", + "Walnuts", + "Balsamic Rice", + "Toast", + "Roasted Hazelnut", + "Roasted Almond", + "Honey", + "Maple Syrup", + "Bakers", + "Dark Chocolate", + "Swiss", + "Butter", + "Piney", + "Blackcurrant-like", + "Camphoric", + "Cineolic", + "Cedar", + "Pepper", + "Clove", + "Thyme", + "Tarry", + "Pipe Tobacco", + "Burnt", + "Charred" + ], + "parents": [ + "", + "", + "Aromas", + "Aromas", + "Aromas", + "Tastes", + "Tastes", + "Tastes", + "Tastes", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Enzymatic", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Sugar Browning", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Aromas-Dry Distillation", + "Tastes-Bitter", + "Tastes-Bitter", + "Tastes-Salt", + "Tastes-Salt", + "Tastes-Sweet", + "Tastes-Sweet", + "Tastes-Sour", + "Tastes-Sour", + "Enzymatic-Flowery", + "Enzymatic-Flowery", + "Enzymatic-Fruity", + "Enzymatic-Fruity", + "Enzymatic-Herby", + "Enzymatic-Herby", + "Sugar Browning-Nutty", + "Sugar Browning-Nutty", + "Sugar Browning-Carmelly", + "Sugar Browning-Carmelly", + "Sugar Browning-Chocolatey", + "Sugar Browning-Chocolatey", + "Dry Distillation-Resinous", + "Dry Distillation-Resinous", + "Dry Distillation-Spicy", + "Dry Distillation-Spicy", + "Dry Distillation-Carbony", + "Dry Distillation-Carbony", + "Bitter-Pungent", + "Bitter-Pungent", + "Bitter-Harsh", + "Bitter-Harsh", + "Salt-Sharp", + "Salt-Sharp", + "Salt-Bland", + "Salt-Bland", + "Sweet-Mellow", + "Sweet-Mellow", + "Sweet-Acidy", + "Sweet-Acidy", + "Sour-Winey", + "Sour-Winey", + "Sour-Soury", + "Sour-Soury", + "Flowery-Floral", + "Flowery-Floral", + "Flowery-Fragrant", + "Flowery-Fragrant", + "Fruity-Citrus", + "Fruity-Citrus", + "Fruity-Berry-like", + "Fruity-Berry-like", + "Herby-Alliaceous", + "Herby-Alliaceous", + "Herby-Leguminous", + "Herby-Leguminous", + "Nutty-Nut-like", + "Nutty-Nut-like", + "Nutty-Malt-like", + "Nutty-Malt-like", + "Carmelly-Candy-like", + "Carmelly-Candy-like", + "Carmelly-Syrup-like", + "Carmelly-Syrup-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Chocolate-like", + "Chocolatey-Vanilla-like", + "Chocolatey-Vanilla-like", + "Resinous-Turpeny", + "Resinous-Turpeny", + "Resinous-Medicinal", + "Resinous-Medicinal", + "Spicy-Warming", + "Spicy-Warming", + "Spicy-Pungent", + "Spicy-Pungent", + "Carbony-Smokey", + "Carbony-Smokey", + "Carbony-Ashy", + "Carbony-Ashy" + ] + } + ], + "layout": { + "margin": {"l": 0, "r": 0, "b": 0, "t": 0}, + "width": 500, + "height": 500, + "shapes": [ + { "type": "rect", "layer": "below", "x0": 0, "x1": 1, "y0": 0, "y1": 1 } + ] + } +} diff --git a/test/image/mocks/icicle_count_branches.json b/test/image/mocks/icicle_count_branches.json new file mode 100644 index 00000000000..10f9a122e0d --- /dev/null +++ b/test/image/mocks/icicle_count_branches.json @@ -0,0 +1,78 @@ +{ + "data": [ + { + "type": "icicle", + "count": "leaves+branches", + "textinfo": "label+percent parent", + "marker": { + "line": { + "color": "#777" + }, + "colorscale": "Blackbody", + "reversescale": true, + "showscale": true + }, + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ] + } + ], + "layout": { + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/icicle_first.json b/test/image/mocks/icicle_first.json new file mode 100644 index 00000000000..415f87ab6ec --- /dev/null +++ b/test/image/mocks/icicle_first.json @@ -0,0 +1,92 @@ +{ + "data": [ + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "textinfo": "label+percent parent", + "labels": [ + "Eve", + "Cain", + "Seth", + "Enos", + "Noam", + "Abel", + "Awan", + "Enoch", + "Azura" + ], + "parents": [ + "", + "Eve", + "Eve", + "Seth", + "Seth", + "Eve", + "Eve", + "Awan", + "Eve" + ], + "domain": { + "x": [ + 0, + 0.5 + ] + } + }, + { + "type": "icicle", + "pathbar": { + "visible": false + }, + "textinfo": "label+percent entry", + "labels": [ + "Cain", + "Seth", + "Enos", + "Noam", + "Abel", + "Awan", + "Enoch", + "Azura" + ], + "parents": [ + "Eve", + "Eve", + "Seth", + "Seth", + "Eve", + "Eve", + "Awan", + "Eve" + ], + "domain": { + "x": [ + 0.5, + 1 + ] + } + } + ], + "layout": { + "annotations": [ + { + "showarrow": false, + "text": "percent parent", + "x": 0.25, + "xanchor": "center", + "y": 1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "percent entry", + "x": 0.75, + "xanchor": "center", + "y": 1, + "yanchor": "bottom" + } + ] + } +} diff --git a/test/image/mocks/icicle_flare.json b/test/image/mocks/icicle_flare.json new file mode 100644 index 00000000000..54a778210fa --- /dev/null +++ b/test/image/mocks/icicle_flare.json @@ -0,0 +1,774 @@ +{ + "data": [ + { + "type": "icicle", + "hoverinfo": "all", + "ids": [ + "flare", + "flare-analytics", + "flare-animate", + "flare-data", + "flare-display", + "flare-flex", + "flare-physics", + "flare-query", + "flare-scale", + "flare-util", + "flare-vis", + "analytics-cluster", + "analytics-graph", + "analytics-optimization", + "animate-Easing", + "animate-FunctionSequence", + "animate-interpolate", + "animate-ISchedulable", + "animate-Parallel", + "animate-Pause", + "animate-Scheduler", + "animate-Sequence", + "animate-Transition", + "animate-Transitioner", + "animate-TransitionEvent", + "animate-Tween", + "data-converters", + "data-DataField", + "data-DataSchema", + "data-DataSet", + "data-DataSource", + "data-DataTable", + "data-DataUtil", + "display-DirtySprite", + "display-LineSprite", + "display-RectSprite", + "display-TextSprite", + "flex-FlareVis", + "physics-DragForce", + "physics-GravityForce", + "physics-IForce", + "physics-NBodyForce", + "physics-Particle", + "physics-Simulation", + "physics-Spring", + "physics-SpringForce", + "query-AggregateExpression", + "query-And", + "query-Arithmetic", + "query-Average", + "query-BinaryExpression", + "query-Comparison", + "query-CompositeExpression", + "query-Count", + "query-DateUtil", + "query-Distinct", + "query-Expression", + "query-ExpressionIterator", + "query-Fn", + "query-If", + "query-IsA", + "query-Literal", + "query-Match", + "query-Maximum", + "query-methods", + "query-Minimum", + "query-Not", + "query-Or", + "query-Query", + "query-Range", + "query-StringUtil", + "query-Sum", + "query-Variable", + "query-Variance", + "query-Xor", + "scale-IScaleMap", + "scale-LinearScale", + "scale-LogScale", + "scale-OrdinalScale", + "scale-QuantileScale", + "scale-QuantitativeScale", + "scale-RootScale", + "scale-Scale", + "scale-ScaleType", + "scale-TimeScale", + "util-Arrays", + "util-Colors", + "util-Dates", + "util-Displays", + "util-Filter", + "util-Geometry", + "util-heap", + "util-IEvaluable", + "util-IPredicate", + "util-IValueProxy", + "util-math", + "util-Maths", + "util-Orientation", + "util-palette", + "util-Property", + "util-Shapes", + "util-Sort", + "util-Stats", + "util-Strings", + "vis-axis", + "vis-controls", + "vis-data", + "vis-events", + "vis-legend", + "vis-operator", + "vis-Visualization", + "cluster-AgglomerativeCluster", + "cluster-CommunityStructure", + "cluster-HierarchicalCluster", + "cluster-MergeEdge", + "graph-BetweennessCentrality", + "graph-LinkDistance", + "graph-MaxFlowMinCut", + "graph-ShortestPaths", + "graph-SpanningTree", + "optimization-AspectRatioBanker", + "interpolate-ArrayInterpolator", + "interpolate-ColorInterpolator", + "interpolate-DateInterpolator", + "interpolate-Interpolator", + "interpolate-MatrixInterpolator", + "interpolate-NumberInterpolator", + "interpolate-ObjectInterpolator", + "interpolate-PointInterpolator", + "interpolate-RectangleInterpolator", + "converters-Converters", + "converters-DelimitedTextConverter", + "converters-GraphMLConverter", + "converters-IDataConverter", + "converters-JSONConverter", + "methods-add", + "methods-and", + "methods-average", + "methods-count", + "methods-distinct", + "methods-div", + "methods-eq", + "methods-fn", + "methods-gt", + "methods-gte", + "methods-iff", + "methods-isa", + "methods-lt", + "methods-lte", + "methods-max", + "methods-min", + "methods-mod", + "methods-mul", + "methods-neq", + "methods-not", + "methods-or", + "methods-orderby", + "methods-range", + "methods-select", + "methods-stddev", + "methods-sub", + "methods-sum", + "methods-update", + "methods-variance", + "methods-where", + "methods-xor", + "methods-_", + "heap-FibonacciHeap", + "heap-HeapNode", + "math-DenseMatrix", + "math-IMatrix", + "math-SparseMatrix", + "palette-ColorPalette", + "palette-Palette", + "palette-ShapePalette", + "palette-SizePalette", + "axis-Axes", + "axis-Axis", + "axis-AxisGridLine", + "axis-AxisLabel", + "axis-CartesianAxes", + "controls-AnchorControl", + "controls-ClickControl", + "controls-Control", + "controls-ControlList", + "controls-DragControl", + "controls-ExpandControl", + "controls-HoverControl", + "controls-IControl", + "controls-PanZoomControl", + "controls-SelectionControl", + "controls-TooltipControl", + "data-Data", + "data-DataList", + "data-DataSprite", + "data-EdgeSprite", + "data-NodeSprite", + "data-render", + "data-ScaleBinding", + "data-Tree", + "data-TreeBuilder", + "events-DataEvent", + "events-SelectionEvent", + "events-TooltipEvent", + "events-VisualizationEvent", + "legend-Legend", + "legend-LegendItem", + "legend-LegendRange", + "operator-distortion", + "operator-encoder", + "operator-filter", + "operator-IOperator", + "operator-label", + "operator-layout", + "operator-Operator", + "operator-OperatorList", + "operator-OperatorSequence", + "operator-OperatorSwitch", + "operator-SortOperator", + "render-ArrowType", + "render-EdgeRenderer", + "render-IRenderer", + "render-ShapeRenderer", + "distortion-BifocalDistortion", + "distortion-Distortion", + "distortion-FisheyeDistortion", + "encoder-ColorEncoder", + "encoder-Encoder", + "encoder-PropertyEncoder", + "encoder-ShapeEncoder", + "encoder-SizeEncoder", + "filter-FisheyeTreeFilter", + "filter-GraphDistanceFilter", + "filter-VisibilityFilter", + "label-Labeler", + "label-RadialLabeler", + "label-StackedAreaLabeler", + "layout-AxisLayout", + "layout-BundledEdgeRouter", + "layout-CircleLayout", + "layout-CirclePackingLayout", + "layout-DendrogramLayout", + "layout-ForceDirectedLayout", + "layout-IcicleTreeLayout", + "layout-IndentedTreeLayout", + "layout-Layout", + "layout-NodeLinkTreeLayout", + "layout-PieLayout", + "layout-RadialTreeLayout", + "layout-RandomLayout", + "layout-StackedAreaLayout", + "layout-TreeMapLayout" + ], + "labels": [ + "flare", + "analytics", + "animate", + "data", + "display", + "flex", + "physics", + "query", + "scale", + "util", + "vis", + "cluster", + "graph", + "optimization", + "Easing", + "FunctionSequence", + "interpolate", + "ISchedulable", + "Parallel", + "Pause", + "Scheduler", + "Sequence", + "Transition", + "Transitioner", + "TransitionEvent", + "Tween", + "converters", + "DataField", + "DataSchema", + "DataSet", + "DataSource", + "DataTable", + "DataUtil", + "DirtySprite", + "LineSprite", + "RectSprite", + "TextSprite", + "FlareVis", + "DragForce", + "GravityForce", + "IForce", + "NBodyForce", + "Particle", + "Simulation", + "Spring", + "SpringForce", + "AggregateExpression", + "And", + "Arithmetic", + "Average", + "BinaryExpression", + "Comparison", + "CompositeExpression", + "Count", + "DateUtil", + "Distinct", + "Expression", + "ExpressionIterator", + "Fn", + "If", + "IsA", + "Literal", + "Match", + "Maximum", + "methods", + "Minimum", + "Not", + "Or", + "Query", + "Range", + "StringUtil", + "Sum", + "Variable", + "Variance", + "Xor", + "IScaleMap", + "LinearScale", + "LogScale", + "OrdinalScale", + "QuantileScale", + "QuantitativeScale", + "RootScale", + "Scale", + "ScaleType", + "TimeScale", + "Arrays", + "Colors", + "Dates", + "Displays", + "Filter", + "Geometry", + "heap", + "IEvaluable", + "IPredicate", + "IValueProxy", + "math", + "Maths", + "Orientation", + "palette", + "Property", + "Shapes", + "Sort", + "Stats", + "Strings", + "axis", + "controls", + "data", + "events", + "legend", + "operator", + "Visualization", + "AgglomerativeCluster", + "CommunityStructure", + "HierarchicalCluster", + "MergeEdge", + "BetweennessCentrality", + "LinkDistance", + "MaxFlowMinCut", + "ShortestPaths", + "SpanningTree", + "AspectRatioBanker", + "ArrayInterpolator", + "ColorInterpolator", + "DateInterpolator", + "Interpolator", + "MatrixInterpolator", + "NumberInterpolator", + "ObjectInterpolator", + "PointInterpolator", + "RectangleInterpolator", + "Converters", + "DelimitedTextConverter", + "GraphMLConverter", + "IDataConverter", + "JSONConverter", + "add", + "and", + "average", + "count", + "distinct", + "div", + "eq", + "fn", + "gt", + "gte", + "iff", + "isa", + "lt", + "lte", + "max", + "min", + "mod", + "mul", + "neq", + "not", + "or", + "orderby", + "range", + "select", + "stddev", + "sub", + "sum", + "update", + "variance", + "where", + "xor", + "_", + "FibonacciHeap", + "HeapNode", + "DenseMatrix", + "IMatrix", + "SparseMatrix", + "ColorPalette", + "Palette", + "ShapePalette", + "SizePalette", + "Axes", + "Axis", + "AxisGridLine", + "AxisLabel", + "CartesianAxes", + "AnchorControl", + "ClickControl", + "Control", + "ControlList", + "DragControl", + "ExpandControl", + "HoverControl", + "IControl", + "PanZoomControl", + "SelectionControl", + "TooltipControl", + "Data", + "DataList", + "DataSprite", + "EdgeSprite", + "NodeSprite", + "render", + "ScaleBinding", + "Tree", + "TreeBuilder", + "DataEvent", + "SelectionEvent", + "TooltipEvent", + "VisualizationEvent", + "Legend", + "LegendItem", + "LegendRange", + "distortion", + "encoder", + "filter", + "IOperator", + "label", + "layout", + "Operator", + "OperatorList", + "OperatorSequence", + "OperatorSwitch", + "SortOperator", + "ArrowType", + "EdgeRenderer", + "IRenderer", + "ShapeRenderer", + "BifocalDistortion", + "Distortion", + "FisheyeDistortion", + "ColorEncoder", + "Encoder", + "PropertyEncoder", + "ShapeEncoder", + "SizeEncoder", + "FisheyeTreeFilter", + "GraphDistanceFilter", + "VisibilityFilter", + "Labeler", + "RadialLabeler", + "StackedAreaLabeler", + "AxisLayout", + "BundledEdgeRouter", + "CircleLayout", + "CirclePackingLayout", + "DendrogramLayout", + "ForceDirectedLayout", + "IcicleTreeLayout", + "IndentedTreeLayout", + "Layout", + "NodeLinkTreeLayout", + "PieLayout", + "RadialTreeLayout", + "RandomLayout", + "StackedAreaLayout", + "TreeMapLayout" + ], + "parents": [ + "", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare", + "flare-analytics", + "flare-analytics", + "flare-analytics", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-animate", + "flare-data", + "flare-data", + "flare-data", + "flare-data", + "flare-data", + "flare-data", + "flare-data", + "flare-display", + "flare-display", + "flare-display", + "flare-display", + "flare-flex", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-physics", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-query", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-scale", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-util", + "flare-vis", + "flare-vis", + "flare-vis", + "flare-vis", + "flare-vis", + "flare-vis", + "flare-vis", + "analytics-cluster", + "analytics-cluster", + "analytics-cluster", + "analytics-cluster", + "analytics-graph", + "analytics-graph", + "analytics-graph", + "analytics-graph", + "analytics-graph", + "analytics-optimization", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "animate-interpolate", + "data-converters", + "data-converters", + "data-converters", + "data-converters", + "data-converters", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "query-methods", + "util-heap", + "util-heap", + "util-math", + "util-math", + "util-math", + "util-palette", + "util-palette", + "util-palette", + "util-palette", + "vis-axis", + "vis-axis", + "vis-axis", + "vis-axis", + "vis-axis", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-controls", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-data", + "vis-events", + "vis-events", + "vis-events", + "vis-events", + "vis-legend", + "vis-legend", + "vis-legend", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "vis-operator", + "data-render", + "data-render", + "data-render", + "data-render", + "operator-distortion", + "operator-distortion", + "operator-distortion", + "operator-encoder", + "operator-encoder", + "operator-encoder", + "operator-encoder", + "operator-encoder", + "operator-filter", + "operator-filter", + "operator-filter", + "operator-label", + "operator-label", + "operator-label", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout", + "operator-layout" + ] + } + ], + "layout": { + "width": 600, + "height": 600 + } +} diff --git a/test/image/mocks/icicle_leaf-opacity-level.json b/test/image/mocks/icicle_leaf-opacity-level.json new file mode 100644 index 00000000000..b8f99331d78 --- /dev/null +++ b/test/image/mocks/icicle_leaf-opacity-level.json @@ -0,0 +1,84 @@ +{ + "data": [ + { + "type": "icicle", + "labels": ["Eve", "Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + "parents": ["", "Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ], + "domain": {"x": [0, 0.20], "y": [0, 0.9]}, + "root": {"color": "limegreen"}, + "level": "", + "leaf": { + "opacity": 1 + } + }, + { + "type": "icicle", + "labels": ["Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + "parents": ["Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ], + "domain": {"x": [0.25, 0.45], "y": [0, 0.9]}, + "root": {"color": "limegreen"}, + "level": "Seth", + "leaf": { + "opacity": 0.7 + } + }, + { + "type": "icicle", + "labels": ["Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + "parents": ["Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ], + "domain": {"x": [0.5, 0.70], "y": [0, 0.9]}, + "root": {"color": "limegreen"}, + "level": "Enos", + "leaf": { + "opacity": 0.3 + } + }, + { + "type": "icicle", + "labels": ["Cain", "Seth", "Enos", "Noam", "Abel", "Awan", "Enoch", "Azura"], + "parents": ["Eve", "Eve", "Seth", "Seth", "Eve", "Eve", "Awan", "Eve" ], + "domain": {"x": [0.75, 0.95], "y": [0, 0.9]}, + "root": {"color": "limegreen"}, + "level": "Enos", + "leaf": { + "opacity": 0.1 + } + } + ], + "layout": { + "annotations": [ + { + "showarrow": false, + "text": "opacity=0.1, level='Enos'", + "x": 0.75, + "xanchor": "left", + "y": 1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "opacity=0.3, level='Enos'", + "x": 0.5, + "xanchor": "left", + "y": 1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "opacity=0.7, level='Seth'", + "x": 0.25, + "xanchor": "left", + "y": 1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "opacity=1, level=''", + "x": 0.0, + "xanchor": "left", + "y": 1, + "yanchor": "bottom" + } + ] + } +} diff --git a/test/image/mocks/icicle_packages_colorscale_novalue.json b/test/image/mocks/icicle_packages_colorscale_novalue.json new file mode 100644 index 00000000000..38e85058555 --- /dev/null +++ b/test/image/mocks/icicle_packages_colorscale_novalue.json @@ -0,0 +1,2328 @@ +{ + "data": [ + { + "type": "icicle", + "maxdepth": 2, + "textinfo": "label+percent parent", + "marker": { + "line": { + "color": "#777" + }, + "colorscale": [[0, "#FFF"], [0.01, "#FF0"], [0.1, "#F00"], [1, "#000"]], + "showscale": true + }, + "ids": [ + "Plotly.js v1.46.x external module counter", + "1. @plotly/d3-sankey", + "1. alpha-shape", + "1. array-range", + "1. canvas-fit", + "1. color-normalize", + "1. convex-hull", + "1. country-regex", + "1. d3", + "1. d3-force", + "1. d3-hierarchy", + "1. d3-interpolate", + "1. d3-sankey-circular", + "1. delaunay-triangulate", + "1. es6-promise", + "1. fast-isnumeric", + "1. font-atlas-sdf", + "1. gl-cone3d", + "1. gl-contour2d", + "1. gl-error3d", + "1. gl-heatmap2d", + "1. gl-line3d", + "1. gl-mat4", + "1. gl-mesh3d", + "1. gl-plot2d", + "1. gl-plot3d", + "1. gl-pointcloud2d", + "1. gl-scatter3d", + "1. gl-select-box", + "1. gl-spikes2d", + "1. gl-streamtube3d", + "1. gl-surface3d", + "1. gl-text", + "1. glslify", + "1. has-hover", + "1. has-passive-events", + "1. mapbox-gl", + "1. matrix-camera-controller", + "1. mouse-change", + "1. mouse-event-offset", + "1. mouse-wheel", + "1. ndarray", + "1. ndarray-fill", + "1. ndarray-homography", + "1. point-cluster", + "1. polybooljs", + "1. regl", + "1. regl-error2d", + "1. regl-line2d", + "1. regl-scatter2d", + "1. regl-splom", + "1. right-now", + "1. robust-orientation", + "1. sane-topojson", + "1. strongly-connected-components", + "1. superscript-text", + "1. svg-path-sdf", + "1. tinycolor2", + "1. topojson-client", + "1. webgl-context", + "1. world-calendars", + "2. 3d-view", + "2. @mapbox/gl-matrix", + "2. @mapbox/jsonlint-lines-primitives", + "2. @mapbox/mapbox-gl-supported", + "2. @mapbox/point-geometry", + "2. @mapbox/shelf-pack", + "2. @mapbox/tiny-sdf", + "2. @mapbox/unitbezier", + "2. @mapbox/vector-tile", + "2. @mapbox/whoots-js", + "2. a-big-triangle", + "2. affine-hull", + "2. alpha-complex", + "2. array-bounds", + "2. array-normalize", + "2. array-range", + "2. array-rearrange", + "2. barycentric", + "2. binary-search-bounds", + "2. bit-twiddle", + "2. bitmap-sdf", + "2. bl", + "2. brfs", + "2. bubleify", + "2. cdt2d", + "2. clamp", + "2. clean-pslg", + "2. color-alpha", + "2. color-id", + "2. color-normalize", + "2. color-rgba", + "2. colormap", + "2. commander", + "2. concat-stream", + "2. css-font", + "2. csscolorparser", + "2. cwise", + "2. d3-array", + "2. d3-collection", + "2. d3-color", + "2. d3-dispatch", + "2. d3-quadtree", + "2. d3-shape", + "2. d3-timer", + "2. defined", + "2. detect-kerning", + "2. draw-svg-path", + "2. dtype", + "2. dup", + "2. duplexify", + "2. earcut", + "2. element-size", + "2. elementary-circuits-directed-graph", + "2. es6-weak-map", + "2. falafel", + "2. flatten-vertex-data", + "2. font-atlas", + "2. font-measure", + "2. from2", + "2. geojson-rewind", + "2. geojson-vt", + "2. get-canvas-context", + "2. gl-axes3d", + "2. gl-buffer", + "2. gl-fbo", + "2. gl-mat4", + "2. gl-matrix-invert", + "2. gl-select-static", + "2. gl-shader", + "2. gl-spikes3d", + "2. gl-texture2d", + "2. gl-util", + "2. gl-vao", + "2. gl-vec3", + "2. glsl-inverse", + "2. glsl-out-of-range", + "2. glsl-read-float", + "2. glsl-resolve", + "2. glsl-specular-beckmann", + "2. glsl-specular-cook-torrance", + "2. glsl-token-whitespace-trim", + "2. glslify", + "2. glslify-bundle", + "2. glslify-deps", + "2. gray-matter", + "2. grid-index", + "2. has-passive-events", + "2. image-palette", + "2. incremental-convex-hull", + "2. iota-array", + "2. is-browser", + "2. is-buffer", + "2. is-iexplorer", + "2. is-mobile", + "2. is-obj", + "2. is-plain-obj", + "2. is-string-blank", + "2. is-svg-path", + "2. left-pad", + "2. mat4-interpolate", + "2. math-log2", + "2. minimist", + "2. monotone-convex-hull-2d", + "2. mouse-change", + "2. mouse-event", + "2. mouse-event-offset", + "2. mouse-wheel", + "2. ndarray", + "2. ndarray-gradient", + "2. ndarray-ops", + "2. ndarray-pack", + "2. ndarray-scratch", + "2. ndarray-warp", + "2. normals", + "2. object-assign", + "2. optical-properties", + "2. parse-rect", + "2. parse-svg-path", + "2. parse-unit", + "2. pbf", + "2. pick-by-alias", + "2. point-cluster", + "2. polytope-closest-point", + "2. quickselect", + "2. raf", + "2. regl", + "2. regl-scatter2d", + "2. resolve", + "2. right-now", + "2. robust-scale", + "2. robust-subtract", + "2. robust-sum", + "2. rw", + "2. shuffle-seed", + "2. signum", + "2. simplicial-complex-boundary", + "2. simplicial-complex-contour", + "2. sort-object", + "2. stack-trace", + "2. static-eval", + "2. supercluster", + "2. surface-nets", + "2. svg-path-bounds", + "2. text-cache", + "2. through2", + "2. tiny-sdf", + "2. tinyqueue", + "2. to-float32", + "2. to-px", + "2. two-product", + "2. typedarray-pool", + "2. uniq", + "2. update-diff", + "2. vectorize-text", + "2. vt-pbf", + "2. xtend", + "3. @choojs/findup", + "3. @mapbox/geojson-area", + "3. @mapbox/point-geometry", + "3. @mapbox/vector-tile", + "3. abs-svg-path", + "3. acorn", + "3. array-bounds", + "3. big-rat", + "3. binary-search-bounds", + "3. bit-twiddle", + "3. boundary-cells", + "3. box-intersect", + "3. buble", + "3. buffer-from", + "3. cdt2d", + "3. circumradius", + "3. clamp", + "3. clean-pslg", + "3. color-id", + "3. color-parse", + "3. color-space", + "3. concat-stream", + "3. css-font", + "3. css-font-size-keywords", + "3. css-font-stretch-keywords", + "3. css-font-style-keywords", + "3. css-font-weight-keywords", + "3. css-global-keywords", + "3. css-system-font-keywords", + "3. cwise", + "3. cwise-compiler", + "3. cwise-parser", + "3. d", + "3. d3-path", + "3. delaunay-triangulate", + "3. dtype", + "3. dup", + "3. end-of-stream", + "3. es5-ext", + "3. es6-iterator", + "3. es6-symbol", + "3. es6-weak-map", + "3. escodegen", + "3. events", + "3. extend-shallow", + "3. extract-frustum-planes", + "3. foreach", + "3. gl-buffer", + "3. gl-fbo", + "3. gl-format-compiler-error", + "3. gl-mat2", + "3. gl-mat3", + "3. gl-mat4", + "3. gl-shader", + "3. gl-state", + "3. gl-texture2d", + "3. gl-vao", + "3. gl-vec3", + "3. gl-vec4", + "3. glsl-inject-defines", + "3. glsl-resolve", + "3. glsl-specular-beckmann", + "3. glsl-token-defines", + "3. glsl-token-depth", + "3. glsl-token-descope", + "3. glsl-token-scope", + "3. glsl-token-string", + "3. glsl-token-whitespace-trim", + "3. glsl-tokenizer", + "3. glslify", + "3. graceful-fs", + "3. ieee754", + "3. inherits", + "3. is-browser", + "3. is-firefox", + "3. is-plain-obj", + "3. is-svg-path", + "3. isarray", + "3. js-yaml", + "3. kdbush", + "3. kind-of", + "3. lerp", + "3. map-limit", + "3. marching-simplex-table", + "3. mat4-decompose", + "3. mat4-recompose", + "3. matrix-camera-controller", + "3. minimist", + "3. murmurhash-js", + "3. ndarray", + "3. ndarray-extract-contour", + "3. ndarray-linear-interpolate", + "3. ndarray-ops", + "3. ndarray-sort", + "3. nextafter", + "3. normalize-svg-path", + "3. number-is-integer", + "3. numeric", + "3. object-assign", + "3. object-keys", + "3. orbit-camera-controller", + "3. parse-svg-path", + "3. parse-unit", + "3. path-parse", + "3. pbf", + "3. performance-now", + "3. pick-by-alias", + "3. planar-graph-to-polyline", + "3. pxls", + "3. quantize", + "3. quat-slerp", + "3. quote-stream", + "3. rat-vec", + "3. readable-stream", + "3. reduce-simplicial-complex", + "3. resolve", + "3. resolve-protobuf-schema", + "3. robust-in-sphere", + "3. robust-linear-solve", + "3. robust-orientation", + "3. robust-segment-intersect", + "3. safe-buffer", + "3. seedrandom", + "3. shallow-copy", + "3. sharkdown", + "3. simplicial-complex", + "3. simplify-planar-graph", + "3. sort-asc", + "3. sort-desc", + "3. split-polygon", + "3. static-module", + "3. stream-shift", + "3. string-split-by", + "3. strip-bom-string", + "3. strongly-connected-components", + "3. surface-nets", + "3. through2", + "3. triangulate-hypercube", + "3. triangulate-polyline", + "3. turntable-camera-controller", + "3. two-product", + "3. two-sum", + "3. typedarray", + "3. typedarray-pool", + "3. uglify-js", + "3. union-find", + "3. uniq", + "3. unquote", + "3. vectorize-text", + "3. weak-map", + "3. weakmap-shim", + "3. xtend", + "3. zero-crossings", + "4. acorn", + "4. acorn-dynamic-import", + "4. acorn-jsx", + "4. add-line-numbers", + "4. argparse", + "4. arr-flatten", + "4. big-rat", + "4. bit-twiddle", + "4. bn.js", + "4. buffer-equal", + "4. cardinal", + "4. cdt2d", + "4. cell-orientation", + "4. chalk", + "4. circumcenter", + "4. color-name", + "4. commander", + "4. compare-cell", + "4. compare-oriented-cell", + "4. compute-dims", + "4. concat-stream", + "4. convert-source-map", + "4. convex-hull", + "4. core-util-is", + "4. cwise-compiler", + "4. d", + "4. defined", + "4. double-bits", + "4. duplexer2", + "4. edges-to-adjacency-list", + "4. es5-ext", + "4. es6-iterator", + "4. es6-symbol", + "4. escodegen", + "4. esprima", + "4. estraverse", + "4. esutils", + "4. expect.js", + "4. falafel", + "4. filtered-vector", + "4. flip-pixels", + "4. gamma", + "4. gl-constants", + "4. gl-mat4", + "4. gl-quat", + "4. gl-vec3", + "4. glsl-shader-name", + "4. glsl-token-assignments", + "4. glsl-token-depth", + "4. glsl-token-inject-block", + "4. glsl-token-properties", + "4. glsl-token-scope", + "4. glsl-token-string", + "4. glsl-tokenizer", + "4. has", + "4. hsluv", + "4. inherits", + "4. is-browser", + "4. is-buffer", + "4. is-extendable", + "4. is-finite", + "4. is-plain-obj", + "4. isarray", + "4. magic-string", + "4. merge-source-map", + "4. minimist", + "4. mumath", + "4. next-tick", + "4. object-inspect", + "4. once", + "4. optionator", + "4. os-homedir", + "4. parenthesis", + "4. permutation-parity", + "4. permutation-rank", + "4. planar-dual", + "4. point-in-big-polygon", + "4. process-nextick-args", + "4. protocol-buffers-schema", + "4. quote-stream", + "4. readable-stream", + "4. robust-determinant", + "4. robust-dot-product", + "4. robust-orientation", + "4. robust-scale", + "4. robust-subtract", + "4. robust-sum", + "4. safe-buffer", + "4. shallow-copy", + "4. simplicial-complex", + "4. source-map", + "4. split", + "4. sprintf-js", + "4. static-eval", + "4. stream-spigot", + "4. string_decoder", + "4. svg-arc-to-cubic-bezier", + "4. tape", + "4. through", + "4. through2", + "4. to-uint8", + "4. two-product", + "4. typedarray-pool", + "4. uglify-to-browserify", + "4. union-find", + "4. uniq", + "4. util-deprecate", + "4. vlq", + "4. wgs84", + "4. yargs", + "5. acorn", + "5. almost-equal", + "5. ansi-styles", + "5. ansicolors", + "5. arr-flatten", + "5. atob-lite", + "5. binary-search-bounds", + "5. bit-twiddle", + "5. camelcase", + "5. cell-orientation", + "5. clamp", + "5. cliui", + "5. compare-angle", + "5. compare-cell", + "5. core-util-is", + "5. cubic-hermite", + "5. decamelize", + "5. deep-equal", + "5. deep-is", + "5. defined", + "5. dup", + "5. escape-string-regexp", + "5. escodegen", + "5. esprima", + "5. estraverse", + "5. esutils", + "5. fast-levenshtein", + "5. for-each", + "5. function-bind", + "5. gl-mat3", + "5. gl-vec3", + "5. gl-vec4", + "5. glob", + "5. glsl-tokenizer", + "5. has", + "5. inherits", + "5. interval-tree-1d", + "5. invert-permutation", + "5. is-base64", + "5. is-float-array", + "5. isarray", + "5. levn", + "5. minimist", + "5. number-is-nan", + "5. object-inspect", + "5. optionator", + "5. pad-left", + "5. prelude-ls", + "5. readable-stream", + "5. redeyed", + "5. resolve", + "5. resumer", + "5. robust-compress", + "5. robust-linear-solve", + "5. robust-orientation", + "5. robust-scale", + "5. robust-sum", + "5. safe-buffer", + "5. slab-decomposition", + "5. source-map", + "5. sprintf-js", + "5. string.prototype.trim", + "5. string_decoder", + "5. supports-color", + "5. through", + "5. through2", + "5. to-array-buffer", + "5. two-product", + "5. type-check", + "5. typedarray-pool", + "5. union-find", + "5. uniq", + "5. utils-copy", + "5. validate.io-array", + "5. validate.io-matrix-like", + "5. validate.io-ndarray-like", + "5. validate.io-positive-integer", + "5. vlq", + "5. window-size", + "5. wordwrap", + "5. wrappy", + "5. xtend", + "6. amdefine", + "6. binary-search-bounds", + "6. center-align", + "6. color-convert", + "6. const-pinf-float64", + "6. core-util-is", + "6. define-properties", + "6. es-abstract", + "6. esprima", + "6. estraverse", + "6. flatten-vertex-data", + "6. fs.realpath", + "6. function-bind", + "6. functional-red-black-tree", + "6. has-flag", + "6. inflight", + "6. inherits", + "6. is-blob", + "6. is-callable", + "6. isarray", + "6. minimatch", + "6. object-keys", + "6. once", + "6. path-is-absolute", + "6. prelude-ls", + "6. readable-stream", + "6. repeat-string", + "6. right-align", + "6. robust-orientation", + "6. robust-product", + "6. robust-sum", + "6. signum", + "6. source-map", + "6. string-to-arraybuffer", + "6. string_decoder", + "6. through", + "6. two-sum", + "6. type-check", + "6. type-name", + "6. utils-copy-error", + "6. utils-indexof", + "6. utils-regex-from-string", + "6. validate.io-array", + "6. validate.io-buffer", + "6. validate.io-integer", + "6. validate.io-nonnegative-integer", + "6. wordwrap", + "6. xtend" + ], + "parents": [ + "", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "Plotly.js v1.46.x external module counter", + "1. @plotly/d3-sankey", + "1. @plotly/d3-sankey", + "1. @plotly/d3-sankey", + "1. alpha-shape", + "1. alpha-shape", + "1. canvas-fit", + "1. color-normalize", + "1. color-normalize", + "1. color-normalize", + "1. convex-hull", + "1. convex-hull", + "1. convex-hull", + "1. d3-force", + "1. d3-force", + "1. d3-force", + "1. d3-force", + "1. d3-interpolate", + "1. d3-sankey-circular", + "1. d3-sankey-circular", + "1. d3-sankey-circular", + "1. d3-sankey-circular", + "1. delaunay-triangulate", + "1. delaunay-triangulate", + "1. fast-isnumeric", + "1. font-atlas-sdf", + "1. font-atlas-sdf", + "1. gl-cone3d", + "1. gl-cone3d", + "1. gl-cone3d", + "1. gl-cone3d", + "1. gl-cone3d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-contour2d", + "1. gl-error3d", + "1. gl-error3d", + "1. gl-error3d", + "1. gl-error3d", + "1. gl-error3d", + "1. gl-heatmap2d", + "1. gl-heatmap2d", + "1. gl-heatmap2d", + "1. gl-heatmap2d", + "1. gl-heatmap2d", + "1. gl-heatmap2d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-line3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-mesh3d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot2d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-plot3d", + "1. gl-pointcloud2d", + "1. gl-pointcloud2d", + "1. gl-pointcloud2d", + "1. gl-pointcloud2d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-scatter3d", + "1. gl-select-box", + "1. gl-select-box", + "1. gl-select-box", + "1. gl-streamtube3d", + "1. gl-streamtube3d", + "1. gl-streamtube3d", + "1. gl-streamtube3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-surface3d", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. gl-text", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. glslify", + "1. has-hover", + "1. has-passive-events", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. mapbox-gl", + "1. matrix-camera-controller", + "1. matrix-camera-controller", + "1. matrix-camera-controller", + "1. matrix-camera-controller", + "1. mouse-change", + "1. mouse-wheel", + "1. mouse-wheel", + "1. mouse-wheel", + "1. ndarray", + "1. ndarray", + "1. ndarray-fill", + "1. ndarray-homography", + "1. ndarray-homography", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. point-cluster", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-error2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-line2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-scatter2d", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. regl-splom", + "1. robust-orientation", + "1. robust-orientation", + "1. robust-orientation", + "1. robust-orientation", + "1. svg-path-sdf", + "1. svg-path-sdf", + "1. svg-path-sdf", + "1. svg-path-sdf", + "1. svg-path-sdf", + "1. topojson-client", + "1. webgl-context", + "1. world-calendars", + "2. d3-shape", + "2. alpha-complex", + "2. alpha-complex", + "2. simplicial-complex-boundary", + "2. simplicial-complex-boundary", + "2. color-rgba", + "2. color-rgba", + "2. color-rgba", + "2. affine-hull", + "2. incremental-convex-hull", + "2. incremental-convex-hull", + "2. monotone-convex-hull-2d", + "2. elementary-circuits-directed-graph", + "2. gl-shader", + "2. gl-shader", + "2. cdt2d", + "2. cdt2d", + "2. cdt2d", + "2. clean-pslg", + "2. clean-pslg", + "2. clean-pslg", + "2. clean-pslg", + "2. clean-pslg", + "2. clean-pslg", + "2. clean-pslg", + "2. gl-buffer", + "2. gl-buffer", + "2. gl-buffer", + "2. surface-nets", + "2. surface-nets", + "2. surface-nets", + "2. typedarray-pool", + "2. typedarray-pool", + "2. gl-texture2d", + "2. gl-texture2d", + "2. gl-texture2d", + "2. barycentric", + "2. colormap", + "2. glsl-specular-cook-torrance", + "2. polytope-closest-point", + "2. simplicial-complex-contour", + "2. simplicial-complex-contour", + "2. simplicial-complex-contour", + "2. simplicial-complex-contour", + "2. gl-select-static", + "2. gl-select-static", + "2. gl-select-static", + "2. gl-select-static", + "2. gl-select-static", + "2. text-cache", + "2. 3d-view", + "2. 3d-view", + "2. 3d-view", + "2. a-big-triangle", + "2. a-big-triangle", + "2. a-big-triangle", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-axes3d", + "2. gl-fbo", + "2. gl-spikes3d", + "2. gl-spikes3d", + "2. gl-spikes3d", + "2. gl-spikes3d", + "2. vectorize-text", + "2. vectorize-text", + "2. vectorize-text", + "2. vectorize-text", + "2. vectorize-text", + "2. vectorize-text", + "2. vectorize-text", + "2. ndarray-gradient", + "2. ndarray-gradient", + "2. ndarray-ops", + "2. ndarray-pack", + "2. ndarray-pack", + "2. ndarray-scratch", + "2. ndarray-scratch", + "2. ndarray-scratch", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. css-font", + "2. es6-weak-map", + "2. es6-weak-map", + "2. es6-weak-map", + "2. es6-weak-map", + "2. flatten-vertex-data", + "2. font-atlas", + "2. font-measure", + "2. gl-util", + "2. gl-util", + "2. gl-util", + "2. gl-util", + "2. gl-util", + "2. gl-util", + "2. gl-util", + "2. parse-rect", + "2. to-px", + "2. bl", + "2. bl", + "2. concat-stream", + "2. concat-stream", + "2. concat-stream", + "2. concat-stream", + "2. duplexify", + "2. duplexify", + "2. duplexify", + "2. duplexify", + "2. falafel", + "2. falafel", + "2. falafel", + "2. falafel", + "2. from2", + "2. from2", + "2. glsl-resolve", + "2. glsl-resolve", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-bundle", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. glslify-deps", + "2. through2", + "2. through2", + "2. resolve", + "2. static-eval", + "2. @mapbox/vector-tile", + "2. geojson-rewind", + "2. geojson-rewind", + "2. geojson-rewind", + "2. geojson-rewind", + "2. gray-matter", + "2. gray-matter", + "2. gray-matter", + "2. gray-matter", + "2. brfs", + "2. brfs", + "2. brfs", + "2. brfs", + "2. through2", + "2. through2", + "2. pbf", + "2. pbf", + "2. shuffle-seed", + "2. sort-object", + "2. sort-object", + "2. supercluster", + "2. vt-pbf", + "2. vt-pbf", + "2. vt-pbf", + "2. mat4-interpolate", + "2. mat4-interpolate", + "2. mat4-interpolate", + "2. mat4-interpolate", + "2. mat4-interpolate", + "2. cwise", + "2. cwise", + "2. cwise", + "2. cwise", + "2. gl-matrix-invert", + "2. gl-matrix-invert", + "2. gl-matrix-invert", + "2. ndarray-warp", + "2. ndarray-warp", + "2. array-normalize", + "2. bubleify", + "2. color-id", + "2. image-palette", + "2. image-palette", + "2. image-palette", + "2. color-alpha", + "2. raf", + "2. robust-scale", + "2. robust-scale", + "2. bitmap-sdf", + "2. draw-svg-path", + "2. draw-svg-path", + "2. svg-path-bounds", + "2. svg-path-bounds", + "2. svg-path-bounds", + "2. svg-path-bounds", + "3. circumradius", + "3. boundary-cells", + "3. reduce-simplicial-complex", + "3. reduce-simplicial-complex", + "3. reduce-simplicial-complex", + "3. color-parse", + "3. color-parse", + "3. color-parse", + "3. color-space", + "3. color-space", + "3. simplicial-complex", + "3. simplicial-complex", + "3. gl-format-compiler-error", + "3. gl-format-compiler-error", + "3. gl-format-compiler-error", + "3. gl-format-compiler-error", + "3. robust-in-sphere", + "3. robust-in-sphere", + "3. robust-in-sphere", + "3. robust-in-sphere", + "3. big-rat", + "3. big-rat", + "3. big-rat", + "3. box-intersect", + "3. box-intersect", + "3. nextafter", + "3. rat-vec", + "3. robust-segment-intersect", + "3. ndarray-extract-contour", + "3. triangulate-hypercube", + "3. triangulate-hypercube", + "3. triangulate-hypercube", + "3. zero-crossings", + "3. robust-linear-solve", + "3. marching-simplex-table", + "3. ndarray-sort", + "3. orbit-camera-controller", + "3. orbit-camera-controller", + "3. turntable-camera-controller", + "3. turntable-camera-controller", + "3. turntable-camera-controller", + "3. gl-state", + "3. split-polygon", + "3. split-polygon", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. planar-graph-to-polyline", + "3. simplify-planar-graph", + "3. simplify-planar-graph", + "3. triangulate-polyline", + "3. cwise-compiler", + "3. string-split-by", + "3. d", + "3. es5-ext", + "3. es5-ext", + "3. es5-ext", + "3. es6-iterator", + "3. es6-iterator", + "3. es6-iterator", + "3. es6-symbol", + "3. es6-symbol", + "3. number-is-integer", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. end-of-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. glsl-inject-defines", + "3. glsl-inject-defines", + "3. glsl-inject-defines", + "3. glsl-token-defines", + "3. glsl-token-descope", + "3. glsl-token-descope", + "3. glsl-token-descope", + "3. glsl-token-descope", + "3. glsl-tokenizer", + "3. @choojs/findup", + "3. map-limit", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. escodegen", + "3. escodegen", + "3. escodegen", + "3. escodegen", + "3. escodegen", + "3. @mapbox/geojson-area", + "3. sharkdown", + "3. sharkdown", + "3. sharkdown", + "3. sharkdown", + "3. sharkdown", + "3. sharkdown", + "3. extend-shallow", + "3. js-yaml", + "3. js-yaml", + "3. quote-stream", + "3. quote-stream", + "3. quote-stream", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. readable-stream", + "3. resolve-protobuf-schema", + "3. mat4-decompose", + "3. mat4-decompose", + "3. mat4-recompose", + "3. quat-slerp", + "3. cwise-parser", + "3. cwise-parser", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. static-module", + "3. uglify-js", + "3. uglify-js", + "3. uglify-js", + "3. buble", + "3. buble", + "3. buble", + "3. buble", + "3. buble", + "3. buble", + "3. buble", + "3. buble", + "3. pxls", + "3. pxls", + "3. pxls", + "3. pxls", + "3. pxls", + "3. pxls", + "3. normalize-svg-path", + "4. circumcenter", + "4. circumcenter", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. tape", + "4. compare-oriented-cell", + "4. compare-oriented-cell", + "4. mumath", + "4. add-line-numbers", + "4. glsl-shader-name", + "4. glsl-shader-name", + "4. permutation-parity", + "4. permutation-rank", + "4. permutation-rank", + "4. robust-determinant", + "4. robust-determinant", + "4. robust-determinant", + "4. robust-determinant", + "4. filtered-vector", + "4. filtered-vector", + "4. robust-dot-product", + "4. robust-dot-product", + "4. edges-to-adjacency-list", + "4. planar-dual", + "4. planar-dual", + "4. point-in-big-polygon", + "4. point-in-big-polygon", + "4. point-in-big-polygon", + "4. point-in-big-polygon", + "4. simplicial-complex", + "4. simplicial-complex", + "4. is-finite", + "4. string_decoder", + "4. string_decoder", + "4. string_decoder", + "4. once", + "4. string_decoder", + "4. through2", + "4. through2", + "4. once", + "4. string_decoder", + "4. optionator", + "4. optionator", + "4. optionator", + "4. optionator", + "4. optionator", + "4. optionator", + "4. cardinal", + "4. cardinal", + "4. split", + "4. stream-spigot", + "4. argparse", + "4. has", + "4. magic-string", + "4. duplexer2", + "4. escodegen", + "4. escodegen", + "4. escodegen", + "4. escodegen", + "4. escodegen", + "4. merge-source-map", + "4. string_decoder", + "4. gl-quat", + "4. gl-quat", + "4. gl-quat", + "4. duplexer2", + "4. quote-stream", + "4. quote-stream", + "4. readable-stream", + "4. readable-stream", + "4. readable-stream", + "4. readable-stream", + "4. escodegen", + "4. escodegen", + "4. escodegen", + "4. escodegen", + "4. static-eval", + "4. through2", + "4. through2", + "4. yargs", + "4. yargs", + "4. yargs", + "4. yargs", + "4. acorn-dynamic-import", + "4. acorn-jsx", + "4. chalk", + "4. chalk", + "4. chalk", + "4. compute-dims", + "4. compute-dims", + "4. compute-dims", + "4. compute-dims", + "4. compute-dims", + "4. to-uint8", + "4. to-uint8", + "4. to-uint8", + "4. to-uint8", + "4. to-uint8" + ], + "labels": [ + "Plotly.js v1.46.x external module counter", + "1. @plotly/d3-sankey", + "1. alpha-shape", + "1. array-range", + "1. canvas-fit", + "1. color-normalize", + "1. convex-hull", + "1. country-regex", + "1. d3", + "1. d3-force", + "1. d3-hierarchy", + "1. d3-interpolate", + "1. d3-sankey-circular", + "1. delaunay-triangulate", + "1. es6-promise", + "1. fast-isnumeric", + "1. font-atlas-sdf", + "1. gl-cone3d", + "1. gl-contour2d", + "1. gl-error3d", + "1. gl-heatmap2d", + "1. gl-line3d", + "1. gl-mat4", + "1. gl-mesh3d", + "1. gl-plot2d", + "1. gl-plot3d", + "1. gl-pointcloud2d", + "1. gl-scatter3d", + "1. gl-select-box", + "1. gl-spikes2d", + "1. gl-streamtube3d", + "1. gl-surface3d", + "1. gl-text", + "1. glslify", + "1. has-hover", + "1. has-passive-events", + "1. mapbox-gl", + "1. matrix-camera-controller", + "1. mouse-change", + "1. mouse-event-offset", + "1. mouse-wheel", + "1. ndarray", + "1. ndarray-fill", + "1. ndarray-homography", + "1. point-cluster", + "1. polybooljs", + "1. regl", + "1. regl-error2d", + "1. regl-line2d", + "1. regl-scatter2d", + "1. regl-splom", + "1. right-now", + "1. robust-orientation", + "1. sane-topojson", + "1. strongly-connected-components", + "1. superscript-text", + "1. svg-path-sdf", + "1. tinycolor2", + "1. topojson-client", + "1. webgl-context", + "1. world-calendars", + "2. d3-array", + "2. d3-collection", + "2. d3-shape", + "2. alpha-complex", + "2. simplicial-complex-boundary", + "2. element-size", + "2. clamp", + "2. color-rgba", + "2. dtype", + "2. affine-hull", + "2. incremental-convex-hull", + "2. monotone-convex-hull-2d", + "2. d3-collection", + "2. d3-dispatch", + "2. d3-quadtree", + "2. d3-timer", + "2. d3-color", + "2. d3-array", + "2. d3-collection", + "2. d3-shape", + "2. elementary-circuits-directed-graph", + "2. incremental-convex-hull", + "2. uniq", + "2. is-string-blank", + "2. optical-properties", + "2. tiny-sdf", + "2. gl-shader", + "2. gl-vec3", + "2. glsl-inverse", + "2. glsl-out-of-range", + "2. glslify", + "2. cdt2d", + "2. clean-pslg", + "2. gl-buffer", + "2. binary-search-bounds", + "2. gl-shader", + "2. glslify", + "2. iota-array", + "2. ndarray", + "2. surface-nets", + "2. gl-buffer", + "2. gl-shader", + "2. gl-vao", + "2. glsl-out-of-range", + "2. glslify", + "2. gl-buffer", + "2. binary-search-bounds", + "2. gl-shader", + "2. glslify", + "2. iota-array", + "2. typedarray-pool", + "2. gl-buffer", + "2. binary-search-bounds", + "2. gl-shader", + "2. gl-texture2d", + "2. gl-vao", + "2. glsl-out-of-range", + "2. glsl-read-float", + "2. glslify", + "2. ndarray", + "2. barycentric", + "2. colormap", + "2. gl-buffer", + "2. gl-mat4", + "2. gl-shader", + "2. gl-texture2d", + "2. gl-vao", + "2. glsl-out-of-range", + "2. glsl-specular-cook-torrance", + "2. glslify", + "2. ndarray", + "2. normals", + "2. polytope-closest-point", + "2. simplicial-complex-contour", + "2. typedarray-pool", + "2. gl-buffer", + "2. binary-search-bounds", + "2. gl-select-static", + "2. gl-shader", + "2. glsl-inverse", + "2. glslify", + "2. text-cache", + "2. 3d-view", + "2. a-big-triangle", + "2. gl-axes3d", + "2. gl-fbo", + "2. gl-mat4", + "2. gl-select-static", + "2. gl-shader", + "2. gl-spikes3d", + "2. glslify", + "2. has-passive-events", + "2. is-mobile", + "2. mouse-change", + "2. mouse-event-offset", + "2. mouse-wheel", + "2. ndarray", + "2. right-now", + "2. gl-buffer", + "2. gl-shader", + "2. glslify", + "2. typedarray-pool", + "2. gl-buffer", + "2. gl-mat4", + "2. gl-shader", + "2. gl-vao", + "2. glsl-out-of-range", + "2. glslify", + "2. is-string-blank", + "2. typedarray-pool", + "2. vectorize-text", + "2. gl-buffer", + "2. gl-shader", + "2. glslify", + "2. gl-vec3", + "2. glsl-inverse", + "2. glsl-out-of-range", + "2. glslify", + "2. bit-twiddle", + "2. colormap", + "2. dup", + "2. gl-buffer", + "2. gl-mat4", + "2. gl-shader", + "2. binary-search-bounds", + "2. gl-texture2d", + "2. gl-vao", + "2. glsl-out-of-range", + "2. glsl-specular-beckmann", + "2. glslify", + "2. ndarray", + "2. ndarray-gradient", + "2. ndarray-ops", + "2. ndarray-pack", + "2. ndarray-scratch", + "2. surface-nets", + "2. typedarray-pool", + "2. bit-twiddle", + "2. color-normalize", + "2. css-font", + "2. detect-kerning", + "2. es6-weak-map", + "2. flatten-vertex-data", + "2. font-atlas", + "2. font-measure", + "2. gl-util", + "2. is-plain-obj", + "2. object-assign", + "2. parse-rect", + "2. parse-unit", + "2. pick-by-alias", + "2. regl", + "2. to-px", + "2. typedarray-pool", + "2. bl", + "2. concat-stream", + "2. duplexify", + "2. falafel", + "2. from2", + "2. glsl-resolve", + "2. glsl-token-whitespace-trim", + "2. glslify-bundle", + "2. glslify-deps", + "2. through2", + "2. minimist", + "2. resolve", + "2. stack-trace", + "2. static-eval", + "2. xtend", + "2. is-browser", + "2. is-browser", + "2. @mapbox/gl-matrix", + "2. @mapbox/jsonlint-lines-primitives", + "2. @mapbox/mapbox-gl-supported", + "2. @mapbox/point-geometry", + "2. @mapbox/shelf-pack", + "2. @mapbox/tiny-sdf", + "2. @mapbox/unitbezier", + "2. @mapbox/vector-tile", + "2. @mapbox/whoots-js", + "2. csscolorparser", + "2. earcut", + "2. geojson-rewind", + "2. geojson-vt", + "2. gray-matter", + "2. grid-index", + "2. brfs", + "2. minimist", + "2. through2", + "2. pbf", + "2. quickselect", + "2. rw", + "2. shuffle-seed", + "2. sort-object", + "2. supercluster", + "2. tinyqueue", + "2. vt-pbf", + "2. binary-search-bounds", + "2. gl-mat4", + "2. gl-vec3", + "2. mat4-interpolate", + "2. mouse-event", + "2. signum", + "2. right-now", + "2. to-px", + "2. iota-array", + "2. is-buffer", + "2. cwise", + "2. gl-matrix-invert", + "2. ndarray-warp", + "2. array-bounds", + "2. array-normalize", + "2. bubleify", + "2. clamp", + "2. dtype", + "2. flatten-vertex-data", + "2. is-obj", + "2. math-log2", + "2. parse-rect", + "2. binary-search-bounds", + "2. array-bounds", + "2. bubleify", + "2. color-normalize", + "2. flatten-vertex-data", + "2. object-assign", + "2. pick-by-alias", + "2. to-float32", + "2. update-diff", + "2. array-bounds", + "2. array-normalize", + "2. bubleify", + "2. color-normalize", + "2. earcut", + "2. es6-weak-map", + "2. flatten-vertex-data", + "2. glslify", + "2. object-assign", + "2. parse-rect", + "2. pick-by-alias", + "2. to-float32", + "2. array-range", + "2. array-rearrange", + "2. clamp", + "2. color-id", + "2. color-normalize", + "2. color-rgba", + "2. flatten-vertex-data", + "2. glslify", + "2. image-palette", + "2. is-iexplorer", + "2. object-assign", + "2. parse-rect", + "2. pick-by-alias", + "2. point-cluster", + "2. to-float32", + "2. update-diff", + "2. array-bounds", + "2. array-range", + "2. bubleify", + "2. color-alpha", + "2. defined", + "2. flatten-vertex-data", + "2. left-pad", + "2. parse-rect", + "2. pick-by-alias", + "2. point-cluster", + "2. raf", + "2. regl-scatter2d", + "2. robust-scale", + "2. robust-subtract", + "2. robust-sum", + "2. two-product", + "2. bitmap-sdf", + "2. draw-svg-path", + "2. is-svg-path", + "2. parse-svg-path", + "2. svg-path-bounds", + "2. commander", + "2. get-canvas-context", + "2. object-assign", + "3. d3-path", + "3. circumradius", + "3. delaunay-triangulate", + "3. boundary-cells", + "3. reduce-simplicial-complex", + "3. clamp", + "3. color-parse", + "3. color-space", + "3. robust-orientation", + "3. robust-orientation", + "3. simplicial-complex", + "3. robust-orientation", + "3. strongly-connected-components", + "3. gl-format-compiler-error", + "3. weakmap-shim", + "3. binary-search-bounds", + "3. robust-in-sphere", + "3. robust-orientation", + "3. big-rat", + "3. box-intersect", + "3. nextafter", + "3. rat-vec", + "3. robust-segment-intersect", + "3. union-find", + "3. uniq", + "3. ndarray", + "3. ndarray-ops", + "3. typedarray-pool", + "3. ndarray-extract-contour", + "3. triangulate-hypercube", + "3. zero-crossings", + "3. bit-twiddle", + "3. dup", + "3. ndarray", + "3. ndarray-ops", + "3. typedarray-pool", + "3. robust-linear-solve", + "3. lerp", + "3. glsl-specular-beckmann", + "3. numeric", + "3. marching-simplex-table", + "3. ndarray", + "3. ndarray-sort", + "3. typedarray-pool", + "3. bit-twiddle", + "3. cwise", + "3. gl-fbo", + "3. ndarray", + "3. typedarray-pool", + "3. vectorize-text", + "3. matrix-camera-controller", + "3. orbit-camera-controller", + "3. turntable-camera-controller", + "3. gl-buffer", + "3. gl-vao", + "3. weak-map", + "3. bit-twiddle", + "3. dup", + "3. extract-frustum-planes", + "3. gl-buffer", + "3. gl-mat4", + "3. gl-shader", + "3. gl-state", + "3. gl-vao", + "3. gl-vec4", + "3. glslify", + "3. robust-orientation", + "3. split-polygon", + "3. vectorize-text", + "3. gl-texture2d", + "3. gl-buffer", + "3. gl-shader", + "3. gl-vao", + "3. glslify", + "3. cdt2d", + "3. clean-pslg", + "3. ndarray", + "3. planar-graph-to-polyline", + "3. simplify-planar-graph", + "3. surface-nets", + "3. triangulate-polyline", + "3. cwise-compiler", + "3. dup", + "3. cwise-compiler", + "3. cwise-compiler", + "3. ndarray", + "3. ndarray", + "3. ndarray-ops", + "3. typedarray-pool", + "3. css-font-size-keywords", + "3. css-font-stretch-keywords", + "3. css-font-style-keywords", + "3. css-font-weight-keywords", + "3. css-global-keywords", + "3. css-system-font-keywords", + "3. pick-by-alias", + "3. string-split-by", + "3. unquote", + "3. d", + "3. es5-ext", + "3. es6-iterator", + "3. es6-symbol", + "3. dtype", + "3. css-font", + "3. css-font", + "3. es6-weak-map", + "3. is-browser", + "3. is-firefox", + "3. is-plain-obj", + "3. number-is-integer", + "3. object-assign", + "3. pick-by-alias", + "3. pick-by-alias", + "3. parse-unit", + "3. readable-stream", + "3. safe-buffer", + "3. buffer-from", + "3. readable-stream", + "3. inherits", + "3. typedarray", + "3. readable-stream", + "3. end-of-stream", + "3. inherits", + "3. stream-shift", + "3. acorn", + "3. foreach", + "3. isarray", + "3. object-keys", + "3. readable-stream", + "3. inherits", + "3. resolve", + "3. xtend", + "3. glsl-inject-defines", + "3. glsl-token-defines", + "3. glsl-token-depth", + "3. glsl-token-descope", + "3. glsl-token-scope", + "3. glsl-token-string", + "3. glsl-token-whitespace-trim", + "3. glsl-tokenizer", + "3. murmurhash-js", + "3. shallow-copy", + "3. @choojs/findup", + "3. events", + "3. glsl-resolve", + "3. glsl-tokenizer", + "3. graceful-fs", + "3. inherits", + "3. map-limit", + "3. resolve", + "3. readable-stream", + "3. xtend", + "3. path-parse", + "3. escodegen", + "3. @mapbox/point-geometry", + "3. @mapbox/geojson-area", + "3. concat-stream", + "3. minimist", + "3. sharkdown", + "3. extend-shallow", + "3. kind-of", + "3. js-yaml", + "3. strip-bom-string", + "3. quote-stream", + "3. static-module", + "3. through2", + "3. resolve", + "3. readable-stream", + "3. xtend", + "3. ieee754", + "3. resolve-protobuf-schema", + "3. seedrandom", + "3. sort-asc", + "3. sort-desc", + "3. kdbush", + "3. @mapbox/point-geometry", + "3. @mapbox/vector-tile", + "3. pbf", + "3. gl-mat4", + "3. gl-vec3", + "3. mat4-decompose", + "3. mat4-recompose", + "3. quat-slerp", + "3. cwise-compiler", + "3. cwise-parser", + "3. static-module", + "3. uglify-js", + "3. gl-mat2", + "3. gl-mat3", + "3. gl-mat4", + "3. cwise", + "3. ndarray-linear-interpolate", + "3. array-bounds", + "3. buble", + "3. clamp", + "3. color-id", + "3. pxls", + "3. quantize", + "3. color-parse", + "3. performance-now", + "3. two-product", + "3. two-sum", + "3. clamp", + "3. abs-svg-path", + "3. normalize-svg-path", + "3. abs-svg-path", + "3. is-svg-path", + "3. parse-svg-path", + "3. normalize-svg-path", + "4. circumcenter", + "4. tape", + "4. cell-orientation", + "4. compare-cell", + "4. compare-oriented-cell", + "4. color-name", + "4. defined", + "4. is-plain-obj", + "4. hsluv", + "4. mumath", + "4. bit-twiddle", + "4. union-find", + "4. add-line-numbers", + "4. gl-constants", + "4. glsl-shader-name", + "4. sprintf-js", + "4. robust-scale", + "4. robust-subtract", + "4. robust-sum", + "4. two-product", + "4. bit-twiddle", + "4. bn.js", + "4. double-bits", + "4. bit-twiddle", + "4. typedarray-pool", + "4. double-bits", + "4. big-rat", + "4. robust-orientation", + "4. typedarray-pool", + "4. gamma", + "4. permutation-parity", + "4. permutation-rank", + "4. cwise-compiler", + "4. robust-determinant", + "4. convex-hull", + "4. typedarray-pool", + "4. filtered-vector", + "4. gl-mat4", + "4. filtered-vector", + "4. gl-mat4", + "4. gl-vec3", + "4. uniq", + "4. robust-dot-product", + "4. robust-sum", + "4. edges-to-adjacency-list", + "4. planar-dual", + "4. point-in-big-polygon", + "4. robust-orientation", + "4. robust-sum", + "4. two-product", + "4. uniq", + "4. robust-orientation", + "4. simplicial-complex", + "4. cdt2d", + "4. uniq", + "4. parenthesis", + "4. es5-ext", + "4. es6-iterator", + "4. es6-symbol", + "4. next-tick", + "4. d", + "4. es5-ext", + "4. es6-symbol", + "4. d", + "4. es5-ext", + "4. is-finite", + "4. isarray", + "4. string_decoder", + "4. core-util-is", + "4. inherits", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. isarray", + "4. string_decoder", + "4. core-util-is", + "4. inherits", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. core-util-is", + "4. isarray", + "4. string_decoder", + "4. inherits", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. once", + "4. core-util-is", + "4. isarray", + "4. string_decoder", + "4. inherits", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. glsl-token-inject-block", + "4. glsl-token-string", + "4. glsl-tokenizer", + "4. glsl-tokenizer", + "4. glsl-token-assignments", + "4. glsl-token-depth", + "4. glsl-token-properties", + "4. glsl-token-scope", + "4. through2", + "4. commander", + "4. once", + "4. core-util-is", + "4. isarray", + "4. string_decoder", + "4. inherits", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. esprima", + "4. estraverse", + "4. esutils", + "4. optionator", + "4. source-map", + "4. wgs84", + "4. cardinal", + "4. expect.js", + "4. minimist", + "4. split", + "4. stream-spigot", + "4. through", + "4. is-extendable", + "4. argparse", + "4. esprima", + "4. buffer-equal", + "4. minimist", + "4. through2", + "4. concat-stream", + "4. convert-source-map", + "4. falafel", + "4. has", + "4. magic-string", + "4. duplexer2", + "4. escodegen", + "4. object-inspect", + "4. quote-stream", + "4. readable-stream", + "4. through2", + "4. merge-source-map", + "4. shallow-copy", + "4. static-eval", + "4. core-util-is", + "4. inherits", + "4. isarray", + "4. string_decoder", + "4. process-nextick-args", + "4. safe-buffer", + "4. util-deprecate", + "4. protocol-buffers-schema", + "4. gl-mat4", + "4. gl-vec3", + "4. gl-mat4", + "4. gl-quat", + "4. esprima", + "4. uniq", + "4. concat-stream", + "4. duplexer2", + "4. falafel", + "4. has", + "4. quote-stream", + "4. readable-stream", + "4. shallow-copy", + "4. escodegen", + "4. object-inspect", + "4. static-eval", + "4. through2", + "4. source-map", + "4. uglify-to-browserify", + "4. yargs", + "4. acorn", + "4. acorn-dynamic-import", + "4. acorn-jsx", + "4. vlq", + "4. chalk", + "4. magic-string", + "4. minimist", + "4. os-homedir", + "4. arr-flatten", + "4. compute-dims", + "4. flip-pixels", + "4. is-browser", + "4. is-buffer", + "4. to-uint8", + "4. svg-arc-to-cubic-bezier", + "5. dup", + "5. robust-linear-solve", + "5. deep-equal", + "5. defined", + "5. for-each", + "5. function-bind", + "5. glob", + "5. has", + "5. inherits", + "5. minimist", + "5. object-inspect", + "5. resolve", + "5. resumer", + "5. string.prototype.trim", + "5. through", + "5. cell-orientation", + "5. compare-cell", + "5. almost-equal", + "5. pad-left", + "5. atob-lite", + "5. glsl-tokenizer", + "5. typedarray-pool", + "5. invert-permutation", + "5. typedarray-pool", + "5. robust-compress", + "5. robust-scale", + "5. robust-sum", + "5. two-product", + "5. binary-search-bounds", + "5. cubic-hermite", + "5. robust-sum", + "5. two-product", + "5. uniq", + "5. compare-angle", + "5. dup", + "5. binary-search-bounds", + "5. interval-tree-1d", + "5. robust-orientation", + "5. slab-decomposition", + "5. bit-twiddle", + "5. union-find", + "5. number-is-nan", + "5. safe-buffer", + "5. safe-buffer", + "5. safe-buffer", + "5. wrappy", + "5. safe-buffer", + "5. readable-stream", + "5. xtend", + "5. wrappy", + "5. safe-buffer", + "5. deep-is", + "5. fast-levenshtein", + "5. levn", + "5. prelude-ls", + "5. type-check", + "5. wordwrap", + "5. ansicolors", + "5. redeyed", + "5. through", + "5. readable-stream", + "5. sprintf-js", + "5. function-bind", + "5. vlq", + "5. readable-stream", + "5. esprima", + "5. estraverse", + "5. esutils", + "5. optionator", + "5. source-map", + "5. source-map", + "5. safe-buffer", + "5. gl-mat3", + "5. gl-vec3", + "5. gl-vec4", + "5. readable-stream", + "5. minimist", + "5. through2", + "5. core-util-is", + "5. inherits", + "5. isarray", + "5. string_decoder", + "5. esprima", + "5. estraverse", + "5. esutils", + "5. source-map", + "5. escodegen", + "5. readable-stream", + "5. xtend", + "5. camelcase", + "5. cliui", + "5. decamelize", + "5. window-size", + "5. acorn", + "5. acorn", + "5. ansi-styles", + "5. escape-string-regexp", + "5. supports-color", + "5. utils-copy", + "5. validate.io-array", + "5. validate.io-matrix-like", + "5. validate.io-ndarray-like", + "5. validate.io-positive-integer", + "5. arr-flatten", + "5. clamp", + "5. is-base64", + "5. is-float-array", + "5. to-array-buffer" + ] + } + ], + "layout": { + "width": 800, + "height": 800 + } +} diff --git a/test/image/mocks/icicle_root-sort.json b/test/image/mocks/icicle_root-sort.json new file mode 100644 index 00000000000..d695b132f51 --- /dev/null +++ b/test/image/mocks/icicle_root-sort.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "type": "icicle", + "sort": true, + "root": { + "color": "DodgerBlue" + }, + "domain": { + "x": [0, 0.45] + }, + "textinfo": "label+percent parent", + "labels": [ + "Fruit", + "Apple", + "Pear", + "Grape", + "Orange", + "Kiwi", + "Banana", + "Melon", + "Watermelon", + "Pineapple", + "Strawberry" + ], + "parents": [ + "", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit" + ], + "values": [100, 5, 15, 10, 8, 20, 12, 9, 5, 8, 8], + "branchvalues": "total" + }, + { + "type": "icicle", + "sort": false, + "root": { + "color": "DodgerBlue" + }, + "domain": { + "x": [0.55, 1], + "y": [0, 1] + }, + "textinfo": "label+percent parent", + "labels": [ + "Fruit", + "Apple", + "Pear", + "Grape", + "Orange", + "Kiwi", + "Banana", + "Melon", + "Watermelon", + "Pineapple", + "Strawberry" + ], + "parents": [ + "", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit", + "Fruit" + ], + "values": [100, 5, 15, 10, 8, 20, 12, 9, 5, 8, 8], + "branchvalues": "total" + } + ], + "layout": { + "width": 800, + "height": 700, + "annotations": [ + { + "showarrow": false, + "text": "sort: true", + "x": 0.225, + "xanchor": "center", + "y": 1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "sort: false", + "x": 0.775, + "xanchor": "center", + "y": 1, + "yanchor": "bottom" + } + ] + } +} diff --git a/test/image/mocks/icicle_textposition.json b/test/image/mocks/icicle_textposition.json new file mode 100644 index 00000000000..ce3ab3b8bca --- /dev/null +++ b/test/image/mocks/icicle_textposition.json @@ -0,0 +1,991 @@ +{ + "data": [ + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "bottom left textposition", + "textposition": "bottom left", + "pathbar": { + "side": "bottom", + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "domain": { + "x": [ + 0, + 0.3 + ], + "y": [ + 0, + 0.3 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "bottom center textposition", + "textposition": "bottom center", + "pathbar": { + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Charlie", + "maxdepth": 3, + "domain": { + "x": [ + 0.35, + 0.65 + ], + "y": [ + 0, + 0.3 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "bottom right textposition", + "textposition": "bottom right", + "pathbar": { + "side": "bottom", + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Foxtrot", + "maxdepth": 3, + "domain": { + "x": [ + 0.7, + 1 + ], + "y": [ + 0, + 0.3 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "middle left textposition", + "textposition": "middle left", + "pathbar": { + "visible": false + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Zulu", + "maxdepth": 3, + "domain": { + "x": [ + 0, + 0.3 + ], + "y": [ + 0.35, + 0.65 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "middle center textposition", + "textposition": "middle center", + "pathbar": { + "visible": false + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Alpha", + "maxdepth": 3, + "domain": { + "x": [ + 0.35, + 0.65 + ], + "y": [ + 0.35, + 0.65 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "middle right textposition", + "textposition": "middle right", + "pathbar": { + "visible": false + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Bravo", + "maxdepth": 3, + "domain": { + "x": [ + 0.7, + 1 + ], + "y": [ + 0.35, + 0.65 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "top left textposition", + "textposition": "top left", + "pathbar": { + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Juliet", + "maxdepth": 3, + "domain": { + "x": [ + 0, + 0.3 + ], + "y": [ + 0.7, + 1 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "top center textposition", + "textposition": "top center", + "pathbar": { + "side": "bottom", + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Oscar", + "maxdepth": 3, + "domain": { + "x": [ + 0.35, + 0.65 + ], + "y": [ + 0.7, + 1 + ] + } + }, + { + "type": "icicle", + "root": { "color": "yellow" }, + "textinfo": "label+text+percent parent+percent entry+percent root", + "name": "top right textposition", + "textposition": "top right", + "pathbar": { + "textfont": { + "size": 12, + "family": "Times New Roman" + } + }, + "text": [ + "A L P H A", + "B R A V O", + "C H A R L I E", + "D E L T A", + "E C H O", + "F O X T R O T", + "G O L F", + "H O T E L", + "I N D I A", + "J U L I E T", + "K I L O", + "L I M A", + "M I K E", + "N O V E M B E R", + "O S C A R", + "P A P A", + "Q U E B E C", + "R O M E O", + "S I E R R A", + "T A N G O", + "U N I F O R M", + "V I C T O R", + "W H I S K E Y", + "X R A Y", + "Y A N K E E", + "Z U L U" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "level": "Uniform", + "maxdepth": 3, + "domain": { + "x": [ + 0.7, + 1 + ], + "y": [ + 0.7, + 1 + ] + } + } + ], + "layout": { + "width": 1200, + "height": 1200, + "paper_bgcolor": "black" + } +} diff --git a/test/image/mocks/icicle_values_colorscale.json b/test/image/mocks/icicle_values_colorscale.json new file mode 100644 index 00000000000..0954d9bfc9d --- /dev/null +++ b/test/image/mocks/icicle_values_colorscale.json @@ -0,0 +1,172 @@ +{ + "data": [ + { + "type": "icicle", + "name": "with branchvalues:remainder", + "labels": [ + "Eve", + "Cain", + "Seth", + "Enos", + "Noam", + "Abel", + "Awan", + "Enoch", + "Azura" + ], + "parents": [ + "", + "Eve", + "Eve", + "Seth", + "Seth", + "Eve", + "Eve", + "Awan", + "Eve" + ], + "values": [ + 10, + 14, + 12, + 10, + 2, + 6, + 6, + 1, + 4 + ], + "domain": { + "x": [ + 0, + 0.48 + ] + }, + "marker": { + "colorscale": "Portland", + "colors": [ + 10, + 14, + 12, + 10, + 2, + 6, + 6, + 1, + 4 + ], + "showscale": true, + "colorbar": { + "title": { + "text": "trace A" + }, + "x": 0, + "xanchor": "right" + } + }, + "textinfo": "label+value", + "tiling": { + "pad": 4 + }, + "pathbar": { + "thickness": 32, + "textfont": { + "color": "white", + "size": 28, + "family": "Times New Roman" + } + } + }, + { + "type": "icicle", + "name": "with branchvalues:total", + "branchvalues": "total", + "labels": [ + "Eve", + "Cain", + "Seth", + "Enos", + "Noam", + "Abel", + "Awan", + "Enoch", + "Azura" + ], + "parents": [ + "", + "Eve", + "Eve", + "Seth", + "Seth", + "Eve", + "Eve", + "Awan", + "Eve" + ], + "domain": { + "x": [ + 0.52, + 1 + ] + }, + "values": [ + 65, + 14, + 12, + 10, + 2, + 6, + 6, + 1, + 4 + ], + "text": [ + "sixty five", + "fourteen", + "twelve", + "ten", + "two", + "six", + "six", + "one", + "four" + ], + "marker": { + "cmin": 0, + "cmax": 25, + "colorscale": "Blackbody", + "reversescale": true, + "showscale": true, + "colorbar": { + "title": { + "text": "trace B" + } + } + }, + "textinfo": "label+text" + } + ], + "layout": { + "width": 800, + "height": 500, + "annotations": [ + { + "showarrow": false, + "text": "branchvalues: remainder", + "x": 0.25, + "xanchor": "center", + "y": 1.1, + "yanchor": "bottom" + }, + { + "showarrow": false, + "text": "branchvalues: total
used as input to marker.color", + "x": 0.75, + "xanchor": "center", + "y": 1.1, + "yanchor": "bottom" + } + ], + "paper_bgcolor": "#d3d3d3" + } +} diff --git a/test/image/mocks/icicle_with-without_values.json b/test/image/mocks/icicle_with-without_values.json new file mode 100644 index 00000000000..158e022e424 --- /dev/null +++ b/test/image/mocks/icicle_with-without_values.json @@ -0,0 +1,430 @@ +{ + "data": [ + { + "type": "icicle", + "textinfo": "label+value+percent parent+percent entry+percent root+text+current path", + "hoverinfo": "all", + "name": "without values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "domain": { + "x": [ + 0.01, + 0.33 + ] + } + }, + { + "type": "icicle", + "textinfo": "label+value+percent parent+percent entry+percent root+text+current path", + "hoverinfo": "all", + "name": "with total values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "branchvalues": "total", + "values": [ + 40, + 2, + 38, + 1.5, + 2.5, + 34, + 1, + 2, + 3, + 28, + 1.25, + 1.75, + 2.25, + 2.75, + 20, + 1, + 1.5, + 2, + 2.5, + 3, + 10, + 1, + 1.5, + 2, + 2.5, + 3 + ], + "text": [ + "forty", + "two", + "thirty-eight", + "one and a half", + "two and a half", + "thirty-four", + "one", + "two", + "three", + "twenty-eight", + "one and twenty-five hundredths", + "one and seventy-five hundredths", + "two and twenty-five hundredths", + "two and seventy-five hundredths", + "twenty", + "one", + "one and a half", + "two", + "two and a half", + "three", + "ten", + "one", + "one and a half", + "two", + "two and a half", + "three" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "marker": { + "colorscale": [ + [ + 0, + "#FFF" + ], + [ + 0.01, + "#0FF" + ], + [ + 0.1, + "#00F" + ], + [ + 1, + "#000" + ] + ] + }, + "domain": { + "x": [ + 0.34, + 0.66 + ] + } + }, + { + "type": "icicle", + "textinfo": "label+value+percent parent+percent entry+percent root+text+current path", + "hoverinfo": "all", + "name": "with remainder values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "branchvalues": "remainder", + "values": [ + 0, + 2, + 0, + 1.5, + 2.5, + 0, + 1, + 2, + 3, + 0, + 1.25, + 1.75, + 2.25, + 2.75, + 0, + 1, + 1.5, + 2, + 2.5, + 3, + 0, + 1, + 1.5, + 2, + 2.5, + 3 + ], + "text": [ + "zero", + "two", + "zero", + "one and a half", + "two and a half", + "zero", + "one", + "two", + "three", + "zero", + "one and twenty-five hundredths", + "one and seventy-five hundredths", + "two and twenty-five hundredths", + "two and seventy-five hundredths", + "zero", + "one", + "one and a half", + "two", + "two and a half", + "three", + "zero", + "one", + "one and a half", + "two", + "two and a half", + "three" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "marker": { + "colorscale": [ + [ + 0, + "#FFF" + ], + [ + 0.01, + "#0FF" + ], + [ + 0.1, + "#00F" + ], + [ + 1, + "#000" + ] + ] + }, + "domain": { + "x": [ + 0.67, + 0.99 + ] + } + } + ], + "layout": { + "margin" : { + "t": 50, + "l": 0, + "r": 0, + "b": 25 + }, + "width": 1500, + "height": 600, + "shapes": [ + { + "type": "rect", + "layer": "above", + "x0": 0.01, + "x1": 0.33, + "y0": 0, + "y1": 1 + }, + { + "type": "rect", + "layer": "above", + "x0": 0.34, + "x1": 0.66, + "y0": 0, + "y1": 1 + }, + { + "type": "rect", + "layer": "above", + "x0": 0.67, + "x1": 0.99, + "y0": 0, + "y1": 1 + } + ], + "annotations": [ + { + "showarrow": false, + "text": "with counted leaves
", + "x": 0.17, + "xanchor": "center", + "y": 0, + "yanchor": "top" + }, + { + "showarrow": false, + "text": "with values and branchvalues: total
", + "x": 0.5, + "xanchor": "center", + "y": 0, + "yanchor": "top" + }, + { + "showarrow": false, + "text": "with values and branchvalues: remainder
", + "x": 0.83, + "xanchor": "center", + "y": 0, + "yanchor": "top" + } + ] + } +} diff --git a/test/image/mocks/icicle_with-without_values_template.json b/test/image/mocks/icicle_with-without_values_template.json new file mode 100644 index 00000000000..c7867756bbd --- /dev/null +++ b/test/image/mocks/icicle_with-without_values_template.json @@ -0,0 +1,434 @@ +{ + "data": [ + { + "type": "icicle", + "texttemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "hovertemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "name": "without values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "domain": { + "x": [ + 0.01, + 0.33 + ] + } + }, + { + "type": "icicle", + "texttemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "hovertemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "name": "with total values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "branchvalues": "total", + "values": [ + 40, + 2, + 38, + 1.5, + 2.5, + 34, + 1, + 2, + 3, + 28, + 1.25, + 1.75, + 2.25, + 2.75, + 20, + 1, + 1.5, + 2, + 2.5, + 3, + 10, + 1, + 1.5, + 2, + 2.5, + 3 + ], + "text": [ + "forty", + "two", + "thirty-eight", + "one and a half", + "two and a half", + "thirty-four", + "one", + "two", + "three", + "twenty-eight", + "one and twenty-five hundredths", + "one and seventy-five hundredths", + "two and twenty-five hundredths", + "two and seventy-five hundredths", + "twenty", + "one", + "one and a half", + "two", + "two and a half", + "three", + "ten", + "one", + "one and a half", + "two", + "two and a half", + "three" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "marker": { + "colorscale": [ + [ + 0, + "#FFF" + ], + [ + 0.01, + "#FF0" + ], + [ + 0.1, + "#F00" + ], + [ + 1, + "#000" + ] + ] + }, + "domain": { + "x": [ + 0.34, + 0.66 + ] + } + }, + { + "type": "icicle", + "texttemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "hovertemplate": "%{currentPath}%{label}
%{text}
%{value}
%{parent} <=> %{percentParent}
%{entry} <=> %{percentEntry}
%{root} <=> %{percentRoot}", + "name": "with remainder values", + "level": "Oscar", + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "branchvalues": "remainder", + "values": [ + 0, + 2, + 0, + 1.5, + 2.5, + 0, + 1, + 2, + 3, + 0, + 1.25, + 1.75, + 2.25, + 2.75, + 0, + 1, + 1.5, + 2, + 2.5, + 3, + 0, + 1, + 1.5, + 2, + 2.5, + 3 + ], + "text": [ + "zero", + "two", + "zero", + "one and a half", + "two and a half", + "zero", + "one", + "two", + "three", + "zero", + "one and twenty-five hundredths", + "one and seventy-five hundredths", + "two and twenty-five hundredths", + "two and seventy-five hundredths", + "zero", + "one", + "one and a half", + "two", + "two and a half", + "three", + "zero", + "one", + "one and a half", + "two", + "two and a half", + "three" + ], + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "marker": { + "colorscale": [ + [ + 0, + "#FFF" + ], + [ + 0.01, + "#FF0" + ], + [ + 0.1, + "#F00" + ], + [ + 1, + "#000" + ] + ] + }, + "domain": { + "x": [ + 0.67, + 0.99 + ] + } + } + ], + "layout": { + "iciclecolorway": [ + "#700", + "#077" + ], + "margin" : { + "t": 50, + "l": 0, + "r": 0, + "b": 25 + }, + "width": 1500, + "height": 600, + "shapes": [ + { + "type": "rect", + "layer": "above", + "x0": 0.01, + "x1": 0.33, + "y0": 0, + "y1": 1 + }, + { + "type": "rect", + "layer": "above", + "x0": 0.34, + "x1": 0.66, + "y0": 0, + "y1": 1 + }, + { + "type": "rect", + "layer": "above", + "x0": 0.67, + "x1": 0.99, + "y0": 0, + "y1": 1 + } + ], + "annotations": [ + { + "showarrow": false, + "text": "with counted leaves
", + "x": 0.17, + "xanchor": "center", + "y": 0, + "yanchor": "top" + }, + { + "showarrow": false, + "text": "with values and branchvalues: total
", + "x": 0.5, + "xanchor": "center", + "y": 0, + "yanchor": "top" + }, + { + "showarrow": false, + "text": "with values and branchvalues: remainder
", + "x": 0.83, + "xanchor": "center", + "y": 0, + "yanchor": "top" + } + ] + } +} diff --git a/test/image/mocks/uniformtext_icicle.json b/test/image/mocks/uniformtext_icicle.json new file mode 100644 index 00000000000..12037ceb949 --- /dev/null +++ b/test/image/mocks/uniformtext_icicle.json @@ -0,0 +1,365 @@ +{ + "data": [ + { + "tiling": { + "pad": 3, + "orientation": "v" + }, + "marker": { + "coloraxis": "coloraxis" + }, + "type": "icicle", + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "textinfo": "label", + "domain": { + "x": [ + 0.52, + 0.98 + ], + "y": [ + 0.52, + 0.98 + ] + } + }, + { + "tiling": { + "pad": 3, + "orientation": "v" + }, + "marker": { + "coloraxis": "coloraxis" + }, + "type": "icicle", + "level": "Uniform", + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "textinfo": "label", + "textfont": { + "family": "Raleway, sans-serif", + "color": "#FFF", + "size": 15 + }, + "domain": { + "x": [ + 0.52, + 0.98 + ], + "y": [ + 0.02, + 0.48 + ] + } + }, + { + "tiling": { + "pad": 3 + }, + "marker": { + "coloraxis": "coloraxis" + }, + "type": "icicle", + "level": "Juliet", + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "textinfo": "label", + "insidetextfont": { + "family": "Courier New, monospace", + "color": "#000", + "size": 10 + }, + "pathbar": { + "textfont": { + "family": "Times New Roman, Times, serif", + "color": "#FF0", + "size": 20 + } + }, + "domain": { + "x": [ + 0.02, + 0.48 + ], + "y": [ + 0.02, + 0.48 + ] + } + }, + { + "tiling": { + "pad": 3 + }, + "marker": { + "coloraxis": "coloraxis" + }, + "type": "icicle", + "level": "Juliet", + "parents": [ + "", + "Alpha", + "Alpha", + "Charlie", + "Charlie", + "Charlie", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Foxtrot", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Juliet", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Oscar", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform", + "Uniform" + ], + "labels": [ + "Alpha", + "Bravo", + "Charlie", + "Delta", + "Echo", + "Foxtrot", + "Golf", + "Hotel", + "India", + "Juliet", + "Kilo", + "Lima", + "Mike", + "November", + "Oscar", + "Papa", + "Quebec", + "Romeo", + "Sierra", + "Tango", + "Uniform", + "Victor", + "Whiskey", + "X ray", + "Yankee", + "Zulu" + ], + "textinfo": "label", + "insidetextfont": { + "family": "Times New Roman, Times, serif", + "color": "#FF0", + "size": 20 + }, + "pathbar": { + "textfont": { + "family": "Courier New, monospace", + "color": "#000", + "size": 10 + } + }, + "domain": { + "x": [ + 0.02, + 0.48 + ], + "y": [ + 0.52, + 0.98 + ] + } + } + ], + "layout": { + "coloraxis": { + "colorscale": "Portland", + "reversescale": true, + "showscale": false + }, + "width": 550, + "height": 800, + "margin": { + "t": 10, + "b": 10, + "l": 10, + "r": 10 + }, + "uniformtext": { + "minsize": 12, + "mode": "hide" + } + } +} diff --git a/test/jasmine/tests/icicle_test.js b/test/jasmine/tests/icicle_test.js new file mode 100644 index 00000000000..143165feb80 --- /dev/null +++ b/test/jasmine/tests/icicle_test.js @@ -0,0 +1,922 @@ +var Plotly = require('@lib/index'); +var Plots = require('@src/plots/plots'); +var Lib = require('@src/lib'); + +var d3Select = require('../../strict-d3').select; +var d3SelectAll = require('../../strict-d3').selectAll; +var supplyAllDefaults = require('../assets/supply_defaults'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var mouseEvent = require('../assets/mouse_event'); + +var customAssertions = require('../assets/custom_assertions'); +var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle; +var assertHoverLabelContent = customAssertions.assertHoverLabelContent; +var checkTextTemplate = require('../assets/check_texttemplate'); + +var SLICES_SELECTOR = '.iciclelayer path.surface'; + +function _mouseEvent(type, gd, v) { + return function() { + if(Array.isArray(v)) { + // px-based position + mouseEvent(type, v[0], v[1]); + } else { + // position from slice number + var gd3 = d3Select(gd); + var el = gd3.select('.slice:nth-child(' + v + ')').node(); + mouseEvent(type, 0, 0, {element: el}); + } + }; +} + +function hover(gd, v) { + return _mouseEvent('mouseover', gd, v); +} + +describe('Test icicle defaults:', function() { + var gd; + var fullData; + + function _supply(opts, layout) { + gd = {}; + opts = Array.isArray(opts) ? opts : [opts]; + + gd.data = opts.map(function(o) { + return Lib.extendFlat({type: 'icicle'}, o || {}); + }); + gd.layout = layout || {}; + + supplyAllDefaults(gd); + fullData = gd._fullData; + } + + it('should set *visible:false* when *labels* or *parents* is missing', function() { + _supply([ + {labels: [1], parents: ['']}, + {labels: [1]}, + {parents: ['']} + ]); + + expect(fullData[0].visible).toBe(true, 'base'); + expect(fullData[1].visible).toBe(false, 'no parents'); + expect(fullData[2].visible).toBe(false, 'no labels'); + }); + + it('should only coerce *count* when the *values* array is not present', function() { + _supply([ + {labels: [1], parents: ['']}, + {labels: [1], parents: [''], values: []}, + {labels: [1], parents: [''], values: [1]} + ]); + + expect(fullData[0].count).toBe('leaves'); + expect(fullData[1].count).toBe('leaves', 'has empty values'); + expect(fullData[2].count).toBe(undefined, 'has values'); + }); + + it('should not coerce *branchvalues* when *values* is not set', function() { + _supply([ + {labels: [1], parents: [''], values: [1]}, + {labels: [1], parents: ['']}, + ]); + + expect(fullData[0].branchvalues).toBe('remainder', 'base'); + expect(fullData[1].branchvalues).toBe(undefined, 'no values'); + }); + + it('should use *paper_bgcolor* as *marker.line.color* default', function() { + _supply([ + {labels: [1], parents: [''], marker: {line: {color: 'red'}}}, + {labels: [1], parents: ['']} + ], { + paper_bgcolor: 'orange' + }); + + expect(fullData[0].marker.line.color).toBe('red', 'set color'); + expect(fullData[1].marker.line.color).toBe('orange', 'using dflt'); + }); + + it('should not coerce *marker.line.color* when *marker.line.width* is 0', function() { + _supply([ + {labels: [1], parents: [''], marker: {line: {width: 0}}}, + {labels: [1], parents: ['']} + ]); + + expect(fullData[0].marker.line.color).toBe(undefined, 'not coerced'); + expect(fullData[1].marker.line.color).toBe('#fff', 'dflt'); + }); + + it('should default *leaf.opacity* depending on a *colorscale* being present or not', function() { + _supply([ + {labels: [1], parents: ['']}, + {labels: [1], parents: [''], marker: {colorscale: 'Blues'}} + ]); + + expect(fullData[0].leaf.opacity).toBe(0.7, 'without colorscale'); + expect(fullData[1].leaf.opacity).toBe(1, 'with colorscale'); + }); + + it('should include "text" flag in *textinfo* when *text* is set', function() { + _supply([ + {labels: [1], parents: [''], text: ['A']}, + {labels: [1], parents: ['']} + ]); + + expect(fullData[0].textinfo).toBe('text+label', 'with text'); + expect(fullData[1].textinfo).toBe('label', 'no text'); + }); + + it('should use *layout.colorway* as dflt for *iciclecolorway*', function() { + _supply([ + {labels: [1], parents: ['']} + ], { + colorway: ['red', 'blue', 'green'] + }); + expect(gd._fullLayout.iciclecolorway) + .toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); + + _supply([ + {labels: [1], parents: ['']} + ], { + colorway: ['red', 'blue', 'green'], + iciclecolorway: ['cyan', 'yellow', 'black'] + }); + expect(gd._fullLayout.iciclecolorway) + .toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); + }); + + it('should not default *marker.colorscale* when *marker.colors* is not present', function() { + _supply([ + {labels: [1], parents: ['']} + ]); + + expect(fullData[0].marker.colorscale).toBe(undefined); + }); + + it('should default *marker.colorscale* to *Reds* when *marker.colors* is present', function() { + _supply([ + {labels: [1], parents: [''], marker: { + colors: [0] + }} + ]); + + expect(fullData[0].marker.colorscale).toBeCloseToArray([ + [ 0, 'rgb(5,10,172)' ], + [ 0.35, 'rgb(106,137,247)' ], + [ 0.5, 'rgb(190,190,190)' ], + [ 0.6, 'rgb(220,170,132)' ], + [ 0.7, 'rgb(230,145,90)' ], + [ 1, 'rgb(178,10,28)' ] + ]); + }); +}); + +describe('Test icicle calc:', function() { + var gd; + + beforeEach(function() { + spyOn(Lib, 'warn'); + }); + + function _calc(opts, layout) { + gd = {}; + opts = Array.isArray(opts) ? opts : [opts]; + + gd.data = opts.map(function(o) { + return Lib.extendFlat({type: 'icicle'}, o || {}); + }); + gd.layout = layout || {}; + + supplyAllDefaults(gd); + Plots.doCalcdata(gd); + } + + function extract(k) { + var out = gd.calcdata.map(function(cd) { + return cd.map(function(pt) { return pt[k]; }); + }); + return out.length > 1 ? out : out[0]; + } + + function extractPt(k) { + var out = gd.calcdata.map(function(cd) { + return cd[0].hierarchy.descendants().map(function(pt) { + return Lib.nestedProperty(pt, k).get(); + }); + }); + return out.length > 1 ? out : out[0]; + } + + it('should generate *id* when it can', function() { + _calc({ + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + }); + + expect(extract('id')).toEqual(['Root', 'A', 'B', 'b']); + expect(Lib.warn).toHaveBeenCalledTimes(0); + }); + + it('should generate "implied root" when it can', function() { + _calc({ + labels: [ 'A', 'B', 'b'], + parents: ['Root', 'Root', 'B'] + }); + + expect(extract('id')).toEqual(['Root', 'A', 'B', 'b']); + expect(extract('pid')).toEqual(['', 'Root', 'Root', 'B']); + expect(extract('label')).toEqual(['Root', 'A', 'B', 'b']); + expect(Lib.warn).toHaveBeenCalledTimes(0); + }); + + it('should warn when there are multiple implied roots', function() { + _calc({ + labels: [ 'A', 'B', 'b'], + parents: ['Root1', 'Root22', 'B'] + }); + + expect(Lib.warn).toHaveBeenCalledTimes(1); + expect(Lib.warn).toHaveBeenCalledWith('Multiple implied roots, cannot build icicle hierarchy of trace 0. These roots include: Root1, Root22'); + }); + + it('should generate "root of roots" when it can', function() { + spyOn(Lib, 'randstr').and.callFake(function() { + return 'dummy'; + }); + + _calc({ + labels: [ 'A', 'B', 'b'], + parents: ['', '', 'B'] + }); + + expect(extract('id')).toEqual(['dummy', 'A', 'B', 'b']); + expect(extract('pid')).toEqual(['', 'dummy', 'dummy', 'B']); + expect(extract('label')).toEqual(['', 'A', 'B', 'b']); + }); + + it('should compute hierarchy values', function() { + var labels = ['Root', 'A', 'B', 'b']; + var parents = ['', 'Root', 'Root', 'B']; + + _calc([ + {labels: labels, parents: parents, count: 'leaves+branches'}, + {labels: labels, parents: parents, count: 'branches'}, + {labels: labels, parents: parents}, // N.B. counts 'leaves' in this case + {labels: labels, parents: parents, values: [0, 1, 2, 3]}, + {labels: labels, parents: parents, values: [30, 20, 10, 5], branchvalues: 'total'} + ]); + + expect(extractPt('value')).toEqual([ + [4, 2, 1, 1], + [2, 1, 0, 0], + [2, 1, 1, 1], + [6, 5, 1, 3], + [30, 20, 10, 5] + ]); + expect(Lib.warn).toHaveBeenCalledTimes(0); + }); + + it('should warn when values under *branchvalues:total* do not add up and not show trace', function() { + _calc({ + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + values: [0, 1, 2, 3], + branchvalues: 'total' + }); + + expect(gd.calcdata[0][0].hierarchy).toBe(undefined, 'no computed hierarchy'); + + expect(Lib.warn).toHaveBeenCalledTimes(2); + expect(Lib.warn.calls.allArgs()[0][0]).toBe('Total value for node Root of trace 0 is smaller than the sum of its children. \nparent value = 0 \nchildren sum = 3'); + expect(Lib.warn.calls.allArgs()[1][0]).toBe('Total value for node B of trace 0 is smaller than the sum of its children. \nparent value = 2 \nchildren sum = 3'); + }); + + it('should warn labels/parents lead to ambiguous hierarchy', function() { + _calc({ + labels: ['Root', 'A', 'A', 'B'], + parents: ['', 'Root', 'Root', 'A'] + }); + + expect(Lib.warn).toHaveBeenCalledTimes(1); + expect(Lib.warn).toHaveBeenCalledWith('Failed to build icicle hierarchy of trace 0. Error: ambiguous: A'); + }); + + it('should warn ids/parents lead to ambiguous hierarchy', function() { + _calc({ + labels: ['label 1', 'label 2', 'label 3', 'label 4'], + ids: ['a', 'b', 'b', 'c'], + parents: ['', 'a', 'a', 'b'] + }); + + expect(Lib.warn).toHaveBeenCalledTimes(1); + expect(Lib.warn).toHaveBeenCalledWith('Failed to build icicle hierarchy of trace 0. Error: ambiguous: b'); + }); + + it('should accept numbers (even `0`) are ids/parents items', function() { + _calc({ + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + ids: [0, 1, 2, 3, 4, 5, 6, 7, 8], + parents: ['', 0, 0, 2, 2, 0, 0, 6, 0] + }); + + expect(extract('id')).toEqual(['0', '1', '2', '3', '4', '5', '6', '7', '8']); + expect(extract('pid')).toEqual(['', '0', '0', '2', '2', '0', '0', '6', '0']); + }); + + it('should accept mix typed are ids/parents items', function() { + _calc({ + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + ids: [true, 1, '2', 3, 4, 5, 6, 7, 8], + parents: ['', true, true, 2, 2, 'true', 'true', '6', true] + }); + + expect(extract('id')).toEqual(['true', '1', '2', '3', '4', '5', '6', '7', '8']); + expect(extract('pid')).toEqual(['', 'true', 'true', '2', '2', 'true', 'true', '6', 'true']); + }); + + it('should compute correct sector *value* for generated implied root', function() { + _calc([{ + labels: [ 'A', 'B', 'b'], + parents: ['Root', 'Root', 'B'], + values: [1, 2, 1], + branchvalues: 'remainder' + }, { + labels: [ 'A', 'B', 'b'], + parents: ['Root', 'Root', 'B'], + values: [1, 2, 1], + branchvalues: 'total' + }]); + + expect(extractPt('data.data.id')).toEqual([ + ['Root', 'B', 'A', 'b'], + ['Root', 'B', 'A', 'b'] + ]); + expect(extractPt('value')).toEqual([ + [4, 3, 1, 1], + [3, 2, 1, 1] + ]); + }); + + it('should compute correct sector *value* for generated "root of roots"', function() { + spyOn(Lib, 'randstr').and.callFake(function() { return 'dummy'; }); + + _calc([{ + labels: [ 'A', 'B', 'b'], + parents: ['', '', 'B'], + values: [1, 2, 1], + branchvalues: 'remainder' + }, { + labels: [ 'A', 'B', 'b'], + parents: ['', '', 'B'], + values: [1, 2, 1], + branchvalues: 'total' + }]); + + expect(extractPt('data.data.id')).toEqual([ + ['dummy', 'B', 'A', 'b'], + ['dummy', 'B', 'A', 'b'] + ]); + expect(extractPt('value')).toEqual([ + [4, 3, 1, 1], + [3, 2, 1, 1] + ]); + }); + + it('should use *marker.colors*', function() { + _calc({ + marker: { colors: ['pink', '#777', '#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#fff'] }, + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve'] + }); + + var cd = gd.calcdata[0]; + expect(cd.length).toEqual(9); + expect(cd[0].color).toEqual('rgba(255, 192, 203, 1)'); + expect(cd[1].color).toEqual('rgba(119, 119, 119, 1)'); + expect(cd[2].color).toEqual('rgba(255, 0, 0, 1)'); + expect(cd[3].color).toEqual('rgba(255, 255, 0, 1)'); + expect(cd[4].color).toEqual('rgba(0, 255, 0, 1)'); + expect(cd[5].color).toEqual('rgba(0, 255, 255, 1)'); + expect(cd[6].color).toEqual('rgba(0, 0, 255, 1)'); + expect(cd[7].color).toEqual('rgba(255, 0, 255, 1)'); + expect(cd[8].color).toEqual('rgba(255, 255, 255, 1)'); + }); + + it('should use *marker.colors* numbers with default colorscale', function() { + _calc({ + marker: { colors: [-4, -3, -2, -1, 0, 1, 2, 3, 4] }, + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve'] + }); + + var cd = gd.calcdata[0]; + expect(cd.length).toEqual(9); + expect(cd[0].color).toEqual('rgb(5, 10, 172)'); + expect(cd[1].color).toEqual('rgb(41, 55, 199)'); + expect(cd[2].color).toEqual('rgb(77, 101, 226)'); + expect(cd[3].color).toEqual('rgb(120, 146, 238)'); + expect(cd[4].color).toEqual('rgb(190, 190, 190)'); + expect(cd[5].color).toEqual('rgb(223, 164, 122)'); + expect(cd[6].color).toEqual('rgb(221, 123, 80)'); + expect(cd[7].color).toEqual('rgb(200, 66, 54)'); + expect(cd[8].color).toEqual('rgb(178, 10, 28)'); + }); + + it('should use *marker.colors* numbers with desired colorscale', function() { + _calc({ + marker: { colors: [1, 2, 3, 4, 5, 6, 7, 8, 9], colorscale: 'Portland' }, + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve'] + }); + + var cd = gd.calcdata[0]; + expect(cd.length).toEqual(9); + expect(cd[0].color).toEqual('rgb(12, 51, 131)'); + expect(cd[1].color).toEqual('rgb(11, 94, 159)'); + expect(cd[2].color).toEqual('rgb(10, 136, 186)'); + expect(cd[3].color).toEqual('rgb(126, 174, 121)'); + expect(cd[4].color).toEqual('rgb(242, 211, 56)'); + expect(cd[5].color).toEqual('rgb(242, 177, 56)'); + expect(cd[6].color).toEqual('rgb(242, 143, 56)'); + expect(cd[7].color).toEqual('rgb(230, 87, 43)'); + expect(cd[8].color).toEqual('rgb(217, 30, 30)'); + }); + + it('should use *marker.colors* numbers not values with colorscale', function() { + _calc({ + values: [0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000], + marker: { colors: [1, 2, 3, 4, 5, 6, 7, 8, 9], colorscale: 'Portland' }, + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve'] + }); + + var cd = gd.calcdata[0]; + expect(cd.length).toEqual(9); + expect(cd[0].color).toEqual('rgb(12, 51, 131)'); + expect(cd[1].color).toEqual('rgb(11, 94, 159)'); + expect(cd[2].color).toEqual('rgb(10, 136, 186)'); + expect(cd[3].color).toEqual('rgb(126, 174, 121)'); + expect(cd[4].color).toEqual('rgb(242, 211, 56)'); + expect(cd[5].color).toEqual('rgb(242, 177, 56)'); + expect(cd[6].color).toEqual('rgb(242, 143, 56)'); + expect(cd[7].color).toEqual('rgb(230, 87, 43)'); + expect(cd[8].color).toEqual('rgb(217, 30, 30)'); + }); + + it('should use values with colorscale when *marker.colors* in empty', function() { + _calc({ + values: [1, 2, 3, 4, 5, 6, 7, 8, 9], + marker: { colors: [], colorscale: 'Portland' }, + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve'] + }); + + var cd = gd.calcdata[0]; + expect(cd.length).toEqual(9); + expect(cd[0].color).toEqual('rgb(12, 51, 131)'); + expect(cd[1].color).toEqual('rgb(11, 94, 159)'); + expect(cd[2].color).toEqual('rgb(10, 136, 186)'); + expect(cd[3].color).toEqual('rgb(126, 174, 121)'); + expect(cd[4].color).toEqual('rgb(242, 211, 56)'); + expect(cd[5].color).toEqual('rgb(242, 177, 56)'); + expect(cd[6].color).toEqual('rgb(242, 143, 56)'); + expect(cd[7].color).toEqual('rgb(230, 87, 43)'); + expect(cd[8].color).toEqual('rgb(217, 30, 30)'); + }); +}); + +describe('Test icicle hover:', function() { + var gd; + + var labels0 = ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura']; + var parents0 = ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve']; + var values0 = [10, 14, 12, 10, 2, 6, 6, 1, 4]; + + afterEach(destroyGraphDiv); + + function run(spec) { + gd = createGraphDiv(); + + var data = (spec.traces || [{}]).map(function(t) { + t.type = 'icicle'; + if(!t.labels) t.labels = labels0.slice(); + if(!t.parents) t.parents = parents0.slice(); + return t; + }); + + var layout = Lib.extendFlat({ + width: 500, + height: 500, + margin: {t: 0, b: 0, l: 0, r: 0, pad: 0} + }, spec.layout || {}); + + var exp = spec.exp || {}; + var ptData = null; + + return Plotly.newPlot(gd, data, layout) + .then(function() { + gd.once('plotly_hover', function(d) { ptData = d.points[0]; }); + }) + .then(hover(gd, spec.pos)) + .then(function() { + assertHoverLabelContent(exp.label); + + for(var k in exp.ptData) { + expect(ptData[k]).toBe(exp.ptData[k], 'pt event data key ' + k); + } + + if(exp.style) { + var gd3 = d3Select(gd); + assertHoverLabelStyle(gd3.select('.hovertext'), exp.style); + } + }); + } + + [{ + desc: 'base', + pos: 2, + exp: { + label: { + nums: 'Seth', + }, + ptData: { + curveNumber: 0, + pointNumber: 2, + label: 'Seth', + parent: 'Eve' + } + } + }, { + desc: 'with scalar hovertext', + traces: [{ hovertext: 'A' }], + pos: 3, + exp: { + label: { + nums: 'Cain\nA', + }, + ptData: { + curveNumber: 0, + pointNumber: 1, + label: 'Cain', + parent: 'Eve' + } + } + }, { + desc: 'with array hovertext', + traces: [{ + hovertext: values0, + hoverinfo: 'all' + }], + pos: 4, + exp: { + label: { + nums: 'Abel\nEve/\n17% of Eve\n6', + name: 'trace 0' + }, + ptData: { + curveNumber: 0, + pointNumber: 5, + label: 'Abel', + parent: 'Eve' + } + } + }, { + desc: 'with hoverlabel.namelength set ', + traces: [{ + hoverlabel: {namelength: 4}, + hoverinfo: 'all' + }], + pos: 4, + exp: { + label: { + nums: 'Abel\nEve/\n17% of Eve', + name: 't...' + }, + ptData: { + curveNumber: 0, + pointNumber: 5, + label: 'Abel', + parent: 'Eve' + } + } + }, { + desc: 'with values', + traces: [{ + values: values0, + hoverinfo: 'value' + }], + pos: 5, + exp: { + label: { + nums: '6' + }, + ptData: { + curveNumber: 0, + pointNumber: 5, + label: 'Abel', + parent: 'Eve', + value: 6 + } + } + }, { + desc: 'with values and hovertemplate', + traces: [{ + values: values0, + hovertemplate: '%{label} :: %{value:.2f}N.B.' + }], + pos: 5, + exp: { + label: { + nums: 'Abel :: 6.00', + name: 'N.B.' + }, + ptData: { + curveNumber: 0, + pointNumber: 5, + label: 'Abel', + parent: 'Eve', + value: 6 + } + } + }, { + desc: 'with array hovertemplate and label styling', + traces: [{ + hovertemplate: parents0.map(function(p) { + return p ? + '%{label} -| %{parent}' : + '%{label}THE ROOT'; + }), + hoverlabel: { + bgcolor: 'red', + bordercolor: 'blue', + font: { + size: 20, + family: 'Roboto', + color: 'orange' + } + } + }], + pos: 1, + exp: { + label: { + nums: 'Eve', + name: 'THE ROOT' + }, + style: { + bgcolor: 'rgb(255, 0, 0)', + bordercolor: 'rgb(0, 0, 255)', + fontSize: 20, + fontFamily: 'Roboto', + fontColor: 'rgb(255, 165, 0)' + }, + ptData: { + curveNumber: 0, + pointNumber: 0, + label: 'Eve', + parent: '' + } + } + }] + .forEach(function(spec) { + it('should generate correct hover labels and event data - ' + spec.desc, function(done) { + run(spec).then(done, done.fail); + }); + }); +}); + +describe('Test icicle restyle:', function() { + var gd; + + beforeEach(function() { gd = createGraphDiv(); }); + + afterEach(destroyGraphDiv); + + function _restyle(updateObj) { + return function() { return Plotly.restyle(gd, updateObj); }; + } + + it('should be able to toggle visibility', function(done) { + var mock = Lib.extendDeep({}, require('@mocks/icicle_first.json')); + + function _assert(msg, exp) { + return function() { + var layer = d3Select(gd).select('.iciclelayer'); + expect(layer.selectAll('.trace').size()).toBe(exp, msg); + }; + } + + Plotly.newPlot(gd, mock) + .then(_assert('base', 2)) + .then(_restyle({'visible': false})) + .then(_assert('both visible:false', 0)) + .then(_restyle({'visible': true})) + .then(_assert('back to visible:true', 2)) + .then(done, done.fail); + }); + + it('should be able to restyle *maxdepth* and *level* w/o recomputing the hierarchy', function(done) { + var mock = Lib.extendDeep({}, require('@mocks/icicle_coffee.json')); + + function _assert(msg, exp) { + return function() { + var layer = d3Select(gd).select('.iciclelayer'); + + expect(layer.selectAll('.slice').size()).toBe(exp, msg); + + // editType:plot + if(msg !== 'base') { + expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); + } + }; + } + + Plotly.newPlot(gd, mock) + .then(_assert('base', 97)) + .then(function() { + spyOn(Plots, 'doCalcdata').and.callThrough(); + }) + .then(_restyle({maxdepth: 3})) + .then(_assert('with maxdepth:3', 97)) + .then(_restyle({level: 'Aromas'})) + .then(_assert('with non-root level', 67)) + .then(_restyle({maxdepth: null, level: null})) + .then(_assert('back to first view', 97)) + .then(done, done.fail); + }); + + it('should be able to restyle *leaf.opacity*', function(done) { + var mock = { + data: [{ + type: 'icicle', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + }] + }; + + function _assert(msg, exp) { + return function() { + var layer = d3Select(gd).select('.iciclelayer'); + + var opacities = []; + layer.selectAll('path.surface').each(function() { + opacities.push(this.style.opacity); + }); + + expect(opacities).toEqual(exp, msg); + + // editType:style + if(msg !== 'base') { + expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); + expect(gd._fullData[0]._module.plot).toHaveBeenCalledTimes(0); + } + }; + } + + Plotly.newPlot(gd, mock) + .then(_assert('base', ['', '0.7', '', '0.7'])) + .then(function() { + spyOn(Plots, 'doCalcdata').and.callThrough(); + spyOn(gd._fullData[0]._module, 'plot').and.callThrough(); + }) + .then(_restyle({'leaf.opacity': 0.3})) + .then(_assert('lower leaf.opacity', ['', '0.3', '', '0.3'])) + .then(_restyle({'leaf.opacity': 1})) + .then(_assert('raise leaf.opacity', ['', '1', '', '1'])) + .then(_restyle({'leaf.opacity': null})) + .then(_assert('back to dflt', ['', '0.7', '', '0.7'])) + .then(done, done.fail); + }); +}); + +describe('Test icicle texttemplate without `values` should work at root level:', function() { + checkTextTemplate([{ + type: 'icicle', + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve' ], + text: ['sixty-five', 'fourteen', 'twelve', 'ten', 'two', 'six', 'six', 'one', 'four'] + }], 'g.slicetext', [ + ['color: %{color}', ['color: rgba(0,0,0,0)', 'color: #1f77b4', 'color: #ff7f0e', 'color: #2ca02c', 'color: #d62728', 'color: #9467bd', 'color: #ff7f0e', 'color: #ff7f0e', 'color: #d62728']], + ['label: %{label}', ['label: Eve', 'label: Cain', 'label: Seth', 'label: Enos', 'label: Noam', 'label: Abel', 'label: Awan', 'label: Enoch', 'label: Azura']], + ['text: %{text}', ['text: sixty-five', 'text: fourteen', 'text: twelve', 'text: ten', 'text: two', 'text: six', 'text: six', 'text: one', 'text: four']], + ['path: %{currentPath}', ['path: /', 'path: Eve/', 'path: Eve/', 'path: Eve/', 'path: Eve/', 'path: Eve', 'path: Eve/Seth', 'path: Eve/Seth/', 'path: Eve/Awan/']], + ['%{percentRoot} of %{root}', ['100% of Eve', '33% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve']], + ['%{percentEntry} of %{entry}', ['100% of Eve', '33% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve']], + ['%{percentParent} of %{parent}', ['%{percentParent} of %{parent}', '100% of Seth', '33% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '17% of Eve', '50% of Seth', '100% of Awan']], + [ + [ + 'label: %{label}', + 'text: %{text}', + 'value: %{value}', + '%{percentRoot} of %{root}', + '%{percentEntry} of %{entry}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}', + 'color: %{color}' + ], + [ + 'label: Eve', + 'text: fourteen', + 'value: %{value}', // N.B. there is no `values` array + '17% of Eve', + '17% of Eve', + '17% of Eve', + '17% of Eve', + '100% of Awan', + 'color: #9467bd' + ] + ] + ]); +}); + +describe('Test icicle texttemplate with *total* `values` should work at root level:', function() { + checkTextTemplate([{ + type: 'icicle', + branchvalues: 'total', + labels: ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura'], + parents: ['', 'Eve', 'Eve', 'Seth', 'Seth', 'Eve', 'Eve', 'Awan', 'Eve' ], + values: [65, 14, 12, 10, 2, 6, 6, 1, 4], + text: ['sixty-five', 'fourteen', 'twelve', 'ten', 'two', 'six', 'six', 'one', 'four'] + }], 'g.slicetext', [ + ['color: %{color}', ['color: rgba(0,0,0,0)', 'color: #1f77b4', 'color: #ff7f0e', 'color: #2ca02c', 'color: #d62728', 'color: #9467bd', 'color: #ff7f0e', 'color: #ff7f0e', 'color: #d62728']], + ['label: %{label}', ['label: Eve', 'label: Cain', 'label: Seth', 'label: Enos', 'label: Noam', 'label: Abel', 'label: Awan', 'label: Enoch', 'label: Azura']], + ['value: %{value}', ['value: 65', 'value: 14', 'value: 12', 'value: 10', 'value: 2', 'value: 6', 'value: 6', 'value: 1', 'value: 4']], + ['text: %{text}', ['text: sixty-five', 'text: fourteen', 'text: twelve', 'text: ten', 'text: two', 'text: six', 'text: six', 'text: one', 'text: four']], + ['path: %{currentPath}', ['path: /', 'path: Eve/', 'path: Eve/', 'path: Eve/', 'path: Eve/', 'path: Eve', 'path: Eve/Seth', 'path: Eve/Seth/', 'path: Eve/Awan/']], + ['%{percentRoot} of %{root}', ['100% of Eve', '22% of Eve', '18% of Eve', '9% of Eve', '9% of Eve', '6% of Eve', '15% of Eve', '3% of Eve', '2% of Eve']], + ['%{percentEntry} of %{entry}', ['100% of Eve', '22% of Eve', '18% of Eve', '9% of Eve', '9% of Eve', '6% of Eve', '15% of Eve', '3% of Eve', '2% of Eve']], + ['%{percentParent} of %{parent}', ['%{percentParent} of %{parent}', '22% of Eve', '18% of Eve', '9% of Eve', '9% of Eve', '6% of Eve', '83% of Seth', '17% of Seth', '17% of Awan']], + [ + [ + 'label: %{label}', + 'text: %{text}', + 'value: %{value}', + '%{percentRoot} of %{root}', + '%{percentEntry} of %{entry}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}', + 'color: %{color}' + ], + [ + 'label: Eve', + 'text: fourteen', + 'value: 12', + '9% of Eve', + '15% of Eve', + '3% of Eve', + '6% of Eve', + '17% of Awan', + 'color: #9467bd' + ] + ] + ]); +}); + +describe('icicle pathbar react', function() { + 'use strict'; + + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(destroyGraphDiv); + + it('should show and hide pathbar', function(done) { + var fig = { + data: [{ + type: 'icicle', + parents: ['', 'A', 'B', 'C'], + labels: ['A', 'B', 'C', 'D'], + level: 'C' + }], + layout: {} + }; + + function _assert(msg, exp) { + return function() { + var selection = d3SelectAll(SLICES_SELECTOR); + var size = selection.size(); + + expect(size).toBe(exp, msg); + }; + } + + Plotly.newPlot(gd, fig) + .then(_assert('default pathbar.visible: true', 4)) + .then(function() { + fig.data[0].pathbar = {visible: false}; + return Plotly.react(gd, fig); + }) + .then(_assert('disable pathbar', 2)) + .then(function() { + fig.data[0].pathbar = {visible: true}; + return Plotly.react(gd, fig); + }) + .then(_assert('enable pathbar', 4)) + .then(done, done.fail); + }); +});