From e980e44ed2c1925f062d39f82a97a8a0f9b9ba88 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 29 Sep 2025 09:49:51 -0600 Subject: [PATCH 01/18] Linting/formatting --- src/components/drawing/index.js | 998 ++--- src/components/fx/hover.js | 1109 +++--- src/components/shapes/attributes.js | 97 +- src/components/shapes/defaults.js | 48 +- src/components/shapes/display_labels.js | 132 +- .../shapes/draw_newshape/attributes.js | 404 +- .../shapes/draw_newshape/defaults.js | 15 +- src/lib/index.js | 495 ++- src/plots/template_attributes.js | 37 +- src/traces/bar/attributes.js | 90 +- src/traces/bar/defaults.js | 50 +- src/traces/bar/plot.js | 613 +-- src/traces/barpolar/attributes.js | 12 +- src/traces/barpolar/defaults.js | 2 +- src/traces/box/attributes.js | 98 +- src/traces/box/defaults.js | 96 +- src/traces/choropleth/attributes.js | 130 +- src/traces/choropleth/defaults.js | 14 +- src/traces/choroplethmap/attributes.js | 158 +- src/traces/choroplethmap/defaults.js | 11 +- src/traces/choroplethmapbox/attributes.js | 158 +- src/traces/choroplethmapbox/defaults.js | 11 +- src/traces/cone/attributes.js | 40 +- src/traces/cone/defaults.js | 18 +- src/traces/contour/attributes.js | 429 ++- src/traces/contour/defaults.js | 12 +- src/traces/densitymap/attributes.js | 87 +- src/traces/densitymap/defaults.js | 4 +- src/traces/densitymapbox/attributes.js | 87 +- src/traces/densitymapbox/defaults.js | 4 +- src/traces/funnel/attributes.js | 40 +- src/traces/funnel/defaults.js | 14 +- src/traces/funnelarea/attributes.js | 30 +- src/traces/funnelarea/defaults.js | 14 +- src/traces/heatmap/attributes.js | 246 +- src/traces/heatmap/defaults.js | 7 +- src/traces/heatmap/plot.js | 253 +- src/traces/histogram/attributes.js | 36 +- src/traces/histogram/defaults.js | 17 +- src/traces/histogram2d/attributes.js | 21 +- src/traces/histogram2d/defaults.js | 5 +- src/traces/histogram2dcontour/attributes.js | 71 +- src/traces/histogram2dcontour/defaults.js | 8 +- src/traces/icicle/attributes.js | 42 +- src/traces/icicle/defaults.js | 18 +- src/traces/image/attributes.js | 39 +- src/traces/image/defaults.js | 12 +- src/traces/isosurface/attributes.js | 310 +- src/traces/mesh3d/attributes.js | 429 ++- src/traces/mesh3d/defaults.js | 30 +- src/traces/parcats/attributes.js | 77 +- src/traces/parcats/defaults.js | 21 +- src/traces/pie/attributes.js | 98 +- src/traces/pie/defaults.js | 47 +- src/traces/pie/plot.js | 561 +-- src/traces/sankey/attributes.js | 477 +-- src/traces/scatter/attributes.js | 325 +- src/traces/scatter/defaults.js | 36 +- src/traces/scatter3d/attributes.js | 251 +- src/traces/scatter3d/convert.js | 205 +- src/traces/scatter3d/defaults.js | 26 +- src/traces/scattercarpet/attributes.js | 52 +- src/traces/scattercarpet/defaults.js | 22 +- src/traces/scattergeo/attributes.js | 281 +- src/traces/scattergeo/defaults.js | 22 +- src/traces/scattergl/attributes.js | 166 +- src/traces/scattergl/convert.js | 268 +- src/traces/scattergl/defaults.js | 18 +- src/traces/scattermap/attributes.js | 292 +- src/traces/scattermap/convert.js | 203 +- src/traces/scattermap/defaults.js | 49 +- src/traces/scattermapbox/attributes.js | 292 +- src/traces/scattermapbox/convert.js | 203 +- src/traces/scattermapbox/defaults.js | 49 +- src/traces/scatterpolar/attributes.js | 13 +- src/traces/scatterpolar/defaults.js | 32 +- src/traces/scatterpolargl/attributes.js | 9 +- src/traces/scatterpolargl/defaults.js | 16 +- src/traces/scattersmith/attributes.js | 11 +- src/traces/scattersmith/defaults.js | 28 +- src/traces/scatterternary/attributes.js | 54 +- src/traces/scatterternary/defaults.js | 33 +- src/traces/splom/attributes.js | 30 +- src/traces/splom/defaults.js | 46 +- src/traces/streamtube/attributes.js | 46 +- src/traces/streamtube/defaults.js | 18 +- src/traces/sunburst/attributes.js | 115 +- src/traces/sunburst/defaults.js | 16 +- src/traces/sunburst/plot.js | 274 +- src/traces/surface/attributes.js | 378 +- src/traces/surface/defaults.js | 60 +- src/traces/treemap/attributes.js | 197 +- src/traces/treemap/defaults.js | 20 +- src/traces/violin/attributes.js | 24 +- src/traces/volume/attributes.js | 118 +- src/traces/waterfall/attributes.js | 42 +- src/traces/waterfall/defaults.js | 17 +- test/jasmine/tests/icicle_test.js | 1048 ++++-- test/jasmine/tests/lib_test.js | 2039 +++++----- test/jasmine/tests/sunburst_test.js | 3308 +++++++++++------ test/jasmine/tests/treemap_test.js | 2484 +++++++------ 101 files changed, 11769 insertions(+), 9849 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 3cb3b447eeb..659e45bded7 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -21,13 +21,13 @@ var subTypes = require('../../traces/scatter/subtypes'); var makeBubbleSizeFn = require('../../traces/scatter/make_bubble_size_func'); var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; -var drawing = module.exports = {}; +var drawing = (module.exports = {}); // ----------------------------------------------------- // styling functions for plot elements // ----------------------------------------------------- -drawing.font = function(s, font) { +drawing.font = function (s, font) { var variant = font.variant; var style = font.style; var weight = font.weight; @@ -38,17 +38,21 @@ drawing.font = function(s, font) { var lineposition = font.lineposition; var textcase = font.textcase; - if(family) s.style('font-family', family); - if(size + 1) s.style('font-size', size + 'px'); - if(color) s.call(Color.fill, color); + if (family) s.style('font-family', family); + if (size + 1) s.style('font-size', size + 'px'); + if (color) s.call(Color.fill, color); - if(weight) s.style('font-weight', weight); - if(style) s.style('font-style', style); - if(variant) s.style('font-variant', variant); + if (weight) s.style('font-weight', weight); + if (style) s.style('font-style', style); + if (variant) s.style('font-variant', variant); - if(textcase) s.style('text-transform', dropNone(textcase2transform(textcase))); - if(shadow) s.style('text-shadow', shadow === 'auto' ? svgTextUtils.makeTextShadow(Color.contrast(color)) : dropNone(shadow)); - if(lineposition) s.style('text-decoration-line', dropNone(lineposition2decorationLine(lineposition))); + if (textcase) s.style('text-transform', dropNone(textcase2transform(textcase))); + if (shadow) + s.style( + 'text-shadow', + shadow === 'auto' ? svgTextUtils.makeTextShadow(Color.contrast(color)) : dropNone(shadow) + ); + if (lineposition) s.style('text-decoration-line', dropNone(lineposition2decorationLine(lineposition))); }; function dropNone(a) { @@ -67,14 +71,12 @@ function textcase2transform(textcase) { } function lineposition2decorationLine(lineposition) { - return ( - lineposition - .replace('under', 'underline') - .replace('over', 'overline') - .replace('through', 'line-through') - .split('+') - .join(' ') - ); + return lineposition + .replace('under', 'underline') + .replace('over', 'overline') + .replace('through', 'line-through') + .split('+') + .join(' '); } /* @@ -83,9 +85,13 @@ function lineposition2decorationLine(lineposition) { * `svgTextUtils.convertToTspans`. Use `svgTextUtils.positionText` * instead, so that elements get updated to match. */ -drawing.setPosition = function(s, x, y) { s.attr('x', x).attr('y', y); }; -drawing.setSize = function(s, w, h) { s.attr('width', w).attr('height', h); }; -drawing.setRect = function(s, x, y, w, h) { +drawing.setPosition = function (s, x, y) { + s.attr('x', x).attr('y', y); +}; +drawing.setSize = function (s, w, h) { + s.attr('width', w).attr('height', h); +}; +drawing.setRect = function (s, x, y, w, h) { s.call(drawing.setPosition, x, y).call(drawing.setSize, w, h); }; @@ -100,13 +106,13 @@ drawing.setRect = function(s, x, y, w, h) { * true if selection got translated * false if selection could not get translated */ -drawing.translatePoint = function(d, sel, xa, ya) { +drawing.translatePoint = function (d, sel, xa, ya) { var x = xa.c2p(d.x); var y = ya.c2p(d.y); - if(isNumeric(x) && isNumeric(y) && sel.node()) { + if (isNumeric(x) && isNumeric(y) && sel.node()) { // for multiline text this works better - if(sel.node().nodeName === 'text') { + if (sel.node().nodeName === 'text') { sel.attr('x', x).attr('y', y); } else { sel.attr('transform', strTranslate(x, y)); @@ -118,52 +124,49 @@ drawing.translatePoint = function(d, sel, xa, ya) { return true; }; -drawing.translatePoints = function(s, xa, ya) { - s.each(function(d) { +drawing.translatePoints = function (s, xa, ya) { + s.each(function (d) { var sel = d3.select(this); drawing.translatePoint(d, sel, xa, ya); }); }; -drawing.hideOutsideRangePoint = function(d, sel, xa, ya, xcalendar, ycalendar) { - sel.attr( - 'display', - (xa.isPtWithinRange(d, xcalendar) && ya.isPtWithinRange(d, ycalendar)) ? null : 'none' - ); +drawing.hideOutsideRangePoint = function (d, sel, xa, ya, xcalendar, ycalendar) { + sel.attr('display', xa.isPtWithinRange(d, xcalendar) && ya.isPtWithinRange(d, ycalendar) ? null : 'none'); }; -drawing.hideOutsideRangePoints = function(traceGroups, subplot) { - if(!subplot._hasClipOnAxisFalse) return; +drawing.hideOutsideRangePoints = function (traceGroups, subplot) { + if (!subplot._hasClipOnAxisFalse) return; var xa = subplot.xaxis; var ya = subplot.yaxis; - traceGroups.each(function(d) { + traceGroups.each(function (d) { var trace = d[0].trace; var xcalendar = trace.xcalendar; var ycalendar = trace.ycalendar; var selector = Registry.traceIs(trace, 'bar-like') ? '.bartext' : '.point,.textpoint'; - traceGroups.selectAll(selector).each(function(d) { + traceGroups.selectAll(selector).each(function (d) { drawing.hideOutsideRangePoint(d, d3.select(this), xa, ya, xcalendar, ycalendar); }); }); }; -drawing.crispRound = function(gd, lineWidth, dflt) { +drawing.crispRound = function (gd, lineWidth, dflt) { // for lines that disable antialiasing we want to // make sure the width is an integer, and at least 1 if it's nonzero - if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0; + if (!lineWidth || !isNumeric(lineWidth)) return dflt || 0; // but not for static plots - these don't get antialiased anyway. - if(gd._context.staticPlot) return lineWidth; + if (gd._context.staticPlot) return lineWidth; - if(lineWidth < 1) return 1; + if (lineWidth < 1) return 1; return Math.round(lineWidth); }; -drawing.singleLineStyle = function(d, s, lw, lc, ld) { +drawing.singleLineStyle = function (d, s, lw, lc, ld) { s.style('fill', 'none'); var line = (((d || [])[0] || {}).trace || {}).line || {}; var lw1 = lw || line.width || 0; @@ -173,9 +176,8 @@ drawing.singleLineStyle = function(d, s, lw, lc, ld) { drawing.dashLine(s, dash, lw1); }; -drawing.lineGroupStyle = function(s, lw, lc, ld) { - s.style('fill', 'none') - .each(function(d) { +drawing.lineGroupStyle = function (s, lw, lc, ld) { + s.style('fill', 'none').each(function (d) { var line = (((d || [])[0] || {}).trace || {}).line || {}; var lw1 = lw || line.width || 0; var dash = ld || line.dash || ''; @@ -186,7 +188,7 @@ drawing.lineGroupStyle = function(s, lw, lc, ld) { }); }; -drawing.dashLine = function(s, dash, lineWidth) { +drawing.dashLine = function (s, dash, lineWidth) { lineWidth = +lineWidth || 0; dash = drawing.dashStyle(dash, lineWidth); @@ -197,18 +199,18 @@ drawing.dashLine = function(s, dash, lineWidth) { }); }; -drawing.dashStyle = function(dash, lineWidth) { +drawing.dashStyle = function (dash, lineWidth) { lineWidth = +lineWidth || 1; var dlw = Math.max(lineWidth, 3); - if(dash === 'solid') dash = ''; - else if(dash === 'dot') dash = dlw + 'px,' + dlw + 'px'; - else if(dash === 'dash') dash = (3 * dlw) + 'px,' + (3 * dlw) + 'px'; - else if(dash === 'longdash') dash = (5 * dlw) + 'px,' + (5 * dlw) + 'px'; - else if(dash === 'dashdot') { - dash = (3 * dlw) + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px'; - } else if(dash === 'longdashdot') { - dash = (5 * dlw) + 'px,' + (2 * dlw) + 'px,' + dlw + 'px,' + (2 * dlw) + 'px'; + if (dash === 'solid') dash = ''; + else if (dash === 'dot') dash = dlw + 'px,' + dlw + 'px'; + else if (dash === 'dash') dash = 3 * dlw + 'px,' + 3 * dlw + 'px'; + else if (dash === 'longdash') dash = 5 * dlw + 'px,' + 5 * dlw + 'px'; + else if (dash === 'dashdot') { + dash = 3 * dlw + 'px,' + dlw + 'px,' + dlw + 'px,' + dlw + 'px'; + } else if (dash === 'longdashdot') { + dash = 5 * dlw + 'px,' + 2 * dlw + 'px,' + dlw + 'px,' + 2 * dlw + 'px'; } // otherwise user wrote the dasharray themselves - leave it be @@ -220,87 +222,98 @@ function setFillStyle(sel, trace, gd, forLegend) { var fillgradient = trace.fillgradient; var pAttr = drawing.getPatternAttr; var patternShape = markerPattern && (pAttr(markerPattern.shape, 0, '') || pAttr(markerPattern.path, 0, '')); - if(patternShape) { + if (patternShape) { var patternBGColor = pAttr(markerPattern.bgcolor, 0, null); var patternFGColor = pAttr(markerPattern.fgcolor, 0, null); var patternFGOpacity = markerPattern.fgopacity; var patternSize = pAttr(markerPattern.size, 0, 8); var patternSolidity = pAttr(markerPattern.solidity, 0, 0.3); var patternID = trace.uid; - drawing.pattern(sel, 'point', gd, patternID, - patternShape, patternSize, patternSolidity, - undefined, markerPattern.fillmode, - patternBGColor, patternFGColor, patternFGOpacity + drawing.pattern( + sel, + 'point', + gd, + patternID, + patternShape, + patternSize, + patternSolidity, + undefined, + markerPattern.fillmode, + patternBGColor, + patternFGColor, + patternFGOpacity ); - } else if(fillgradient && fillgradient.type !== 'none') { + } else if (fillgradient && fillgradient.type !== 'none') { var direction = fillgradient.type; var gradientID = 'scatterfill-' + trace.uid; - if(forLegend) { + if (forLegend) { gradientID = 'legendfill-' + trace.uid; } - if(!forLegend && (fillgradient.start !== undefined || fillgradient.stop !== undefined)) { + if (!forLegend && (fillgradient.start !== undefined || fillgradient.stop !== undefined)) { var start, stop; - if(direction === 'horizontal') { + if (direction === 'horizontal') { start = { x: fillgradient.start, - y: 0, + y: 0 }; stop = { x: fillgradient.stop, - y: 0, + y: 0 }; - } else if(direction === 'vertical') { + } else if (direction === 'vertical') { start = { x: 0, - y: fillgradient.start, + y: fillgradient.start }; stop = { x: 0, - y: fillgradient.stop, + y: fillgradient.stop }; } - start.x = trace._xA.c2p( - (start.x === undefined) ? trace._extremes.x.min[0].val : start.x, true - ); - start.y = trace._yA.c2p( - (start.y === undefined) ? trace._extremes.y.min[0].val : start.y, true - ); - - stop.x = trace._xA.c2p( - (stop.x === undefined) ? trace._extremes.x.max[0].val : stop.x, true + start.x = trace._xA.c2p(start.x === undefined ? trace._extremes.x.min[0].val : start.x, true); + start.y = trace._yA.c2p(start.y === undefined ? trace._extremes.y.min[0].val : start.y, true); + + stop.x = trace._xA.c2p(stop.x === undefined ? trace._extremes.x.max[0].val : stop.x, true); + stop.y = trace._yA.c2p(stop.y === undefined ? trace._extremes.y.max[0].val : stop.y, true); + sel.call( + gradientWithBounds, + gd, + gradientID, + 'linear', + fillgradient.colorscale, + 'fill', + start, + stop, + true, + false ); - stop.y = trace._yA.c2p( - (stop.y === undefined) ? trace._extremes.y.max[0].val : stop.y, true - ); - sel.call(gradientWithBounds, gd, gradientID, 'linear', fillgradient.colorscale, 'fill', start, stop, true, false); } else { - if(direction === 'horizontal') { + if (direction === 'horizontal') { direction = direction + 'reversed'; } sel.call(drawing.gradient, gd, gradientID, direction, fillgradient.colorscale, 'fill'); } - } else if(trace.fillcolor) { + } else if (trace.fillcolor) { sel.call(Color.fill, trace.fillcolor); } } // Same as fillGroupStyle, except in this case the selection may be a transition -drawing.singleFillStyle = function(sel, gd) { +drawing.singleFillStyle = function (sel, gd) { var node = d3.select(sel.node()); var data = node.data(); var trace = ((data[0] || [])[0] || {}).trace || {}; setFillStyle(sel, trace, gd, false); }; -drawing.fillGroupStyle = function(s, gd, forLegend) { - s.style('stroke-width', 0) - .each(function(d) { +drawing.fillGroupStyle = function (s, gd, forLegend) { + s.style('stroke-width', 0).each(function (d) { var shape = d3.select(this); // N.B. 'd' won't be a calcdata item when // fill !== 'none' on a segment-less and marker-less trace - if(d[0].trace) { + if (d[0].trace) { setFillStyle(shape, d[0].trace, gd, forLegend); } }); @@ -316,7 +329,7 @@ drawing.symbolNoDot = {}; drawing.symbolNoFill = {}; drawing.symbolList = []; -Object.keys(SYMBOLDEFS).forEach(function(k) { +Object.keys(SYMBOLDEFS).forEach(function (k) { var symDef = SYMBOLDEFS[k]; var n = symDef.n; drawing.symbolList.push( @@ -332,10 +345,10 @@ Object.keys(SYMBOLDEFS).forEach(function(k) { drawing.symbolFuncs[n] = symDef.f; drawing.symbolBackOffs[n] = symDef.backoff || 0; - if(symDef.needLine) { + if (symDef.needLine) { drawing.symbolNeedLines[n] = true; } - if(symDef.noDot) { + if (symDef.noDot) { drawing.symbolNoDot[n] = true; } else { drawing.symbolList.push( @@ -348,7 +361,7 @@ Object.keys(SYMBOLDEFS).forEach(function(k) { k + '-open-dot' ); } - if(symDef.noFill) { + if (symDef.noFill) { drawing.symbolNoFill[n] = true; } }); @@ -357,25 +370,26 @@ var MAXSYMBOL = drawing.symbolNames.length; // add a dot in the middle of the symbol var DOTPATH = 'M0,0.5L0.5,0L0,-0.5L-0.5,0Z'; -drawing.symbolNumber = function(v) { - if(isNumeric(v)) { +drawing.symbolNumber = function (v) { + if (isNumeric(v)) { v = +v; - } else if(typeof v === 'string') { + } else if (typeof v === 'string') { var vbase = 0; - if(v.indexOf('-open') > 0) { + if (v.indexOf('-open') > 0) { vbase = 100; v = v.replace('-open', ''); } - if(v.indexOf('-dot') > 0) { + if (v.indexOf('-dot') > 0) { vbase += 200; v = v.replace('-dot', ''); } v = drawing.symbolNames.indexOf(v); - if(v >= 0) { v += vbase; } + if (v >= 0) { + v += vbase; + } } - return (v % 100 >= MAXSYMBOL || v >= 400) ? - 0 : Math.floor(Math.max(v, 0)); + return v % 100 >= MAXSYMBOL || v >= 400 ? 0 : Math.floor(Math.max(v, 0)); }; function makePointPath(symbolNumber, r, t, s) { @@ -385,12 +399,12 @@ function makePointPath(symbolNumber, r, t, s) { var stopFormatter = numberFormat('~f'); var gradientInfo = { - radial: {type: 'radial'}, - radialreversed: {type: 'radial', reversed: true}, - horizontal: {type: 'linear', start: {x: 1, y: 0}, stop: {x: 0, y: 0}}, - horizontalreversed: {type: 'linear', start: {x: 1, y: 0}, stop: {x: 0, y: 0}, reversed: true}, - vertical: {type: 'linear', start: {x: 0, y: 1}, stop: {x: 0, y: 0}}, - verticalreversed: {type: 'linear', start: {x: 0, y: 1}, stop: {x: 0, y: 0}, reversed: true} + radial: { type: 'radial' }, + radialreversed: { type: 'radial', reversed: true }, + horizontal: { type: 'linear', start: { x: 1, y: 0 }, stop: { x: 0, y: 0 } }, + horizontalreversed: { type: 'linear', start: { x: 1, y: 0 }, stop: { x: 0, y: 0 }, reversed: true }, + vertical: { type: 'linear', start: { x: 0, y: 1 }, stop: { x: 0, y: 0 } }, + verticalreversed: { type: 'linear', start: { x: 0, y: 1 }, stop: { x: 0, y: 0 }, reversed: true } }; /** @@ -407,10 +421,19 @@ var gradientInfo = { * @param {array} colorscale: as in attribute values, [[fraction, color], ...] * @param {string} prop: the property to apply to, 'fill' or 'stroke' */ -drawing.gradient = function(sel, gd, gradientID, type, colorscale, prop) { +drawing.gradient = function (sel, gd, gradientID, type, colorscale, prop) { var info = gradientInfo[type]; return gradientWithBounds( - sel, gd, gradientID, info.type, colorscale, prop, info.start, info.stop, false, info.reversed + sel, + gd, + gradientID, + info.type, + colorscale, + prop, + info.start, + info.stop, + false, + info.reversed ); }; @@ -440,7 +463,7 @@ function gradientWithBounds(sel, gd, gradientID, type, colorscale, prop, start, var len = colorscale.length; var info; - if(type === 'linear') { + if (type === 'linear') { info = { node: 'linearGradient', attrs: { @@ -448,20 +471,20 @@ function gradientWithBounds(sel, gd, gradientID, type, colorscale, prop, start, y1: start.y, x2: stop.x, y2: stop.y, - gradientUnits: inUserSpace ? 'userSpaceOnUse' : 'objectBoundingBox', + gradientUnits: inUserSpace ? 'userSpaceOnUse' : 'objectBoundingBox' }, - reversed: reversed, + reversed: reversed }; - } else if(type === 'radial') { + } else if (type === 'radial') { info = { node: 'radialGradient', - reversed: reversed, + reversed: reversed }; } var colorStops = new Array(len); - for(var i = 0; i < len; i++) { - if(info.reversed) { + for (var i = 0; i < len; i++) { + if (info.reversed) { colorStops[len - 1 - i] = [stopFormatter((1 - colorscale[i][0]) * 100), colorscale[i][1]]; } else { colorStops[i] = [stopFormatter(colorscale[i][0] * 100), colorscale[i][1]]; @@ -471,26 +494,27 @@ function gradientWithBounds(sel, gd, gradientID, type, colorscale, prop, start, var fullLayout = gd._fullLayout; var fullID = 'g' + fullLayout._uid + '-' + gradientID; - var gradient = fullLayout._defs.select('.gradients') + var gradient = fullLayout._defs + .select('.gradients') .selectAll('#' + fullID) .data([type + colorStops.join(';')], Lib.identity); gradient.exit().remove(); - gradient.enter() + gradient + .enter() .append(info.node) - .each(function() { + .each(function () { var el = d3.select(this); - if(info.attrs) el.attr(info.attrs); + if (info.attrs) el.attr(info.attrs); el.attr('id', fullID); - var stops = el.selectAll('stop') - .data(colorStops); + var stops = el.selectAll('stop').data(colorStops); stops.exit().remove(); stops.enter().append('stop'); - stops.each(function(d) { + stops.each(function (d) { var tc = tinycolor(d[1]); d3.select(this).attr({ offset: d[0] + '%', @@ -500,8 +524,7 @@ function gradientWithBounds(sel, gd, gradientID, type, colorscale, prop, start, }); }); - sel.style(prop, getFullUrl(fullID, gd)) - .style(prop + '-opacity', null); + sel.style(prop, getFullUrl(fullID, gd)).style(prop + '-opacity', null); sel.classed('gradient_filled', true); } @@ -523,11 +546,24 @@ function gradientWithBounds(sel, gd, gradientID, type, colorscale, prop, start, * @param {string} fgcolor: foreground color for this pattern * @param {number} fgopacity: foreground opacity for this pattern */ -drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, mcc, fillmode, bgcolor, fgcolor, fgopacity) { +drawing.pattern = function ( + sel, + calledBy, + gd, + patternID, + shape, + size, + solidity, + mcc, + fillmode, + bgcolor, + fgcolor, + fgopacity +) { var isLegend = calledBy === 'legend'; - if(mcc) { - if(fillmode === 'overlay') { + if (mcc) { + if (fillmode === 'overlay') { bgcolor = mcc; fgcolor = Color.contrast(bgcolor); } else { @@ -541,8 +577,8 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, var width, height; // linear interpolation - var linearFn = function(x, x0, x1, y0, y1) { - return y0 + (y1 - y0) * (x - x0) / (x1 - x0); + var linearFn = function (x, x0, x1, y0, y1) { + return y0 + ((y1 - y0) * (x - x0)) / (x1 - x0); }; var path, linewidth, radius; @@ -554,13 +590,32 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, var fgAlpha = fgC.getAlpha(); var opacity = fgopacity * fgAlpha; - switch(shape) { + switch (shape) { case '/': width = size * Math.sqrt(2); height = size * Math.sqrt(2); - path = 'M-' + (width / 4) + ',' + (height / 4) + 'l' + (width / 2) + ',-' + (height / 2) + - 'M0,' + height + 'L' + width + ',0' + - 'M' + (width / 4 * 3) + ',' + (height / 4 * 5) + 'l' + (width / 2) + ',-' + (height / 2); + path = + 'M-' + + width / 4 + + ',' + + height / 4 + + 'l' + + width / 2 + + ',-' + + height / 2 + + 'M0,' + + height + + 'L' + + width + + ',0' + + 'M' + + (width / 4) * 3 + + ',' + + (height / 4) * 5 + + 'l' + + width / 2 + + ',-' + + height / 2; linewidth = solidity * size; patternTag = 'path'; patternAttrs = { @@ -573,9 +628,27 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, case '\\': width = size * Math.sqrt(2); height = size * Math.sqrt(2); - path = 'M' + (width / 4 * 3) + ',-' + (height / 4) + 'l' + (width / 2) + ',' + (height / 2) + - 'M0,0L' + width + ',' + height + - 'M-' + (width / 4) + ',' + (height / 4 * 3) + 'l' + (width / 2) + ',' + (height / 2); + path = + 'M' + + (width / 4) * 3 + + ',-' + + height / 4 + + 'l' + + width / 2 + + ',' + + height / 2 + + 'M0,0L' + + width + + ',' + + height + + 'M-' + + width / 4 + + ',' + + (height / 4) * 3 + + 'l' + + width / 2 + + ',' + + height / 2; linewidth = solidity * size; patternTag = 'path'; patternAttrs = { @@ -588,12 +661,48 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, case 'x': width = size * Math.sqrt(2); height = size * Math.sqrt(2); - path = 'M-' + (width / 4) + ',' + (height / 4) + 'l' + (width / 2) + ',-' + (height / 2) + - 'M0,' + height + 'L' + width + ',0' + - 'M' + (width / 4 * 3) + ',' + (height / 4 * 5) + 'l' + (width / 2) + ',-' + (height / 2) + - 'M' + (width / 4 * 3) + ',-' + (height / 4) + 'l' + (width / 2) + ',' + (height / 2) + - 'M0,0L' + width + ',' + height + - 'M-' + (width / 4) + ',' + (height / 4 * 3) + 'l' + (width / 2) + ',' + (height / 2); + path = + 'M-' + + width / 4 + + ',' + + height / 4 + + 'l' + + width / 2 + + ',-' + + height / 2 + + 'M0,' + + height + + 'L' + + width + + ',0' + + 'M' + + (width / 4) * 3 + + ',' + + (height / 4) * 5 + + 'l' + + width / 2 + + ',-' + + height / 2 + + 'M' + + (width / 4) * 3 + + ',-' + + height / 4 + + 'l' + + width / 2 + + ',' + + height / 2 + + 'M0,0L' + + width + + ',' + + height + + 'M-' + + width / 4 + + ',' + + (height / 4) * 3 + + 'l' + + width / 2 + + ',' + + height / 2; linewidth = size - size * Math.sqrt(1.0 - solidity); patternTag = 'path'; patternAttrs = { @@ -607,7 +716,7 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, width = size; height = size; patternTag = 'path'; - path = 'M' + (width / 2) + ',0L' + (width / 2) + ',' + height; + path = 'M' + width / 2 + ',0L' + width / 2 + ',' + height; linewidth = solidity * size; patternTag = 'path'; patternAttrs = { @@ -621,7 +730,7 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, width = size; height = size; patternTag = 'path'; - path = 'M0,' + (height / 2) + 'L' + width + ',' + (height / 2); + path = 'M0,' + height / 2 + 'L' + width + ',' + height / 2; linewidth = solidity * size; patternTag = 'path'; patternAttrs = { @@ -635,8 +744,19 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, width = size; height = size; patternTag = 'path'; - path = 'M' + (width / 2) + ',0L' + (width / 2) + ',' + height + - 'M0,' + (height / 2) + 'L' + width + ',' + (height / 2); + path = + 'M' + + width / 2 + + ',0L' + + width / 2 + + ',' + + height + + 'M0,' + + height / 2 + + 'L' + + width + + ',' + + height / 2; linewidth = size - size * Math.sqrt(1.0 - solidity); patternTag = 'path'; patternAttrs = { @@ -649,8 +769,8 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, case '.': width = size; height = size; - if(solidity < Math.PI / 4) { - radius = Math.sqrt(solidity * size * size / Math.PI); + if (solidity < Math.PI / 4) { + radius = Math.sqrt((solidity * size * size) / Math.PI); } else { radius = linearFn(solidity, Math.PI / 4, 1.0, size / 2, size / Math.sqrt(2)); } @@ -675,23 +795,19 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, break; } - var str = [ - shape || 'noSh', - bgcolor || 'noBg', - fgcolor || 'noFg', - size, - solidity - ].join(';'); + var str = [shape || 'noSh', bgcolor || 'noBg', fgcolor || 'noFg', size, solidity].join(';'); - var pattern = fullLayout._defs.select('.patterns') + var pattern = fullLayout._defs + .select('.patterns') .selectAll('#' + fullID) .data([str], Lib.identity); pattern.exit().remove(); - pattern.enter() + pattern + .enter() .append('pattern') - .each(function() { + .each(function () { var el = d3.select(this); el.attr({ @@ -703,32 +819,30 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, patternTransform: isLegend ? 'scale(0.8)' : '' }); - if(bgcolor) { + if (bgcolor) { var bgC = tinycolor(bgcolor); var bgRGB = Color.tinyRGB(bgC); var bgAlpha = bgC.getAlpha(); var rects = el.selectAll('rect').data([0]); rects.exit().remove(); - rects.enter() + rects + .enter() .append('rect') .attr({ width: width + 'px', height: height + 'px', fill: bgRGB, - 'fill-opacity': bgAlpha, + 'fill-opacity': bgAlpha }); } var patterns = el.selectAll(patternTag).data([0]); patterns.exit().remove(); - patterns.enter() - .append(patternTag) - .attr(patternAttrs); + patterns.enter().append(patternTag).attr(patternAttrs); }); - sel.style('fill', getFullUrl(fullID, gd)) - .style('fill-opacity', null); + sel.style('fill', getFullUrl(fullID, gd)).style('fill-opacity', null); sel.classed('pattern_filled', true); }; @@ -740,7 +854,7 @@ drawing.pattern = function(sel, calledBy, gd, patternID, shape, size, solidity, * except all at once before a full redraw. * The upside of this is arbitrary points can share gradient defs */ -drawing.initGradients = function(gd) { +drawing.initGradients = function (gd) { var fullLayout = gd._fullLayout; var gradientsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'gradients'); @@ -749,7 +863,7 @@ drawing.initGradients = function(gd) { d3.select(gd).selectAll('.gradient_filled').classed('gradient_filled', false); }; -drawing.initPatterns = function(gd) { +drawing.initPatterns = function (gd) { var fullLayout = gd._fullLayout; var patternsGroup = Lib.ensureSingle(fullLayout._defs, 'g', 'patterns'); @@ -758,39 +872,36 @@ drawing.initPatterns = function(gd) { d3.select(gd).selectAll('.pattern_filled').classed('pattern_filled', false); }; -drawing.getPatternAttr = function(mp, i, dflt) { - if(mp && Lib.isArrayOrTypedArray(mp)) { +drawing.getPatternAttr = function (mp, i, dflt) { + if (mp && Lib.isArrayOrTypedArray(mp)) { return i < mp.length ? mp[i] : dflt; } return mp; }; -drawing.pointStyle = function(s, trace, gd, pt) { - if(!s.size()) return; +drawing.pointStyle = function (s, trace, gd, pt) { + if (!s.size()) return; var fns = drawing.makePointStyleFns(trace); - s.each(function(d) { + s.each(function (d) { drawing.singlePointStyle(d, d3.select(this), trace, fns, gd, pt); }); }; -drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) { +drawing.singlePointStyle = function (d, sel, trace, fns, gd, pt) { var marker = trace.marker; var markerLine = marker.line; - if(pt && pt.i >= 0 && d.i === undefined) d.i = pt.i; + if (pt && pt.i >= 0 && d.i === undefined) d.i = pt.i; - sel.style('opacity', - fns.selectedOpacityFn ? fns.selectedOpacityFn(d) : - (d.mo === undefined ? marker.opacity : d.mo) - ); + sel.style('opacity', fns.selectedOpacityFn ? fns.selectedOpacityFn(d) : d.mo === undefined ? marker.opacity : d.mo); - if(fns.ms2mrc) { + if (fns.ms2mrc) { var r; // handle multi-trace graph edit case - if(d.ms === 'various' || marker.size === 'various') { + if (d.ms === 'various' || marker.size === 'various') { r = 3; } else { r = fns.ms2mrc(d.ms); @@ -799,7 +910,7 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) { // store the calculated size so hover can use it d.mrc = r; - if(fns.selectedSizeFn) { + if (fns.selectedSizeFn) { r = d.mrc = fns.selectedSizeFn(d); } @@ -820,85 +931,90 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) { var fillColor, lineColor, lineWidth; // 'so' is suspected outliers, for box plots - if(d.so) { + if (d.so) { lineWidth = markerLine.outlierwidth; lineColor = markerLine.outliercolor; fillColor = marker.outliercolor; } else { var markerLineWidth = (markerLine || {}).width; - lineWidth = ( - d.mlw + 1 || - markerLineWidth + 1 || - // TODO: we need the latter for legends... can we get rid of it? - (d.trace ? (d.trace.marker.line || {}).width : 0) + 1 - ) - 1 || 0; + lineWidth = + (d.mlw + 1 || + markerLineWidth + 1 || + // TODO: we need the latter for legends... can we get rid of it? + (d.trace ? (d.trace.marker.line || {}).width : 0) + 1) - 1 || 0; - if('mlc' in d) lineColor = d.mlcc = fns.lineScale(d.mlc); + if ('mlc' in d) lineColor = d.mlcc = fns.lineScale(d.mlc); // weird case: array wasn't long enough to apply to every point - else if(Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine; + else if (Lib.isArrayOrTypedArray(markerLine.color)) lineColor = Color.defaultLine; else lineColor = markerLine.color; - if(Lib.isArrayOrTypedArray(marker.color)) { + if (Lib.isArrayOrTypedArray(marker.color)) { fillColor = Color.defaultLine; perPointGradient = true; } - if('mc' in d) { + if ('mc' in d) { fillColor = d.mcc = fns.markerScale(d.mc); } else { fillColor = marker.color || marker.colors || 'rgba(0,0,0,0)'; } - if(fns.selectedColorFn) { + if (fns.selectedColorFn) { fillColor = fns.selectedColorFn(d); } } - if(d.om) { + if (d.om) { // open markers can't have zero linewidth, default to 1px, // and use fill color as stroke color - sel.call(Color.stroke, fillColor) - .style({ - 'stroke-width': (lineWidth || 1) + 'px', - fill: 'none' - }); + sel.call(Color.stroke, fillColor).style({ + 'stroke-width': (lineWidth || 1) + 'px', + fill: 'none' + }); } else { sel.style('stroke-width', (d.isBlank ? 0 : lineWidth) + 'px'); var markerGradient = marker.gradient; var gradientType = d.mgt; - if(gradientType) perPointGradient = true; + if (gradientType) perPointGradient = true; else gradientType = markerGradient && markerGradient.type; // for legend - arrays will propagate through here, but we don't need // to treat it as per-point. - if(Lib.isArrayOrTypedArray(gradientType)) { + if (Lib.isArrayOrTypedArray(gradientType)) { gradientType = gradientType[0]; - if(!gradientInfo[gradientType]) gradientType = 0; + if (!gradientInfo[gradientType]) gradientType = 0; } var markerPattern = marker.pattern; var pAttr = drawing.getPatternAttr; - var patternShape = markerPattern && ( - pAttr(markerPattern.shape, d.i, '') || pAttr(markerPattern.path, d.i, '') - ); + var patternShape = markerPattern && (pAttr(markerPattern.shape, d.i, '') || pAttr(markerPattern.path, d.i, '')); - if(gradientType && gradientType !== 'none') { + if (gradientType && gradientType !== 'none') { var gradientColor = d.mgc; - if(gradientColor) perPointGradient = true; + if (gradientColor) perPointGradient = true; else gradientColor = markerGradient.color; var gradientID = trace.uid; - if(perPointGradient) gradientID += '-' + d.i; - - drawing.gradient(sel, gd, gradientID, gradientType, - [[0, gradientColor], [1, fillColor]], 'fill'); - } else if(patternShape) { + if (perPointGradient) gradientID += '-' + d.i; + + drawing.gradient( + sel, + gd, + gradientID, + gradientType, + [ + [0, gradientColor], + [1, fillColor] + ], + 'fill' + ); + } else if (patternShape) { var perPointPattern = false; var fgcolor = markerPattern.fgcolor; - if(!fgcolor && pt && pt.color) { + if (!fgcolor && pt && pt.color) { fgcolor = pt.color; perPointPattern = true; } @@ -908,7 +1024,9 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) { var patternFGOpacity = markerPattern.fgopacity; var patternSize = pAttr(markerPattern.size, d.i, 8); var patternSolidity = pAttr(markerPattern.solidity, d.i, 0.3); - perPointPattern = perPointPattern || d.mcc || + perPointPattern = + perPointPattern || + d.mcc || Lib.isArrayOrTypedArray(markerPattern.shape) || Lib.isArrayOrTypedArray(markerPattern.path) || Lib.isArrayOrTypedArray(markerPattern.bgcolor) || @@ -917,25 +1035,33 @@ drawing.singlePointStyle = function(d, sel, trace, fns, gd, pt) { Lib.isArrayOrTypedArray(markerPattern.solidity); var patternID = trace.uid; - if(perPointPattern) patternID += '-' + d.i; + if (perPointPattern) patternID += '-' + d.i; drawing.pattern( - sel, 'point', gd, patternID, - patternShape, patternSize, patternSolidity, - d.mcc, markerPattern.fillmode, - patternBGColor, patternFGColor, patternFGOpacity + sel, + 'point', + gd, + patternID, + patternShape, + patternSize, + patternSolidity, + d.mcc, + markerPattern.fillmode, + patternBGColor, + patternFGColor, + patternFGOpacity ); } else { Lib.isArrayOrTypedArray(fillColor) ? Color.fill(sel, fillColor[d.i]) : Color.fill(sel, fillColor); } - if(lineWidth) { + if (lineWidth) { Color.stroke(sel, lineColor); } } }; -drawing.makePointStyleFns = function(trace) { +drawing.makePointStyleFns = function (trace) { var out = {}; var marker = trace.marker; @@ -944,20 +1070,22 @@ drawing.makePointStyleFns = function(trace) { out.markerScale = drawing.tryColorscale(marker, ''); out.lineScale = drawing.tryColorscale(marker, 'line'); - if(Registry.traceIs(trace, 'symbols')) { - out.ms2mrc = subTypes.isBubble(trace) ? - makeBubbleSizeFn(trace) : - function() { return (marker.size || 6) / 2; }; + if (Registry.traceIs(trace, 'symbols')) { + out.ms2mrc = subTypes.isBubble(trace) + ? makeBubbleSizeFn(trace) + : function () { + return (marker.size || 6) / 2; + }; } - if(trace.selectedpoints) { + if (trace.selectedpoints) { Lib.extendFlat(out, drawing.makeSelectedPointStyleFns(trace)); } return out; }; -drawing.makeSelectedPointStyleFns = function(trace) { +drawing.makeSelectedPointStyleFns = function (trace) { var out = {}; var selectedAttrs = trace.selected || {}; @@ -973,11 +1101,11 @@ drawing.makeSelectedPointStyleFns = function(trace) { var smoIsDefined = smo !== undefined; var usmoIsDefined = usmo !== undefined; - if(Lib.isArrayOrTypedArray(mo) || smoIsDefined || usmoIsDefined) { - out.selectedOpacityFn = function(d) { + if (Lib.isArrayOrTypedArray(mo) || smoIsDefined || usmoIsDefined) { + out.selectedOpacityFn = function (d) { var base = d.mo === undefined ? marker.opacity : d.mo; - if(d.selected) { + if (d.selected) { return smoIsDefined ? smo : base; } else { return usmoIsDefined ? usmo : DESELECTDIM * base; @@ -989,11 +1117,11 @@ drawing.makeSelectedPointStyleFns = function(trace) { var smc = selectedMarker.color; var usmc = unselectedMarker.color; - if(smc || usmc) { - out.selectedColorFn = function(d) { + if (smc || usmc) { + out.selectedColorFn = function (d) { var base = d.mcc || mc; - if(d.selected) { + if (d.selected) { return smc || base; } else { return usmc || base; @@ -1007,11 +1135,11 @@ drawing.makeSelectedPointStyleFns = function(trace) { var smsIsDefined = sms !== undefined; var usmsIsDefined = usms !== undefined; - if(Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) { - out.selectedSizeFn = function(d) { + if (Registry.traceIs(trace, 'symbols') && (smsIsDefined || usmsIsDefined)) { + out.selectedSizeFn = function (d) { var base = d.mrc || ms / 2; - if(d.selected) { + if (d.selected) { return smsIsDefined ? sms / 2 : base; } else { return usmsIsDefined ? usms / 2 : base; @@ -1022,7 +1150,7 @@ drawing.makeSelectedPointStyleFns = function(trace) { return out; }; -drawing.makeSelectedTextStyleFns = function(trace) { +drawing.makeSelectedTextStyleFns = function (trace) { var out = {}; var selectedAttrs = trace.selected || {}; @@ -1036,13 +1164,13 @@ drawing.makeSelectedTextStyleFns = function(trace) { var stc = selectedTextFont.color; var utc = unselectedTextFont.color; - out.selectedTextColorFn = function(d) { + out.selectedTextColorFn = function (d) { var base = d.tc || tc; - if(d.selected) { + if (d.selected) { return stc || base; } else { - if(utc) return utc; + if (utc) return utc; else return stc ? base : Color.addOpacity(base, DESELECTDIM); } }; @@ -1050,53 +1178,56 @@ drawing.makeSelectedTextStyleFns = function(trace) { return out; }; -drawing.selectedPointStyle = function(s, trace) { - if(!s.size() || !trace.selectedpoints) return; +drawing.selectedPointStyle = function (s, trace) { + if (!s.size() || !trace.selectedpoints) return; var fns = drawing.makeSelectedPointStyleFns(trace); var marker = trace.marker || {}; var seq = []; - if(fns.selectedOpacityFn) { - seq.push(function(pt, d) { + if (fns.selectedOpacityFn) { + seq.push(function (pt, d) { pt.style('opacity', fns.selectedOpacityFn(d)); }); } - if(fns.selectedColorFn) { - seq.push(function(pt, d) { + if (fns.selectedColorFn) { + seq.push(function (pt, d) { Color.fill(pt, fns.selectedColorFn(d)); }); } - if(fns.selectedSizeFn) { - seq.push(function(pt, d) { + if (fns.selectedSizeFn) { + seq.push(function (pt, d) { var mx = d.mx || marker.symbol || 0; var mrc2 = fns.selectedSizeFn(d); - pt.attr('d', makePointPath(drawing.symbolNumber(mx), mrc2, getMarkerAngle(d, trace), getMarkerStandoff(d, trace))); + pt.attr( + 'd', + makePointPath(drawing.symbolNumber(mx), mrc2, getMarkerAngle(d, trace), getMarkerStandoff(d, trace)) + ); // save for Drawing.selectedTextStyle d.mrc2 = mrc2; }); } - if(seq.length) { - s.each(function(d) { + if (seq.length) { + s.each(function (d) { var pt = d3.select(this); - for(var i = 0; i < seq.length; i++) { + for (var i = 0; i < seq.length; i++) { seq[i](pt, d); } }); } }; -drawing.tryColorscale = function(marker, prefix) { +drawing.tryColorscale = function (marker, prefix) { var cont = prefix ? Lib.nestedProperty(marker, prefix).get() : marker; - if(cont) { + if (cont) { var colorArray = cont.color; - if((cont.colorscale || cont._colorAx) && Lib.isArrayOrTypedArray(colorArray)) { + if ((cont.colorscale || cont._colorAx) && Lib.isArrayOrTypedArray(colorArray)) { return Colorscale.makeColorScaleFuncFromTrace(cont); } } @@ -1104,18 +1235,18 @@ drawing.tryColorscale = function(marker, prefix) { }; var TEXTOFFSETSIGN = { - start: 1, end: -1, middle: 0, bottom: 1, top: -1 + start: 1, + end: -1, + middle: 0, + bottom: 1, + top: -1 }; function textPointPosition(s, textPosition, fontSize, markerRadius, dontTouchParent) { var group = d3.select(s.node().parentNode); - var v = textPosition.indexOf('top') !== -1 ? - 'top' : - textPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle'; - var h = textPosition.indexOf('left') !== -1 ? - 'end' : - textPosition.indexOf('right') !== -1 ? 'start' : 'middle'; + var v = textPosition.indexOf('top') !== -1 ? 'top' : textPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle'; + var h = textPosition.indexOf('left') !== -1 ? 'end' : textPosition.indexOf('right') !== -1 ? 'start' : 'middle'; // if markers are shown, offset a little more than // the nominal marker size @@ -1124,27 +1255,26 @@ function textPointPosition(s, textPosition, fontSize, markerRadius, dontTouchPar var numLines = (svgTextUtils.lineCount(s) - 1) * LINE_SPACING + 1; var dx = TEXTOFFSETSIGN[h] * r; - var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + - (TEXTOFFSETSIGN[v] - 1) * numLines * fontSize / 2; + var dy = fontSize * 0.75 + TEXTOFFSETSIGN[v] * r + ((TEXTOFFSETSIGN[v] - 1) * numLines * fontSize) / 2; // fix the overall text group position s.attr('text-anchor', h); - if(!dontTouchParent) { + if (!dontTouchParent) { group.attr('transform', strTranslate(dx, dy)); } } function extracTextFontSize(d, trace) { var fontSize = d.ts || trace.textfont.size; - return (isNumeric(fontSize) && fontSize > 0) ? fontSize : 0; + return isNumeric(fontSize) && fontSize > 0 ? fontSize : 0; } // draw text at points -drawing.textPointStyle = function(s, trace, gd) { - if(!s.size()) return; +drawing.textPointStyle = function (s, trace, gd) { + if (!s.size()) return; var selectedTextColorFn; - if(trace.selectedpoints) { + if (trace.selectedpoints) { var fns = drawing.makeSelectedTextStyleFns(trace); selectedTextColorFn = fns.selectedTextColorFn; } @@ -1152,19 +1282,19 @@ drawing.textPointStyle = function(s, trace, gd) { var texttemplate = trace.texttemplate; var fullLayout = gd._fullLayout; - s.each(function(d) { + s.each(function (d) { var p = d3.select(this); - var text = texttemplate ? - Lib.extractOption(d, trace, 'txt', 'texttemplate') : - Lib.extractOption(d, trace, 'tx', 'text'); + var text = texttemplate + ? Lib.extractOption(d, trace, 'txt', 'texttemplate') + : Lib.extractOption(d, trace, 'tx', 'text'); - if(!text && text !== 0) { + if (!text && text !== 0) { p.remove(); return; } - if(texttemplate) { + if (texttemplate) { var fn = trace._module.formatLabels; var labels = fn ? fn(d, trace, fullLayout) : {}; var pointValues = {}; @@ -1175,9 +1305,7 @@ drawing.textPointStyle = function(s, trace, gd) { var pos = d.tp || trace.textposition; var fontSize = extracTextFontSize(d, trace); - var fontColor = selectedTextColorFn ? - selectedTextColorFn(d) : - (d.tc || trace.textfont.color); + var fontColor = selectedTextColorFn ? selectedTextColorFn(d) : d.tc || trace.textfont.color; p.call(drawing.font, { family: d.tf || trace.textfont.family, @@ -1196,12 +1324,12 @@ drawing.textPointStyle = function(s, trace, gd) { }); }; -drawing.selectedTextStyle = function(s, trace) { - if(!s.size() || !trace.selectedpoints) return; +drawing.selectedTextStyle = function (s, trace) { + if (!s.size() || !trace.selectedpoints) return; var fns = drawing.makeSelectedTextStyleFns(trace); - s.each(function(d) { + s.each(function (d) { var tx = d3.select(this); var tc = fns.selectedTextColorFn(d); var tp = d.tp || trace.textposition; @@ -1216,36 +1344,38 @@ drawing.selectedTextStyle = function(s, trace) { // generalized Catmull-Rom splines, per // http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf var CatmullRomExp = 0.5; -drawing.smoothopen = function(pts, smoothness) { - if(pts.length < 3) { return 'M' + pts.join('L');} +drawing.smoothopen = function (pts, smoothness) { + if (pts.length < 3) { + return 'M' + pts.join('L'); + } var path = 'M' + pts[0]; var tangents = []; var i; - for(i = 1; i < pts.length - 1; i++) { + for (i = 1; i < pts.length - 1; i++) { tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); } path += 'Q' + tangents[0][0] + ' ' + pts[1]; - for(i = 2; i < pts.length - 1; i++) { + for (i = 2; i < pts.length - 1; i++) { path += 'C' + tangents[i - 2][1] + ' ' + tangents[i - 1][0] + ' ' + pts[i]; } path += 'Q' + tangents[pts.length - 3][1] + ' ' + pts[pts.length - 1]; return path; }; -drawing.smoothclosed = function(pts, smoothness) { - if(pts.length < 3) { return 'M' + pts.join('L') + 'Z'; } +drawing.smoothclosed = function (pts, smoothness) { + if (pts.length < 3) { + return 'M' + pts.join('L') + 'Z'; + } var path = 'M' + pts[0]; var pLast = pts.length - 1; var tangents = [makeTangent(pts[pLast], pts[0], pts[1], smoothness)]; var i; - for(i = 1; i < pLast; i++) { + for (i = 1; i < pLast; i++) { tangents.push(makeTangent(pts[i - 1], pts[i], pts[i + 1], smoothness)); } - tangents.push( - makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness) - ); + tangents.push(makeTangent(pts[pLast - 1], pts[pLast], pts[0], smoothness)); - for(i = 1; i <= pLast; i++) { + for (i = 1; i <= pLast; i++) { path += 'C' + tangents[i - 1][1] + ' ' + tangents[i][0] + ' ' + pts[i]; } path += 'C' + tangents[pLast][1] + ' ' + tangents[0][0] + ' ' + pts[0] + 'Z'; @@ -1255,7 +1385,7 @@ drawing.smoothclosed = function(pts, smoothness) { var lastDrawnX, lastDrawnY; function roundEnd(pt, isY, isLastPoint) { - if(isLastPoint) pt = applyBackoff(pt); + if (isLastPoint) pt = applyBackoff(pt); return isY ? roundY(pt[1]) : roundX(pt[0]); } @@ -1284,53 +1414,36 @@ function makeTangent(prevpt, thispt, nextpt, smoothness) { var denom1 = 3 * d2a * (d1a + d2a); var denom2 = 3 * d1a * (d1a + d2a); return [ - [ - roundX(thispt[0] + (denom1 && numx / denom1)), - roundY(thispt[1] + (denom1 && numy / denom1)) - ], [ - roundX(thispt[0] - (denom2 && numx / denom2)), - roundY(thispt[1] - (denom2 && numy / denom2)) - ] + [roundX(thispt[0] + (denom1 && numx / denom1)), roundY(thispt[1] + (denom1 && numy / denom1))], + [roundX(thispt[0] - (denom2 && numx / denom2)), roundY(thispt[1] - (denom2 && numy / denom2))] ]; } // step paths - returns a generator function for paths // with the given step shape var STEPPATH = { - hv: function(p0, p1, isLastPoint) { - return 'H' + - roundX(p1[0]) + 'V' + - roundEnd(p1, 1, isLastPoint); + hv: function (p0, p1, isLastPoint) { + return 'H' + roundX(p1[0]) + 'V' + roundEnd(p1, 1, isLastPoint); }, - vh: function(p0, p1, isLastPoint) { - return 'V' + - roundY(p1[1]) + 'H' + - roundEnd(p1, 0, isLastPoint); + vh: function (p0, p1, isLastPoint) { + return 'V' + roundY(p1[1]) + 'H' + roundEnd(p1, 0, isLastPoint); }, - hvh: function(p0, p1, isLastPoint) { - return 'H' + - roundX((p0[0] + p1[0]) / 2) + 'V' + - roundY(p1[1]) + 'H' + - roundEnd(p1, 0, isLastPoint); + hvh: function (p0, p1, isLastPoint) { + return 'H' + roundX((p0[0] + p1[0]) / 2) + 'V' + roundY(p1[1]) + 'H' + roundEnd(p1, 0, isLastPoint); }, - vhv: function(p0, p1, isLastPoint) { - return 'V' + - roundY((p0[1] + p1[1]) / 2) + 'H' + - roundX(p1[0]) + 'V' + - roundEnd(p1, 1, isLastPoint); + vhv: function (p0, p1, isLastPoint) { + return 'V' + roundY((p0[1] + p1[1]) / 2) + 'H' + roundX(p1[0]) + 'V' + roundEnd(p1, 1, isLastPoint); } }; -var STEPLINEAR = function(p0, p1, isLastPoint) { - return 'L' + - roundEnd(p1, 0, isLastPoint) + ',' + - roundEnd(p1, 1, isLastPoint); +var STEPLINEAR = function (p0, p1, isLastPoint) { + return 'L' + roundEnd(p1, 0, isLastPoint) + ',' + roundEnd(p1, 1, isLastPoint); }; -drawing.steps = function(shape) { +drawing.steps = function (shape) { var onestep = STEPPATH[shape] || STEPLINEAR; - return function(pts) { + return function (pts) { var path = 'M' + roundX(pts[0][0]) + ',' + roundY(pts[0][1]); var len = pts.length; - for(var i = 1; i < len; i++) { + for (var i = 1; i < len; i++) { path += onestep(pts[i - 1], pts[i], i === len - 1); } return path; @@ -1343,7 +1456,9 @@ function applyBackoff(pt, start) { var d = pt.d; var i = pt.i; - if(backoff && trace && + if ( + backoff && + trace && trace.marker && trace.marker.angle % 360 === 0 && trace.line && @@ -1365,16 +1480,16 @@ function applyBackoff(pt, start) { var b = arrayBackoff ? backoff[i] : backoff; - if(b === 'auto') { + if (b === 'auto') { var endI = end.i; - if(trace.type === 'scatter') endI--; // Why we need this hack? + if (trace.type === 'scatter') endI--; // Why we need this hack? var endMarker = end.marker; var endMarkerSymbol = endMarker.symbol; - if(Lib.isArrayOrTypedArray(endMarkerSymbol)) endMarkerSymbol = endMarkerSymbol[endI]; + if (Lib.isArrayOrTypedArray(endMarkerSymbol)) endMarkerSymbol = endMarkerSymbol[endI]; var endMarkerSize = endMarker.size; - if(Lib.isArrayOrTypedArray(endMarkerSize)) endMarkerSize = endMarkerSize[endI]; + if (Lib.isArrayOrTypedArray(endMarkerSize)) endMarkerSize = endMarkerSize[endI]; b = endMarker ? drawing.symbolBackOffs[drawing.symbolNumber(endMarkerSymbol)] * endMarkerSize : 0; b += drawing.getMarkerStandoff(d[endI], trace) || 0; @@ -1383,10 +1498,7 @@ function applyBackoff(pt, start) { var x = x2 - b * Math.cos(t); var y = y2 - b * Math.sin(t); - if( - ((x <= x2 && x >= x1) || (x >= x2 && x <= x1)) && - ((y <= y2 && y >= y1) || (y >= y2 && y <= y1)) - ) { + if (((x <= x2 && x >= x1) || (x >= x2 && x <= x1)) && ((y <= y2 && y >= y1) || (y >= y2 && y <= y1))) { pt = [x, y]; } } @@ -1398,28 +1510,26 @@ drawing.applyBackoff = applyBackoff; // off-screen svg render testing element, shared by the whole page // uses the id 'js-plotly-tester' and stores it in drawing.tester -drawing.makeTester = function() { - var tester = Lib.ensureSingleById(d3.select('body'), 'svg', 'js-plotly-tester', function(s) { - s.attr(xmlnsNamespaces.svgAttrs) - .style({ - position: 'absolute', - left: '-10000px', - top: '-10000px', - width: '9000px', - height: '9000px', - 'z-index': '1' - }); +drawing.makeTester = function () { + var tester = Lib.ensureSingleById(d3.select('body'), 'svg', 'js-plotly-tester', function (s) { + s.attr(xmlnsNamespaces.svgAttrs).style({ + position: 'absolute', + left: '-10000px', + top: '-10000px', + width: '9000px', + height: '9000px', + 'z-index': '1' + }); }); // browsers differ on how they describe the bounding rect of // the svg if its contents spill over... so make a 1x1px // reference point we can measure off of. - var testref = Lib.ensureSingle(tester, 'path', 'js-reference-point', function(s) { - s.attr('d', 'M0,0H1V1H0Z') - .style({ - 'stroke-width': 0, - fill: 'black' - }); + var testref = Lib.ensureSingle(tester, 'path', 'js-reference-point', function (s) { + s.attr('d', 'M0,0H1V1H0Z').style({ + 'stroke-width': 0, + fill: 'black' + }); }); drawing.tester = tester; @@ -1452,7 +1562,7 @@ drawing.savedBBoxes = {}; var savedBBoxesCount = 0; var maxSavedBBoxes = 10000; -drawing.bBox = function(node, inTester, hash) { +drawing.bBox = function (node, inTester, hash) { /* * Cache elements we've already measured so we don't have to * remeasure the same thing many times @@ -1461,12 +1571,12 @@ drawing.bBox = function(node, inTester, hash) { * These will not generate a hash (unless we figure out an appropriate * hash key for them) and thus we will not hash them. */ - if(!hash) hash = nodeHash(node); + if (!hash) hash = nodeHash(node); var out; - if(hash) { + if (hash) { out = drawing.savedBBoxes[hash]; - if(out) return Lib.extendFlat({}, out); - } else if(node.childNodes.length === 1) { + if (out) return Lib.extendFlat({}, out); + } else if (node.childNodes.length === 1) { /* * If we have only one child element, which is itself hashable, make * a new hash from this element plus its x,y,transform @@ -1476,20 +1586,20 @@ drawing.bBox = function(node, inTester, hash) { var innerNode = node.childNodes[0]; hash = nodeHash(innerNode); - if(hash) { + if (hash) { var x = +innerNode.getAttribute('x') || 0; var y = +innerNode.getAttribute('y') || 0; var transform = innerNode.getAttribute('transform'); - if(!transform) { + if (!transform) { // in this case, just varying x and y, don't bother caching // the final bBox because the alteration is quick. var innerBB = drawing.bBox(innerNode, false, hash); - if(x) { + if (x) { innerBB.left += x; innerBB.right += x; } - if(y) { + if (y) { innerBB.top += y; innerBB.bottom += y; } @@ -1508,11 +1618,11 @@ drawing.bBox = function(node, inTester, hash) { hash += '~' + x + '~' + y + '~' + transform; out = drawing.savedBBoxes[hash]; - if(out) return Lib.extendFlat({}, out); + if (out) return Lib.extendFlat({}, out); } } var testNode, tester; - if(inTester) { + if (inTester) { testNode = node; } else { tester = drawing.tester.node(); @@ -1523,16 +1633,12 @@ drawing.bBox = function(node, inTester, hash) { } // standardize its position (and newline tspans if any) - d3.select(testNode) - .attr('transform', null) - .call(svgTextUtils.positionText, 0, 0); + d3.select(testNode).attr('transform', null).call(svgTextUtils.positionText, 0, 0); var testRect = testNode.getBoundingClientRect(); - var refRect = drawing.testref - .node() - .getBoundingClientRect(); + var refRect = drawing.testref.node().getBoundingClientRect(); - if(!inTester) tester.removeChild(testNode); + if (!inTester) tester.removeChild(testNode); var bb = { height: testRect.height, @@ -1546,13 +1652,13 @@ drawing.bBox = function(node, inTester, hash) { // make sure we don't have too many saved boxes, // or a long session could overload on memory // by saving boxes for long-gone elements - if(savedBBoxesCount >= maxSavedBBoxes) { + if (savedBBoxesCount >= maxSavedBBoxes) { drawing.savedBBoxes = {}; savedBBoxesCount = 0; } // cache this bbox - if(hash) drawing.savedBBoxes[hash] = bb; + if (hash) drawing.savedBBoxes[hash] = bb; savedBBoxesCount++; return Lib.extendFlat({}, bb); @@ -1562,11 +1668,8 @@ drawing.bBox = function(node, inTester, hash) { // impacts its bounding box, given that bBox clears x, y, and transform function nodeHash(node) { var inputText = node.getAttribute('data-unformatted'); - if(inputText === null) return; - return inputText + - node.getAttribute('data-math') + - node.getAttribute('text-anchor') + - node.getAttribute('style'); + if (inputText === null) return; + return inputText + node.getAttribute('data-math') + node.getAttribute('text-anchor') + node.getAttribute('style'); } /** @@ -1581,31 +1684,30 @@ function nodeHash(node) { * - context._baseUrl {string} * - context._exportedPlot {boolean} */ -drawing.setClipUrl = function(s, localId, gd) { +drawing.setClipUrl = function (s, localId, gd) { s.attr('clip-path', getFullUrl(localId, gd)); }; function getFullUrl(localId, gd) { - if(!localId) return null; + if (!localId) return null; var context = gd._context; - var baseUrl = context._exportedPlot ? '' : (context._baseUrl || ''); - return baseUrl ? - 'url(\'' + baseUrl + '#' + localId + '\')' : - 'url(#' + localId + ')'; + var baseUrl = context._exportedPlot ? '' : context._baseUrl || ''; + return baseUrl ? "url('" + baseUrl + '#' + localId + "')" : 'url(#' + localId + ')'; } -drawing.getTranslate = function(element) { +drawing.getTranslate = function (element) { // Note the separator [^\d] between x and y in this regex // We generally use ',' but IE will convert it to ' ' var re = /.*\btranslate\((-?\d*\.?\d*)[^-\d]*(-?\d*\.?\d*)[^\d].*/; var getter = element.attr ? 'attr' : 'getAttribute'; var transform = element[getter]('transform') || ''; - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); + var translate = transform + .replace(re, function (match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); return { x: +translate[0] || 0, @@ -1613,7 +1715,7 @@ drawing.getTranslate = function(element) { }; }; -drawing.setTranslate = function(element, x, y) { +drawing.setTranslate = function (element, x, y) { var re = /(\btranslate\(.*?\);?)/; var getter = element.attr ? 'attr' : 'getAttribute'; var setter = element.attr ? 'attr' : 'setAttribute'; @@ -1631,15 +1733,16 @@ drawing.setTranslate = function(element, x, y) { return transform; }; -drawing.getScale = function(element) { +drawing.getScale = function (element) { var re = /.*\bscale\((\d*\.?\d*)[^\d]*(\d*\.?\d*)[^\d].*/; var getter = element.attr ? 'attr' : 'getAttribute'; var transform = element[getter]('transform') || ''; - var translate = transform.replace(re, function(match, p1, p2) { - return [p1, p2].join(' '); - }) - .split(' '); + var translate = transform + .replace(re, function (match, p1, p2) { + return [p1, p2].join(' '); + }) + .split(' '); return { x: +translate[0] || 1, @@ -1647,7 +1750,7 @@ drawing.getScale = function(element) { }; }; -drawing.setScale = function(element, x, y) { +drawing.setScale = function (element, x, y) { var re = /(\bscale\(.*?\);?)/; var getter = element.attr ? 'attr' : 'getAttribute'; var setter = element.attr ? 'attr' : 'setAttribute'; @@ -1667,18 +1770,16 @@ drawing.setScale = function(element, x, y) { var SCALE_RE = /\s*sc.*/; -drawing.setPointGroupScale = function(selection, xScale, yScale) { +drawing.setPointGroupScale = function (selection, xScale, yScale) { xScale = xScale || 1; yScale = yScale || 1; - if(!selection) return; + if (!selection) return; // The same scale transform for every point: - var scale = (xScale === 1 && yScale === 1) ? - '' : - 'scale(' + xScale + ',' + yScale + ')'; + var scale = xScale === 1 && yScale === 1 ? '' : 'scale(' + xScale + ',' + yScale + ')'; - selection.each(function() { + selection.each(function () { var t = (this.getAttribute('transform') || '').replace(SCALE_RE, ''); t += scale; t = t.trim(); @@ -1688,32 +1789,28 @@ drawing.setPointGroupScale = function(selection, xScale, yScale) { var TEXT_POINT_LAST_TRANSLATION_RE = /translate\([^)]*\)\s*$/; -drawing.setTextPointsScale = function(selection, xScale, yScale) { - if(!selection) return; +drawing.setTextPointsScale = function (selection, xScale, yScale) { + if (!selection) return; - selection.each(function() { + selection.each(function () { var transforms; var el = d3.select(this); var text = el.select('text'); - if(!text.node()) return; + if (!text.node()) return; var x = parseFloat(text.attr('x') || 0); var y = parseFloat(text.attr('y') || 0); var existingTransform = (el.attr('transform') || '').match(TEXT_POINT_LAST_TRANSLATION_RE); - if(xScale === 1 && yScale === 1) { + if (xScale === 1 && yScale === 1) { transforms = []; } else { - transforms = [ - strTranslate(x, y), - 'scale(' + xScale + ',' + yScale + ')', - strTranslate(-x, -y), - ]; + transforms = [strTranslate(x, y), 'scale(' + xScale + ',' + yScale + ')', strTranslate(-x, -y)]; } - if(existingTransform) { + if (existingTransform) { transforms.push(existingTransform); } @@ -1724,13 +1821,13 @@ drawing.setTextPointsScale = function(selection, xScale, yScale) { function getMarkerStandoff(d, trace) { var standoff; - if(d) standoff = d.mf; + if (d) standoff = d.mf; - if(standoff === undefined) { + if (standoff === undefined) { standoff = trace.marker ? trace.marker.standoff || 0 : 0; } - if(!trace._geo && !trace._xA) { + if (!trace._geo && !trace._xA) { // case of legends return -standoff; } @@ -1747,10 +1844,7 @@ var sin = Math.sin; function rotate(t, xy) { var x = xy[0]; var y = xy[1]; - return [ - x * cos(t) - y * sin(t), - x * sin(t) + y * cos(t) - ]; + return [x * cos(t) - y * sin(t), x * sin(t) + y * cos(t)]; } var previousLon; @@ -1763,24 +1857,24 @@ var previousTraceUid; function getMarkerAngle(d, trace) { var angle = d.ma; - if(angle === undefined) { + if (angle === undefined) { angle = trace.marker.angle; - if(!angle || Lib.isArrayOrTypedArray(angle)) { + if (!angle || Lib.isArrayOrTypedArray(angle)) { angle = 0; } } var x, y; var ref = trace.marker.angleref; - if(ref === 'previous' || ref === 'north') { - if(trace._geo) { + if (ref === 'previous' || ref === 'north') { + if (trace._geo) { var p = trace._geo.project(d.lonlat); x = p[0]; y = p[1]; } else { var xa = trace._xA; var ya = trace._yA; - if(xa && ya) { + if (xa && ya) { x = xa.c2p(d.x); y = ya.c2p(d.y); } else { @@ -1789,7 +1883,7 @@ function getMarkerAngle(d, trace) { } } - if(trace._geo) { + if (trace._geo) { var lon = d.lonlat[0]; var lat = d.lonlat[1]; @@ -1803,38 +1897,29 @@ function getMarkerAngle(d, trace) { lat ]); - var u = atan2( - east[1] - y, - east[0] - x - ); + var u = atan2(east[1] - y, east[0] - x); - var v = atan2( - north[1] - y, - north[0] - x - ); + var v = atan2(north[1] - y, north[0] - x); var t; - if(ref === 'north') { - t = angle / 180 * Math.PI; + if (ref === 'north') { + t = (angle / 180) * Math.PI; // To use counter-clockwise angles i.e. // East: 90, West: -90 // to facilitate wind visualisations // in future we should use t = -t here. - } else if(ref === 'previous') { - var lon1 = lon / 180 * Math.PI; - var lat1 = lat / 180 * Math.PI; - var lon2 = previousLon / 180 * Math.PI; - var lat2 = previousLat / 180 * Math.PI; + } else if (ref === 'previous') { + var lon1 = (lon / 180) * Math.PI; + var lat1 = (lat / 180) * Math.PI; + var lon2 = (previousLon / 180) * Math.PI; + var lat2 = (previousLat / 180) * Math.PI; var dLon = lon2 - lon1; var deltaY = cos(lat2) * sin(dLon); var deltaX = sin(lat2) * cos(lat1) - cos(lat2) * sin(lat1) * cos(dLon); - t = -atan2( - deltaY, - deltaX - ) - Math.PI; + t = -atan2(deltaY, deltaX) - Math.PI; previousLon = lon; previousLat = lat; @@ -1843,36 +1928,25 @@ function getMarkerAngle(d, trace) { var A = rotate(u, [cos(t), 0]); var B = rotate(v, [sin(t), 0]); - angle = atan2( - A[1] + B[1], - A[0] + B[0] - ) / Math.PI * 180; + angle = (atan2(A[1] + B[1], A[0] + B[0]) / Math.PI) * 180; - if(ref === 'previous' && !( - previousTraceUid === trace.uid && - d.i === previousI + 1 - )) { + if (ref === 'previous' && !(previousTraceUid === trace.uid && d.i === previousI + 1)) { angle = null; } } - if(ref === 'previous' && !trace._geo) { - if( - previousTraceUid === trace.uid && - d.i === previousI + 1 && - isNumeric(x) && - isNumeric(y) - ) { + if (ref === 'previous' && !trace._geo) { + if (previousTraceUid === trace.uid && d.i === previousI + 1 && isNumeric(x) && isNumeric(y)) { var dX = x - previousX; var dY = y - previousY; var shape = trace.line ? trace.line.shape || '' : ''; var lastShapeChar = shape.slice(shape.length - 1); - if(lastShapeChar === 'h') dY = 0; - if(lastShapeChar === 'v') dX = 0; + if (lastShapeChar === 'h') dY = 0; + if (lastShapeChar === 'v') dX = 0; - angle += atan2(dY, dX) / Math.PI * 180 + 90; + angle += (atan2(dY, dX) / Math.PI) * 180 + 90; } else { angle = null; } diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 41ee1352f62..31175121656 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -27,7 +27,7 @@ var legendDraw = require('../legend/draw'); // hover labels for multiple horizontal bars get tilted by some angle, // then need to be offset differently if they overlap var YANGLE = constants.YANGLE; -var YA_RADIANS = Math.PI * YANGLE / 180; +var YA_RADIANS = (Math.PI * YANGLE) / 180; // expansion of projected height var YFACTOR = 1 / Math.sin(YA_RADIANS); @@ -87,11 +87,9 @@ exports.hover = function hover(gd, evt, subplot, noHoverEvent) { // The 'target' property changes when bubbling out of Shadow DOM. // Throttling can delay reading the target, so we save the current value. var eventTarget = evt.target; - Lib.throttle( - gd._fullLayout._uid + constants.HOVERID, - constants.HOVERMINTIME, - function() { _hover(gd, evt, subplot, noHoverEvent, eventTarget); } - ); + Lib.throttle(gd._fullLayout._uid + constants.HOVERID, constants.HOVERMINTIME, function () { + _hover(gd, evt, subplot, noHoverEvent, eventTarget); + }); }; /* @@ -129,7 +127,7 @@ exports.hover = function hover(gd, evt, subplot, noHoverEvent) { */ exports.loneHover = function loneHover(hoverItems, opts) { var multiHover = true; - if(!Array.isArray(hoverItems)) { + if (!Array.isArray(hoverItems)) { multiHover = false; hoverItems = [hoverItems]; } @@ -138,21 +136,21 @@ exports.loneHover = function loneHover(hoverItems, opts) { var gTop = getTopOffset(gd); var gLeft = getLeftOffset(gd); - var pointsData = hoverItems.map(function(hoverItem) { + var pointsData = hoverItems.map(function (hoverItem) { var _x0 = hoverItem._x0 || hoverItem.x0 || hoverItem.x || 0; var _x1 = hoverItem._x1 || hoverItem.x1 || hoverItem.x || 0; var _y0 = hoverItem._y0 || hoverItem.y0 || hoverItem.y || 0; var _y1 = hoverItem._y1 || hoverItem.y1 || hoverItem.y || 0; var eventData = hoverItem.eventData; - if(eventData) { + if (eventData) { var x0 = Math.min(_x0, _x1); var x1 = Math.max(_x0, _x1); var y0 = Math.min(_y0, _y1); var y1 = Math.max(_y0, _y1); var trace = hoverItem.trace; - if(Registry.traceIs(trace, 'gl3d')) { + if (Registry.traceIs(trace, 'gl3d')) { var container = gd._fullLayout[trace.scene]._scene.container; var dx = container.offsetLeft; var dy = container.offsetTop; @@ -169,7 +167,7 @@ exports.loneHover = function loneHover(hoverItems, opts) { y1: y1 + gTop }; - if(opts.inOut_bbox) { + if (opts.inOut_bbox) { opts.inOut_bbox.push(eventData.bbox); } } else { @@ -205,8 +203,8 @@ exports.loneHover = function loneHover(hoverItems, opts) { index: 0, hoverinfo: '' }, - xa: {_offset: 0}, - ya: {_offset: 0}, + xa: { _offset: 0 }, + ya: { _offset: 0 }, index: 0, hovertemplate: hoverItem.hovertemplate || false, @@ -233,21 +231,23 @@ exports.loneHover = function loneHover(hoverItems, opts) { var lastBottomY = 0; var anchor = 0; hoverLabel - .sort(function(a, b) {return a.y0 - b.y0;}) - .each(function(d, i) { + .sort(function (a, b) { + return a.y0 - b.y0; + }) + .each(function (d, i) { var topY = d.y0 - d.by / 2; - if((topY - tooltipSpacing) < lastBottomY) { - d.offset = (lastBottomY - topY) + tooltipSpacing; + if (topY - tooltipSpacing < lastBottomY) { + d.offset = lastBottomY - topY + tooltipSpacing; } else { d.offset = 0; } lastBottomY = topY + d.by + d.offset; - if(i === opts.anchorIndex || 0) anchor = d.offset; + if (i === opts.anchorIndex || 0) anchor = d.offset; }) - .each(function(d) { + .each(function (d) { d.offset -= anchor; }); @@ -260,9 +260,9 @@ exports.loneHover = function loneHover(hoverItems, opts) { // The actual implementation is here: function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { - if(!subplot) subplot = 'xy'; + if (!subplot) subplot = 'xy'; - if(typeof subplot === 'string') { + if (typeof subplot === 'string') { // drop zindex from subplot id subplot = subplot.split(zindexSeparator)[0]; } @@ -286,22 +286,20 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var firstXaxis; var firstYaxis; - if(hasCartesian && (hovermodeHasX || hovermodeHasY) && hoversubplots === 'axis') { + if (hasCartesian && (hovermodeHasX || hovermodeHasY) && hoversubplots === 'axis') { var subplotsLength = subplots.length; - for(var p = 0; p < subplotsLength; p++) { + for (var p = 0; p < subplotsLength; p++) { spId = subplots[p]; - if(plots[spId]) { + if (plots[spId]) { // 'cartesian' case firstXaxis = Axes.getFromId(gd, spId, 'x'); firstYaxis = Axes.getFromId(gd, spId, 'y'); - var subplotsWith = ( - hovermodeHasX ? firstXaxis : firstYaxis - )._subplotsWith; + var subplotsWith = (hovermodeHasX ? firstXaxis : firstYaxis)._subplotsWith; - if(subplotsWith && subplotsWith.length) { - for(var q = 0; q < subplotsWith.length; q++) { + if (subplotsWith && subplotsWith.length) { + for (var q = 0; q < subplotsWith.length; q++) { pushUnique(subplots, subplotsWith[q]); } } @@ -310,8 +308,8 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } // list of all overlaid subplots to look at - if(plotinfo && hoversubplots !== 'single') { - var overlayedSubplots = plotinfo.overlays.map(function(pi) { + if (plotinfo && hoversubplots !== 'single') { + var overlayedSubplots = plotinfo.overlays.map(function (pi) { return pi.id; }); @@ -323,15 +321,15 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var yaArray = new Array(len); var supportsCompare = false; - for(var i = 0; i < len; i++) { + for (var i = 0; i < len; i++) { spId = subplots[i]; - if(plots[spId]) { + if (plots[spId]) { // 'cartesian' case supportsCompare = true; xaArray[i] = plots[spId].xaxis; yaArray[i] = plots[spId].yaxis; - } else if(fullLayout[spId] && fullLayout[spId]._subplot) { + } else if (fullLayout[spId] && fullLayout[spId]._subplot) { // other subplot types var _subplot = fullLayout[spId]._subplot; xaArray[i] = _subplot.xaxis; @@ -342,18 +340,22 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } } - if(hovermode && !supportsCompare) hovermode = 'closest'; + if (hovermode && !supportsCompare) hovermode = 'closest'; - if(['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || !gd.calcdata || - gd.querySelector('.zoombox') || gd._dragging) { + if ( + ['x', 'y', 'closest', 'x unified', 'y unified'].indexOf(hovermode) === -1 || + !gd.calcdata || + gd.querySelector('.zoombox') || + gd._dragging + ) { return dragElement.unhoverRaw(gd, evt); } var hoverdistance = fullLayout.hoverdistance; - if(hoverdistance === -1) hoverdistance = Infinity; + if (hoverdistance === -1) hoverdistance = Infinity; var spikedistance = fullLayout.spikedistance; - if(spikedistance === -1) spikedistance = Infinity; + if (spikedistance === -1) spikedistance = Infinity; // hoverData: the set of candidate points we've found to highlight var hoverData = []; @@ -368,8 +370,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // mapped onto each of the currently selected overlaid subplots var xvalArray, yvalArray; - var itemnum, curvenum, cd, trace, subplotId, subploti, _mode, - xval, yval, pointData, closedataPreviousLength; + var itemnum, curvenum, cd, trace, subplotId, subploti, _mode, xval, yval, pointData, closedataPreviousLength; // spikePoints: the set of candidate points we've found to draw spikes to var spikePoints = { @@ -384,16 +385,16 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // Figure out what we're hovering on: // mouse location or user-supplied data - if(Array.isArray(evt)) { + if (Array.isArray(evt)) { // user specified an array of points to highlight hovermode = 'array'; - for(itemnum = 0; itemnum < evt.length; itemnum++) { + for (itemnum = 0; itemnum < evt.length; itemnum++) { cd = gd.calcdata[evt[itemnum].curveNumber || 0]; - if(cd) { + if (cd) { trace = cd[0].trace; - if(cd[0].trace.hoverinfo !== 'skip') { + if (cd[0].trace.hoverinfo !== 'skip') { searchData.push(cd); - if(trace.orientation === 'h') { + if (trace.orientation === 'h') { hasOneHorizontalTrace = true; } } @@ -402,18 +403,18 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } else { // take into account zorder var zorderedCalcdata = gd.calcdata.slice(); - zorderedCalcdata.sort(function(a, b) { + zorderedCalcdata.sort(function (a, b) { var aZorder = a[0].trace.zorder || 0; var bZorder = b[0].trace.zorder || 0; return aZorder - bZorder; }); - for(curvenum = 0; curvenum < zorderedCalcdata.length; curvenum++) { + for (curvenum = 0; curvenum < zorderedCalcdata.length; curvenum++) { cd = zorderedCalcdata[curvenum]; trace = cd[0].trace; - if(trace.hoverinfo !== 'skip' && helpers.isTraceInSubplots(trace, subplots)) { + if (trace.hoverinfo !== 'skip' && helpers.isTraceInSubplots(trace, subplots)) { searchData.push(cd); - if(trace.orientation === 'h') { + if (trace.orientation === 'h') { hasOneHorizontalTrace = true; } } @@ -425,17 +426,17 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var hasUserCalledHover = !eventTarget; var xpx, ypx; - if(hasUserCalledHover) { - if('xpx' in evt) xpx = evt.xpx; + if (hasUserCalledHover) { + if ('xpx' in evt) xpx = evt.xpx; else xpx = xaArray[0]._length / 2; - if('ypx' in evt) ypx = evt.ypx; + if ('ypx' in evt) ypx = evt.ypx; else ypx = yaArray[0]._length / 2; } else { // fire the beforehover event and quit if it returns false // note that we're only calling this on real mouse events, so // manual calls to fx.hover will always run. - if(Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { + if (Events.triggerHandler(gd, 'plotly_beforehover', evt) === false) { return; } @@ -452,7 +453,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // in case hover was called from mouseout into hovertext, // it's possible you're not actually over the plot anymore - if(xpx < 0 || xpx > xaArray[0]._length || ypx < 0 || ypx > yaArray[0]._length) { + if (xpx < 0 || xpx > xaArray[0]._length || ypx < 0 || ypx > yaArray[0]._length) { return dragElement.unhoverRaw(gd, evt); } } @@ -460,13 +461,13 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { evt.pointerX = xpx + xaArray[0]._offset; evt.pointerY = ypx + yaArray[0]._offset; - if('xval' in evt) xvalArray = helpers.flat(subplots, evt.xval); + if ('xval' in evt) xvalArray = helpers.flat(subplots, evt.xval); else xvalArray = helpers.p2c(xaArray, xpx); - if('yval' in evt) yvalArray = helpers.flat(subplots, evt.yval); + if ('yval' in evt) yvalArray = helpers.flat(subplots, evt.yval); else yvalArray = helpers.p2c(yaArray, ypx); - if(!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { + if (!isNumeric(xvalArray[0]) || !isNumeric(yvalArray[0])) { Lib.warn('Fx.hover failed', evt, gd); return dragElement.unhoverRaw(gd, evt); } @@ -480,27 +481,27 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // this is minimum dx and/or dy, depending on mode // and the pixel position for the label (labelXpx, labelYpx) function findHoverPoints(customXVal, customYVal) { - for(curvenum = 0; curvenum < searchData.length; curvenum++) { + for (curvenum = 0; curvenum < searchData.length; curvenum++) { cd = searchData[curvenum]; // filter out invisible or broken data - if(!cd || !cd[0] || !cd[0].trace) continue; + if (!cd || !cd[0] || !cd[0].trace) continue; trace = cd[0].trace; - if(trace.visible !== true || trace._length === 0) continue; + if (trace.visible !== true || trace._length === 0) continue; // Explicitly bail out for these two. I don't know how to otherwise prevent // the rest of this function from running and failing - if(['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue; + if (['carpet', 'contourcarpet'].indexOf(trace._module.name) !== -1) continue; // within one trace mode can sometimes be overridden _mode = hovermode; - if(helpers.isUnifiedHover(_mode)) { + if (helpers.isUnifiedHover(_mode)) { _mode = _mode.charAt(0); } - if(trace.type === 'splom') { + if (trace.type === 'splom') { // splom traces do not generate overlay subplots, // it is safe to assume here splom traces correspond to the 0th subplot subploti = 0; @@ -553,33 +554,33 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { }; // add ref to subplot object (non-cartesian case) - if(fullLayout[subplotId]) { + if (fullLayout[subplotId]) { pointData.subplot = fullLayout[subplotId]._subplot; } // add ref to splom scene - if(fullLayout._splomScenes && fullLayout._splomScenes[trace.uid]) { + if (fullLayout._splomScenes && fullLayout._splomScenes[trace.uid]) { pointData.scene = fullLayout._splomScenes[trace.uid]; } // for a highlighting array, figure out what // we're searching for with this element - if(_mode === 'array') { + if (_mode === 'array') { var selection = evt[curvenum]; - if('pointNumber' in selection) { + if ('pointNumber' in selection) { pointData.index = selection.pointNumber; _mode = 'closest'; } else { _mode = ''; - if('xval' in selection) { + if ('xval' in selection) { xval = selection.xval; _mode = 'x'; } - if('yval' in selection) { + if ('yval' in selection) { yval = selection.yval; _mode = _mode ? 'closest' : 'y'; } } - } else if(customXVal !== undefined && customYVal !== undefined) { + } else if (customXVal !== undefined && customYVal !== undefined) { xval = customXVal; yval = customYVal; } else { @@ -590,8 +591,8 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { closedataPreviousLength = hoverData.length; // Now if there is range to look in, find the points to hover. - if(hoverdistance !== 0) { - if(trace._module && trace._module.hoverPoints) { + if (hoverdistance !== 0) { + if (trace._module && trace._module.hoverPoints) { var newPoints = trace._module.hoverPoints(pointData, xval, yval, _mode, { finiteRange: true, hoverLayer: fullLayout._hoverlayer, @@ -601,11 +602,11 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd: gd }); - if(newPoints) { + if (newPoints) { var newPoint; - for(var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) { + for (var newPointNum = 0; newPointNum < newPoints.length; newPointNum++) { newPoint = newPoints[newPointNum]; - if(isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) { + if (isNumeric(newPoint.x0) && isNumeric(newPoint.y0)) { hoverData.push(cleanPoint(newPoint, hovermode)); } } @@ -618,50 +619,56 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // in closest mode, remove any existing (farther) points // and don't look any farther than this latest point (or points, some // traces like box & violin make multiple hover labels at once) - if(hovermode === 'closest' && hoverData.length > closedataPreviousLength) { + if (hovermode === 'closest' && hoverData.length > closedataPreviousLength) { hoverData.splice(0, closedataPreviousLength); distance = hoverData[0].distance; } // Now if there is range to look in, find the points to draw the spikelines // Do it only if there is no hoverData - if(hasCartesian && (spikedistance !== 0)) { - if(hoverData.length === 0) { + if (hasCartesian && spikedistance !== 0) { + if (hoverData.length === 0) { pointData.distance = spikedistance; pointData.index = false; var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', { hoverLayer: fullLayout._hoverlayer }); - if(closestPoints) { - closestPoints = closestPoints.filter(function(point) { + if (closestPoints) { + closestPoints = closestPoints.filter(function (point) { // some hover points, like scatter fills, do not allow spikes, // so will generate a hover point but without a valid spikeDistance return point.spikeDistance <= spikedistance; }); } - if(closestPoints && closestPoints.length) { + if (closestPoints && closestPoints.length) { var tmpPoint; - var closestVPoints = closestPoints.filter(function(point) { + var closestVPoints = closestPoints.filter(function (point) { return point.xa.showspikes && point.xa.spikesnap !== 'hovered data'; }); - if(closestVPoints.length) { + if (closestVPoints.length) { var closestVPt = closestVPoints[0]; - if(isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) { + if (isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) { tmpPoint = fillSpikePoint(closestVPt); - if(!spikePoints.vLinePoint || (spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance)) { + if ( + !spikePoints.vLinePoint || + spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance + ) { spikePoints.vLinePoint = tmpPoint; } } } - var closestHPoints = closestPoints.filter(function(point) { + var closestHPoints = closestPoints.filter(function (point) { return point.ya.showspikes && point.ya.spikesnap !== 'hovered data'; }); - if(closestHPoints.length) { + if (closestHPoints.length) { var closestHPt = closestHPoints[0]; - if(isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) { + if (isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) { tmpPoint = fillSpikePoint(closestHPt); - if(!spikePoints.hLinePoint || (spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance)) { + if ( + !spikePoints.hLinePoint || + spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance + ) { spikePoints.hLinePoint = tmpPoint; } } @@ -679,14 +686,14 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var minDistance = Infinity; var thisSpikeDistance; - for(var i = 0; i < pointsData.length; i++) { - if(firstXaxis && firstXaxis._id !== pointsData[i].xa._id) continue; - if(firstYaxis && firstYaxis._id !== pointsData[i].ya._id) continue; + for (var i = 0; i < pointsData.length; i++) { + if (firstXaxis && firstXaxis._id !== pointsData[i].xa._id) continue; + if (firstYaxis && firstYaxis._id !== pointsData[i].ya._id) continue; thisSpikeDistance = pointsData[i].spikeDistance; - if(spikeOnWinning && i === 0) thisSpikeDistance = -Infinity; + if (spikeOnWinning && i === 0) thisSpikeDistance = -Infinity; - if(thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) { + if (thisSpikeDistance <= minDistance && thisSpikeDistance <= spikedistance) { resultPoint = pointsData[i]; minDistance = thisSpikeDistance; } @@ -695,7 +702,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } function fillSpikePoint(point) { - if(!point) return null; + if (!point) return null; return { xa: point.xa, ya: point.ya, @@ -721,22 +728,16 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { }; gd._spikepoints = newspikepoints; - var sortHoverData = function() { + var sortHoverData = function () { // When sorting keep the points in the main subplot at the top // then add points in other subplots - var hoverDataInSubplot = hoverData.filter(function(a) { - return ( - (firstXaxis && firstXaxis._id === a.xa._id) && - (firstYaxis && firstYaxis._id === a.ya._id) - ); + var hoverDataInSubplot = hoverData.filter(function (a) { + return firstXaxis && firstXaxis._id === a.xa._id && firstYaxis && firstYaxis._id === a.ya._id; }); - var hoverDataOutSubplot = hoverData.filter(function(a) { - return !( - (firstXaxis && firstXaxis._id === a.xa._id) && - (firstYaxis && firstYaxis._id === a.ya._id) - ); + var hoverDataOutSubplot = hoverData.filter(function (a) { + return !(firstXaxis && firstXaxis._id === a.xa._id && firstYaxis && firstYaxis._id === a.ya._id); }); hoverDataInSubplot.sort(distanceSort); @@ -749,18 +750,19 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { sortHoverData(); var axLetter = hovermode.charAt(0); - var spikeOnWinning = (axLetter === 'x' || axLetter === 'y') && hoverData[0] && cartesianScatterPoints[hoverData[0].trace.type]; + var spikeOnWinning = + (axLetter === 'x' || axLetter === 'y') && hoverData[0] && cartesianScatterPoints[hoverData[0].trace.type]; // Now if it is not restricted by spikedistance option, set the points to draw the spikelines - if(hasCartesian && (spikedistance !== 0)) { - if(hoverData.length !== 0) { - var tmpHPointData = hoverData.filter(function(point) { + if (hasCartesian && spikedistance !== 0) { + if (hoverData.length !== 0) { + var tmpHPointData = hoverData.filter(function (point) { return point.ya.showspikes; }); var tmpHPoint = selectClosestPoint(tmpHPointData, spikedistance, spikeOnWinning); spikePoints.hLinePoint = fillSpikePoint(tmpHPoint); - var tmpVPointData = hoverData.filter(function(point) { + var tmpVPointData = hoverData.filter(function (point) { return point.xa.showspikes; }); var tmpVPoint = selectClosestPoint(tmpVPointData, spikedistance, spikeOnWinning); @@ -769,23 +771,23 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { } // if hoverData is empty check for the spikes to draw and quit if there are none - if(hoverData.length === 0) { + if (hoverData.length === 0) { var result = dragElement.unhoverRaw(gd, evt); - if(hasCartesian && ((spikePoints.hLinePoint !== null) || (spikePoints.vLinePoint !== null))) { - if(spikesChanged(oldspikepoints)) { + if (hasCartesian && (spikePoints.hLinePoint !== null || spikePoints.vLinePoint !== null)) { + if (spikesChanged(oldspikepoints)) { createSpikelines(gd, spikePoints, spikelineOpts); } } return result; } - if(hasCartesian) { - if(spikesChanged(oldspikepoints)) { + if (hasCartesian) { + if (spikesChanged(oldspikepoints)) { createSpikelines(gd, spikePoints, spikelineOpts); } } - if( + if ( helpers.isXYhover(_mode) && hoverData[0].length !== 0 && hoverData[0].trace.type !== 'splom' // TODO: add support for splom @@ -793,8 +795,8 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // pick winning point var winningPoint = hoverData[0]; // discard other points - if(multipleHoverPoints[winningPoint.trace.type]) { - hoverData = hoverData.filter(function(d) { + if (multipleHoverPoints[winningPoint.trace.type]) { + hoverData = hoverData.filter(function (d) { return d.trace.index === winningPoint.trace.index; }); } else { @@ -811,19 +813,16 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var finalPoints = []; var seen = {}; var id = 0; - var insert = function(newHd) { + var insert = function (newHd) { var key = multipleHoverPoints[newHd.trace.type] ? hoverDataKey(newHd) : newHd.trace.index; - if(!seen[key]) { + if (!seen[key]) { id++; seen[key] = id; finalPoints.push(newHd); } else { var oldId = seen[key] - 1; var oldHd = finalPoints[oldId]; - if(oldId > 0 && - Math.abs(newHd.distance) < - Math.abs(oldHd.distance) - ) { + if (oldId > 0 && Math.abs(newHd.distance) < Math.abs(oldHd.distance)) { // replace with closest finalPoints[oldId] = newHd; } @@ -832,11 +831,11 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var k; // insert the winnig point(s) first - for(k = 0; k < initLen; k++) { + for (k = 0; k < initLen; k++) { insert(hoverData[k]); } // override from the end - for(k = hoverData.length - 1; k > initLen - 1; k--) { + for (k = hoverData.length - 1; k > initLen - 1; k--) { insert(hoverData[k]); } hoverData = finalPoints; @@ -852,19 +851,19 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // pull out just the data that's useful to // other people and send it to the event - for(itemnum = 0; itemnum < hoverData.length; itemnum++) { + for (itemnum = 0; itemnum < hoverData.length; itemnum++) { var pt = hoverData[itemnum]; var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); - if(pt.hovertemplate !== false) { + if (pt.hovertemplate !== false) { var ht = false; - if(pt.cd[pt.index] && pt.cd[pt.index].ht) { + if (pt.cd[pt.index] && pt.cd[pt.index].ht) { ht = pt.cd[pt.index].ht; } pt.hovertemplate = ht || pt.trace.hovertemplate || false; } - if(pt.xa && pt.ya) { + if (pt.xa && pt.ya) { var _x0 = pt.x0 + pt.xa._offset; var _x1 = pt.x1 + pt.xa._offset; var _y0 = pt.y0 + pt.ya._offset; @@ -889,15 +888,11 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { gd._hoverdata = newhoverdata; - var rotateLabels = ( + var rotateLabels = (hovermode === 'y' && (searchData.length > 1 || hoverData.length > 1)) || - (hovermode === 'closest' && hasOneHorizontalTrace && hoverData.length > 1) - ); + (hovermode === 'closest' && hasOneHorizontalTrace && hoverData.length > 1); - var bgColor = Color.combine( - fullLayout.plot_bgcolor || Color.background, - fullLayout.paper_bgcolor - ); + var bgColor = Color.combine(fullLayout.plot_bgcolor || Color.background, fullLayout.paper_bgcolor); var hoverText = createHoverText(hoverData, { gd: gd, @@ -911,20 +906,20 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { }); var hoverLabels = hoverText.hoverLabels; - if(!helpers.isUnifiedHover(hovermode)) { + if (!helpers.isUnifiedHover(hovermode)) { hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, hoverText.commonLabelBoundingBox); alignHoverText(hoverLabels, rotateLabels, fullLayout._invScaleX, fullLayout._invScaleY); - } // TODO: tagName hack is needed to appease geo.js's hack of using eventTarget=true + } // TODO: tagName hack is needed to appease geo.js's hack of using eventTarget=true // we should improve the "fx" API so other plots can use it without these hack. - if(eventTarget && eventTarget.tagName) { + if (eventTarget && eventTarget.tagName) { var hasClickToShow = Registry.getComponentMethod('annotations', 'hasClickToShow')(gd, newhoverdata); overrideCursor(d3.select(eventTarget), hasClickToShow ? 'pointer' : ''); } // don't emit events if called manually - if(!eventTarget || noHoverEvent || !hoverChanged(gd, evt, oldhoverdata)) return; + if (!eventTarget || noHoverEvent || !hoverChanged(gd, evt, oldhoverdata)) return; - if(oldhoverdata) { + if (oldhoverdata) { gd.emit('plotly_unhover', { event: evt, points: oldhoverdata @@ -957,7 +952,7 @@ function createHoverText(hoverData, opts) { var outerContainer = opts.outerContainer; var commonLabelOpts = opts.commonLabelOpts || {}; // Early exit if no labels are drawn - if(hoverData.length === 0) return [[]]; + if (hoverData.length === 0) return [[]]; // opts.fontFamily/Size are used for the common label // and as defaults for each hover label, though the individual labels @@ -979,10 +974,10 @@ function createHoverText(hoverData, opts) { var t0 = c0[axLabel]; // search in array for the label - if(t0 === undefined && xa.type === 'multicategory') { - for(var q = 0; q < hoverData.length; q++) { + if (t0 === undefined && xa.type === 'multicategory') { + for (var q = 0; q < hoverData.length; q++) { t0 = hoverData[q][axLabel]; - if(t0 !== undefined) break; + if (t0 !== undefined) break; } } @@ -994,25 +989,21 @@ function createHoverText(hoverData, opts) { // show the common label, if any, on the axis // never show a common label in array mode, // even if sometimes there could be one - var showCommonLabel = ( - (t0 !== undefined) && - (c0.distance <= opts.hoverdistance) && - (hovermode === 'x' || hovermode === 'y') - ); + var showCommonLabel = + t0 !== undefined && c0.distance <= opts.hoverdistance && (hovermode === 'x' || hovermode === 'y'); // all hover traces hoverinfo must contain the hovermode // to have common labels - if(showCommonLabel) { + if (showCommonLabel) { var allHaveZ = true; var i, traceHoverinfo; - for(i = 0; i < hoverData.length; i++) { - if(allHaveZ && hoverData[i].zLabel === undefined) allHaveZ = false; + for (i = 0; i < hoverData.length; i++) { + if (allHaveZ && hoverData[i].zLabel === undefined) allHaveZ = false; traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo; - if(traceHoverinfo) { + if (traceHoverinfo) { var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+'); - if(parts.indexOf('all') === -1 && - parts.indexOf(hovermode) === -1) { + if (parts.indexOf('all') === -1 && parts.indexOf(hovermode) === -1) { showCommonLabel = false; break; } @@ -1020,13 +1011,11 @@ function createHoverText(hoverData, opts) { } // xyz labels put all info in their main label, so have no need of a common label - if(allHaveZ) showCommonLabel = false; + if (allHaveZ) showCommonLabel = false; } - var commonLabel = container.selectAll('g.axistext') - .data(showCommonLabel ? [0] : []); - commonLabel.enter().append('g') - .classed('axistext', true); + var commonLabel = container.selectAll('g.axistext').data(showCommonLabel ? [0] : []); + commonLabel.enter().append('g').classed('axistext', true); commonLabel.exit().remove(); // set rect (without arrow) behind label below for later collision detection @@ -1036,12 +1025,12 @@ function createHoverText(hoverData, opts) { minY: 0, maxY: 0 }; - commonLabel.each(function() { + commonLabel.each(function () { var label = d3.select(this); - var lpath = Lib.ensureSingle(label, 'path', '', function(s) { - s.style({'stroke-width': '1px'}); + var lpath = Lib.ensureSingle(label, 'path', '', function (s) { + s.style({ 'stroke-width': '1px' }); }); - var ltext = Lib.ensureSingle(label, 'text', '', function(s) { + var ltext = Lib.ensureSingle(label, 'text', '', function (s) { // prohibit tex interpretation until we can handle // tex and regular text together s.attr('data-notex', 1); @@ -1068,7 +1057,8 @@ function createHoverText(hoverData, opts) { stroke: commonStroke }); - ltext.text(t0) + ltext + .text(t0) .call(Drawing.font, commonLabelFont) .call(svgTextUtils.positionText, 0, 0) .call(svgTextUtils.convertToTspans, gd); @@ -1078,13 +1068,18 @@ function createHoverText(hoverData, opts) { var tbb = getBoundingClientRect(gd, ltext.node()); var lx, ly; - if(hovermode === 'x') { + if (hovermode === 'x') { var topsign = xa.side === 'top' ? '-' : ''; - ltext.attr('text-anchor', 'middle') - .call(svgTextUtils.positionText, 0, (xa.side === 'top' ? - (outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD) : - (outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD))); + ltext + .attr('text-anchor', 'middle') + .call( + svgTextUtils.positionText, + 0, + xa.side === 'top' + ? outerTop - tbb.bottom - HOVERARROWSIZE - HOVERTEXTPAD + : outerTop - tbb.top + HOVERARROWSIZE + HOVERTEXTPAD + ); lx = xa._offset + (c0.x0 + c0.x1) / 2; ly = ya._offset + (xa.side === 'top' ? 0 : ya._length); @@ -1092,25 +1087,41 @@ function createHoverText(hoverData, opts) { var halfWidth = tbb.width / 2 + HOVERTEXTPAD; var tooltipMidX = lx; - if(lx < halfWidth) { + if (lx < halfWidth) { tooltipMidX = halfWidth; - } else if(lx > (fullLayout.width - halfWidth)) { + } else if (lx > fullLayout.width - halfWidth) { tooltipMidX = fullLayout.width - halfWidth; } - lpath.attr('d', 'M' + (lx - tooltipMidX) + ',0' + - 'L' + (lx - tooltipMidX + HOVERARROWSIZE) + ',' + topsign + HOVERARROWSIZE + - 'H' + halfWidth + - 'v' + topsign + (HOVERTEXTPAD * 2 + tbb.height) + - 'H' + (-halfWidth) + - 'V' + topsign + HOVERARROWSIZE + - 'H' + (lx - tooltipMidX - HOVERARROWSIZE) + - 'Z'); + lpath.attr( + 'd', + 'M' + + (lx - tooltipMidX) + + ',0' + + 'L' + + (lx - tooltipMidX + HOVERARROWSIZE) + + ',' + + topsign + + HOVERARROWSIZE + + 'H' + + halfWidth + + 'v' + + topsign + + (HOVERTEXTPAD * 2 + tbb.height) + + 'H' + + -halfWidth + + 'V' + + topsign + + HOVERARROWSIZE + + 'H' + + (lx - tooltipMidX - HOVERARROWSIZE) + + 'Z' + ); lx = tooltipMidX; commonLabelRect.minX = lx - halfWidth; commonLabelRect.maxX = lx + halfWidth; - if(xa.side === 'top') { + if (xa.side === 'top') { // label on negative y side commonLabelRect.minY = ly - (HOVERTEXTPAD * 2 + tbb.height); commonLabelRect.maxY = ly - HOVERTEXTPAD; @@ -1122,7 +1133,7 @@ function createHoverText(hoverData, opts) { var anchor; var sgn; var leftsign; - if(ya.side === 'right') { + if (ya.side === 'right') { anchor = 'start'; sgn = 1; leftsign = ''; @@ -1138,16 +1149,32 @@ function createHoverText(hoverData, opts) { ltext.attr('text-anchor', anchor); - lpath.attr('d', 'M0,0' + - 'L' + leftsign + HOVERARROWSIZE + ',' + HOVERARROWSIZE + - 'V' + (HOVERTEXTPAD + tbb.height / 2) + - 'h' + leftsign + (HOVERTEXTPAD * 2 + tbb.width) + - 'V-' + (HOVERTEXTPAD + tbb.height / 2) + - 'H' + leftsign + HOVERARROWSIZE + 'V-' + HOVERARROWSIZE + 'Z'); + lpath.attr( + 'd', + 'M0,0' + + 'L' + + leftsign + + HOVERARROWSIZE + + ',' + + HOVERARROWSIZE + + 'V' + + (HOVERTEXTPAD + tbb.height / 2) + + 'h' + + leftsign + + (HOVERTEXTPAD * 2 + tbb.width) + + 'V-' + + (HOVERTEXTPAD + tbb.height / 2) + + 'H' + + leftsign + + HOVERARROWSIZE + + 'V-' + + HOVERARROWSIZE + + 'Z' + ); commonLabelRect.minY = ly - (HOVERTEXTPAD + tbb.height / 2); commonLabelRect.maxY = ly + (HOVERTEXTPAD + tbb.height / 2); - if(ya.side === 'right') { + if (ya.side === 'right') { commonLabelRect.minX = lx + HOVERARROWSIZE; commonLabelRect.maxX = lx + HOVERARROWSIZE + (HOVERTEXTPAD * 2 + tbb.width); } else { @@ -1161,25 +1188,31 @@ function createHoverText(hoverData, opts) { var clipId = 'clip' + fullLayout._uid + 'commonlabel' + ya._id; var clipPath; - if(lx < (tbb.width + 2 * HOVERTEXTPAD + HOVERARROWSIZE)) { - clipPath = 'M-' + (HOVERARROWSIZE + HOVERTEXTPAD) + '-' + halfHeight + - 'h-' + (tbb.width - HOVERTEXTPAD) + - 'V' + halfHeight + - 'h' + (tbb.width - HOVERTEXTPAD) + 'Z'; + if (lx < tbb.width + 2 * HOVERTEXTPAD + HOVERARROWSIZE) { + clipPath = + 'M-' + + (HOVERARROWSIZE + HOVERTEXTPAD) + + '-' + + halfHeight + + 'h-' + + (tbb.width - HOVERTEXTPAD) + + 'V' + + halfHeight + + 'h' + + (tbb.width - HOVERTEXTPAD) + + 'Z'; var ltx = tbb.width - lx + HOVERTEXTPAD; svgTextUtils.positionText(ltext, ltx, lty); // shift each line (except the longest) so that start-of-line // is always visible - if(anchor === 'end') { - ltext.selectAll('tspan').each(function() { + if (anchor === 'end') { + ltext.selectAll('tspan').each(function () { var s = d3.select(this); - var dummy = Drawing.tester.append('text') - .text(s.text()) - .call(Drawing.font, commonLabelFont); + var dummy = Drawing.tester.append('text').text(s.text()).call(Drawing.font, commonLabelFont); var dummyBB = getBoundingClientRect(gd, dummy.node()); - if(Math.round(dummyBB.width) < Math.round(tbb.width)) { + if (Math.round(dummyBB.width) < Math.round(tbb.width)) { s.attr('x', ltx - dummyBB.width); } dummy.remove(); @@ -1201,12 +1234,14 @@ function createHoverText(hoverData, opts) { }); // Show a single hover label - if(helpers.isUnifiedHover(hovermode)) { + if (helpers.isUnifiedHover(hovermode)) { // Delete leftover hover labels from other hovermodes container.selectAll('g.hovertext').remove(); - var groupedHoverData = hoverData.filter(function(data) {return data.hoverinfo !== 'none';}); + var groupedHoverData = hoverData.filter(function (data) { + return data.hoverinfo !== 'none'; + }); // Return early if nothing is hovered on - if(groupedHoverData.length === 0) return []; + if (groupedHoverData.length === 0) return []; // mock legend var hoverlabel = fullLayout.hoverlabel; @@ -1214,23 +1249,21 @@ function createHoverText(hoverData, opts) { var item0 = groupedHoverData[0]; - var unifiedhovertitleText = (( - hovermode === 'x unified' ? - item0.xa : - item0.ya - ).unifiedhovertitle || {}).text; - - var mainText = !unifiedhovertitleText ? t0 : - Lib.hovertemplateString(unifiedhovertitleText, {}, fullLayout._d3locale, - hovermode === 'x unified' ? - {xa: item0.xa, x: item0.xVal} : - {ya: item0.ya, y: item0.yVal} - ); + var unifiedhovertitleText = ((hovermode === 'x unified' ? item0.xa : item0.ya).unifiedhovertitle || {}).text; + + var mainText = !unifiedhovertitleText + ? t0 + : Lib.hovertemplateString( + unifiedhovertitleText, + {}, + fullLayout._d3locale, + hovermode === 'x unified' ? { xa: item0.xa, x: item0.xVal } : { ya: item0.ya, y: item0.yVal } + ); var mockLayoutIn = { showlegend: true, legend: { - title: {text: mainText, font: font}, + title: { text: mainText, font: font }, font: font, bgcolor: hoverlabel.bgcolor, bordercolor: hoverlabel.bordercolor, @@ -1248,16 +1281,16 @@ function createHoverText(hoverData, opts) { // prepare items for the legend mockLegend.entries = []; - for(var j = 0; j < groupedHoverData.length; j++) { + for (var j = 0; j < groupedHoverData.length; j++) { var pt = groupedHoverData[j]; - if(pt.hoverinfo === 'none') continue; + if (pt.hoverinfo === 'none') continue; var texts = getHoverLabelText(pt, true, hovermode, fullLayout, t0); var text = texts[0]; var name = texts[1]; pt.name = name; - if(name !== '') { + if (name !== '') { pt.text = name + ' : ' + text; } else { pt.text = text; @@ -1265,20 +1298,22 @@ function createHoverText(hoverData, opts) { // pass through marker's calcdata to style legend items var cd = pt.cd[pt.index]; - if(cd) { - if(cd.mc) pt.mc = cd.mc; - if(cd.mcc) pt.mc = cd.mcc; - if(cd.mlc) pt.mlc = cd.mlc; - if(cd.mlcc) pt.mlc = cd.mlcc; - if(cd.mlw) pt.mlw = cd.mlw; - if(cd.mrc) pt.mrc = cd.mrc; - if(cd.dir) pt.dir = cd.dir; + if (cd) { + if (cd.mc) pt.mc = cd.mc; + if (cd.mcc) pt.mc = cd.mcc; + if (cd.mlc) pt.mlc = cd.mlc; + if (cd.mlcc) pt.mlc = cd.mlcc; + if (cd.mlw) pt.mlw = cd.mlw; + if (cd.mrc) pt.mrc = cd.mrc; + if (cd.dir) pt.dir = cd.dir; } pt._distinct = true; mockLegend.entries.push([pt]); } - mockLegend.entries.sort(function(a, b) { return a[0].trace.index - b[0].trace.index;}); + mockLegend.entries.sort(function (a, b) { + return a[0].trace.index - b[0].trace.index; + }); mockLegend.layer = container; // Draw unified hover label @@ -1297,34 +1332,65 @@ function createHoverText(hoverData, opts) { var avgY = (winningPoint.y0 + winningPoint.y1) / 2; // When a scatter (or e.g. heatmap) point wins, it's OK for the hovelabel to occlude the bar and other points. var pointWon = !( - Registry.traceIs(winningPoint.trace, 'bar-like') || - Registry.traceIs(winningPoint.trace, 'box-violin') + Registry.traceIs(winningPoint.trace, 'bar-like') || Registry.traceIs(winningPoint.trace, 'box-violin') ); var lyBottom, lyTop; - if(axLetter === 'y') { - if(pointWon) { + if (axLetter === 'y') { + if (pointWon) { lyTop = avgY - HOVERTEXTPAD; lyBottom = avgY + HOVERTEXTPAD; } else { - lyTop = Math.min.apply(null, groupedHoverData.map(function(c) { return Math.min(c.y0, c.y1); })); - lyBottom = Math.max.apply(null, groupedHoverData.map(function(c) { return Math.max(c.y0, c.y1); })); + lyTop = Math.min.apply( + null, + groupedHoverData.map(function (c) { + return Math.min(c.y0, c.y1); + }) + ); + lyBottom = Math.max.apply( + null, + groupedHoverData.map(function (c) { + return Math.max(c.y0, c.y1); + }) + ); } } else { - lyTop = lyBottom = Lib.mean(groupedHoverData.map(function(c) { return (c.y0 + c.y1) / 2; })) - tHeight / 2; + lyTop = lyBottom = + Lib.mean( + groupedHoverData.map(function (c) { + return (c.y0 + c.y1) / 2; + }) + ) - + tHeight / 2; } var lxRight, lxLeft; - if(axLetter === 'x') { - if(pointWon) { + if (axLetter === 'x') { + if (pointWon) { lxRight = avgX + HOVERTEXTPAD; lxLeft = avgX - HOVERTEXTPAD; } else { - lxRight = Math.max.apply(null, groupedHoverData.map(function(c) { return Math.max(c.x0, c.x1); })); - lxLeft = Math.min.apply(null, groupedHoverData.map(function(c) { return Math.min(c.x0, c.x1); })); + lxRight = Math.max.apply( + null, + groupedHoverData.map(function (c) { + return Math.max(c.x0, c.x1); + }) + ); + lxLeft = Math.min.apply( + null, + groupedHoverData.map(function (c) { + return Math.min(c.x0, c.x1); + }) + ); } } else { - lxRight = lxLeft = Lib.mean(groupedHoverData.map(function(c) { return (c.x0 + c.x1) / 2; })) - tWidth / 2; + lxRight = lxLeft = + Lib.mean( + groupedHoverData.map(function (c) { + return (c.x0 + c.x1) / 2; + }) + ) - + tWidth / 2; } var xOffset = xa._offset; @@ -1337,15 +1403,15 @@ function createHoverText(hoverData, opts) { var lx, ly; // top and left positions of the hover box // horizontal alignment to end up on screen - if(lxRight + tWidth < outerWidth && lxRight >= 0) { + if (lxRight + tWidth < outerWidth && lxRight >= 0) { lx = lxRight; - } else if(lxLeft + tWidth < outerWidth && lxLeft >= 0) { + } else if (lxLeft + tWidth < outerWidth && lxLeft >= 0) { lx = lxLeft; - } else if(xOffset + tWidth < outerWidth) { + } else if (xOffset + tWidth < outerWidth) { lx = xOffset; // subplot left corner } else { // closest left or right side of the paper - if(lxRight - avgX < avgX - lxLeft + tWidth) { + if (lxRight - avgX < avgX - lxLeft + tWidth) { lx = outerWidth - tWidth; } else { lx = 0; @@ -1354,15 +1420,15 @@ function createHoverText(hoverData, opts) { lx += HOVERTEXTPAD; // vertical alignement to end up on screen - if(lyBottom + tHeight < outerHeight && lyBottom >= 0) { + if (lyBottom + tHeight < outerHeight && lyBottom >= 0) { ly = lyBottom; - } else if(lyTop + tHeight < outerHeight && lyTop >= 0) { + } else if (lyTop + tHeight < outerHeight && lyTop >= 0) { ly = lyTop; - } else if(yOffset + tHeight < outerHeight) { + } else if (yOffset + tHeight < outerHeight) { ly = yOffset; // subplot top corner } else { // closest top or bottom side of the paper - if(lyBottom - avgY < avgY - lyTop + tHeight) { + if (lyBottom - avgY < avgY - lyTop + tHeight) { ly = outerHeight - tHeight; } else { ly = 0; @@ -1377,59 +1443,51 @@ function createHoverText(hoverData, opts) { // show all the individual labels // first create the objects - var hoverLabels = container.selectAll('g.hovertext') - .data(hoverData, function(d) { - // N.B. when multiple items have the same result key-function value, - // only the first of those items in hoverData gets rendered - return hoverDataKey(d); - }); - hoverLabels.enter().append('g') + var hoverLabels = container.selectAll('g.hovertext').data(hoverData, function (d) { + // N.B. when multiple items have the same result key-function value, + // only the first of those items in hoverData gets rendered + return hoverDataKey(d); + }); + hoverLabels + .enter() + .append('g') .classed('hovertext', true) - .each(function() { + .each(function () { var g = d3.select(this); // trace name label (rect and text.name) - g.append('rect') - .call(Color.fill, Color.addOpacity(bgColor, 0.8)); + g.append('rect').call(Color.fill, Color.addOpacity(bgColor, 0.8)); g.append('text').classed('name', true); // trace data label (path and text.nums) - g.append('path') - .style('stroke-width', '1px'); - g.append('text').classed('nums', true) - .call(Drawing.font, { - weight: fontWeight, - style: fontStyle, - variant: fontVariant, - textcase: fontTextcase, - lineposition: fontLineposition, - shadow: fontShadow, - family: fontFamily, - size: fontSize - }); + g.append('path').style('stroke-width', '1px'); + g.append('text').classed('nums', true).call(Drawing.font, { + weight: fontWeight, + style: fontStyle, + variant: fontVariant, + textcase: fontTextcase, + lineposition: fontLineposition, + shadow: fontShadow, + family: fontFamily, + size: fontSize + }); }); hoverLabels.exit().remove(); // then put the text in, position the pointer to the data, // and figure out sizes - hoverLabels.each(function(d) { + hoverLabels.each(function (d) { var g = d3.select(this).attr('transform', ''); var dColor = d.color; - if(Array.isArray(dColor)) { + if (Array.isArray(dColor)) { dColor = dColor[d.eventData[0].pointNumber]; } // combine possible non-opaque trace color with bgColor var color0 = d.bgcolor || dColor; // color for 'nums' part of the label - var numsColor = Color.combine( - Color.opacity(color0) ? color0 : Color.defaultLine, - bgColor - ); + var numsColor = Color.combine(Color.opacity(color0) ? color0 : Color.defaultLine, bgColor); // color for 'name' part of the label - var nameColor = Color.combine( - Color.opacity(dColor) ? dColor : Color.defaultLine, - bgColor - ); + var nameColor = Color.combine(Color.opacity(dColor) ? dColor : Color.defaultLine, bgColor); // find a contrasting color for border and text var contrastColor = d.borderColor || Color.contrast(numsColor); @@ -1438,7 +1496,8 @@ function createHoverText(hoverData, opts) { var name = texts[1]; // main label - var tx = g.select('text.nums') + var tx = g + .select('text.nums') .call(Drawing.font, { family: d.fontFamily || fontFamily, size: d.fontSize || fontSize, @@ -1448,7 +1507,7 @@ function createHoverText(hoverData, opts) { variant: d.fontVariant || fontVariant, textcase: d.fontTextcase || fontTextcase, lineposition: d.fontLineposition || fontLineposition, - shadow: d.fontShadow || fontShadow, + shadow: d.fontShadow || fontShadow }) .text(text) .attr('data-notex', 1) @@ -1460,7 +1519,7 @@ function createHoverText(hoverData, opts) { var tx2height = 0; // secondary label for non-empty 'name' - if(name && name !== text) { + if (name && name !== text) { tx2.call(Drawing.font, { family: d.fontFamily || fontFamily, size: d.fontSize || fontSize, @@ -1470,8 +1529,9 @@ function createHoverText(hoverData, opts) { variant: d.fontVariant || fontVariant, textcase: d.fontTextcase || fontTextcase, lineposition: d.fontLineposition || fontLineposition, - shadow: d.fontShadow || fontShadow, - }).text(name) + shadow: d.fontShadow || fontShadow + }) + .text(name) .attr('data-notex', 1) .call(svgTextUtils.positionText, 0, 0) .call(svgTextUtils.convertToTspans, gd); @@ -1509,14 +1569,14 @@ function createHoverText(hoverData, opts) { var txTotalWidth = (tbbWidth + HOVERARROWSIZE + HOVERTEXTPAD + tx2width) * fullLayout._invScaleX; var anchorStartOK, anchorEndOK; - if(rotateLabels) { + if (rotateLabels) { d.pos = htx; anchorStartOK = hty + dy / 2 + txTotalWidth <= outerHeight; anchorEndOK = hty - dy / 2 - txTotalWidth >= 0; - if((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) { + if ((d.idealAlign === 'top' || !anchorStartOK) && anchorEndOK) { hty -= dy / 2; d.anchor = 'end'; - } else if(anchorStartOK) { + } else if (anchorStartOK) { hty += dy / 2; d.anchor = 'start'; } else { @@ -1528,10 +1588,10 @@ function createHoverText(hoverData, opts) { anchorStartOK = htx + dx / 2 + txTotalWidth <= outerWidth; anchorEndOK = htx - dx / 2 - txTotalWidth >= 0; - if((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) { + if ((d.idealAlign === 'left' || !anchorStartOK) && anchorEndOK) { htx -= dx / 2; d.anchor = 'end'; - } else if(anchorStartOK) { + } else if (anchorStartOK) { htx += dx / 2; d.anchor = 'start'; } else { @@ -1540,16 +1600,15 @@ function createHoverText(hoverData, opts) { var txHalfWidth = txTotalWidth / 2; var overflowR = htx + txHalfWidth - outerWidth; var overflowL = htx - txHalfWidth; - if(overflowR > 0) htx -= overflowR; - if(overflowL < 0) htx += -overflowL; + if (overflowR > 0) htx -= overflowR; + if (overflowL < 0) htx += -overflowL; } d.crossPos = htx; } tx.attr('text-anchor', d.anchor); - if(tx2width) tx2.attr('text-anchor', d.anchor); - g.attr('transform', strTranslate(htx, hty) + - (rotateLabels ? strRotate(YANGLE) : '')); + if (tx2width) tx2.attr('text-anchor', d.anchor); + g.attr('transform', strTranslate(htx, hty) + (rotateLabels ? strRotate(YANGLE) : '')); }); return { @@ -1562,10 +1621,10 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { var name = ''; var text = ''; // to get custom 'name' labels pass cleanPoint - if(d.nameOverride !== undefined) d.name = d.nameOverride; + if (d.nameOverride !== undefined) d.name = d.nameOverride; - if(d.name) { - if(d.trace._meta) { + if (d.name) { + if (d.trace._meta) { d.name = Lib.templateString(d.name, d.trace._meta); } name = plainText(d.name, d.nameLength); @@ -1574,26 +1633,22 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { var h0 = hovermode.charAt(0); var h1 = h0 === 'x' ? 'y' : 'x'; - if(d.zLabel !== undefined) { - if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; - if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; - if( - d.trace.type !== 'choropleth' && - d.trace.type !== 'choroplethmapbox' && - d.trace.type !== 'choroplethmap' - ) { + if (d.zLabel !== undefined) { + if (d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
'; + if (d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
'; + if (d.trace.type !== 'choropleth' && d.trace.type !== 'choroplethmapbox' && d.trace.type !== 'choroplethmap') { text += (text ? 'z: ' : '') + d.zLabel; } - } else if(showCommonLabel && d[h0 + 'Label'] === t0) { + } else if (showCommonLabel && d[h0 + 'Label'] === t0) { text = d[h1 + 'Label'] || ''; - } else if(d.xLabel === undefined) { - if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') { + } else if (d.xLabel === undefined) { + if (d.yLabel !== undefined && d.trace.type !== 'scattercarpet') { text = d.yLabel; } - } else if(d.yLabel === undefined) text = d.xLabel; + } else if (d.yLabel === undefined) text = d.xLabel; else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; - if((d.text || d.text === 0) && !Array.isArray(d.text)) { + if ((d.text || d.text === 0) && !Array.isArray(d.text)) { text += (text ? '
' : '') + d.text; } @@ -1602,23 +1657,23 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { // the rest of this will still apply, so such modules // can still put things in (x|y|z)Label, text, and name // and hoverinfo will still determine their visibility - if(d.extraText !== undefined) text += (text ? '
' : '') + d.extraText; + if (d.extraText !== undefined) text += (text ? '
' : '') + d.extraText; // if 'text' is empty at this point, // and hovertemplate is not defined, // put 'name' in main label and don't show secondary label - if(g && text === '' && !d.hovertemplate) { + if (g && text === '' && !d.hovertemplate) { // if 'name' is also empty, remove entire label - if(name === '') g.remove(); + if (name === '') g.remove(); text = name; } // hovertemplate var hovertemplate = d.hovertemplate || false; - if(hovertemplate) { + if (hovertemplate) { var labels = d.hovertemplateLabels || d; - if(d[h0 + 'Label'] !== t0) { + if (d[h0 + 'Label'] !== t0) { labels[h0 + 'other'] = labels[h0 + 'Val']; labels[h0 + 'otherLabel'] = labels[h0 + 'Label']; } @@ -1631,7 +1686,7 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { d.trace._meta ); - text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { + text = text.replace(EXTRA_STRING_REGEX, function (match, extra) { // assign name for secondary text label name = plainText(extra, d.nameLength); // remove from main text label @@ -1670,35 +1725,39 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo var axisLabelMinY = commonLabelBoundingBox.minY; var axisLabelMaxY = commonLabelBoundingBox.maxY; - var pX = function(x) { return x * fullLayout._invScaleX; }; - var pY = function(y) { return y * fullLayout._invScaleY; }; + var pX = function (x) { + return x * fullLayout._invScaleX; + }; + var pY = function (y) { + return y * fullLayout._invScaleY; + }; - hoverLabels.each(function(d) { + hoverLabels.each(function (d) { var ax = d[axKey]; var crossAx = d[crossAxKey]; var axIsX = ax._id.charAt(0) === 'x'; var rng = ax.range; - if(k === 0 && rng && ((rng[0] > rng[1]) !== axIsX)) { + if (k === 0 && rng && rng[0] > rng[1] !== axIsX) { axSign = -1; } var pmin = 0; - var pmax = (axIsX ? fullLayout.width : fullLayout.height); + var pmax = axIsX ? fullLayout.width : fullLayout.height; // in hovermode avoid overlap between hover labels and axis label - if(fullLayout.hovermode === 'x' || fullLayout.hovermode === 'y') { + if (fullLayout.hovermode === 'x' || fullLayout.hovermode === 'y') { // extent of rect behind hover label on cross axis: var offsets = getHoverLabelOffsets(d, rotateLabels); var anchor = d.anchor; var horzSign = anchor === 'end' ? -1 : 1; var labelMin; var labelMax; - if(anchor === 'middle') { + if (anchor === 'middle') { // use extent of centered rect either on x or y axis depending on current axis labelMin = d.crossPos + (axIsX ? pY(offsets.y - d.by / 2) : pX(d.bx / 2 + d.tx2width / 2)); labelMax = labelMin + (axIsX ? pY(d.by) : pX(d.bx)); } else { // use extend of path (see alignHoverText function) without arrow - if(axIsX) { + if (axIsX) { labelMin = d.crossPos + pY(HOVERARROWSIZE + offsets.y) - pY(d.by / 2 - HOVERARROWSIZE); labelMax = labelMin + pY(d.by); } else { @@ -1709,10 +1768,14 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo } } - if(axIsX) { - if(axisLabelMinY !== undefined && axisLabelMaxY !== undefined && Math.min(labelMax, axisLabelMaxY) - Math.max(labelMin, axisLabelMinY) > 1) { + if (axIsX) { + if ( + axisLabelMinY !== undefined && + axisLabelMaxY !== undefined && + Math.min(labelMax, axisLabelMaxY) - Math.max(labelMin, axisLabelMinY) > 1 + ) { // has at least 1 pixel overlap with axis label - if(crossAx.side === 'left') { + if (crossAx.side === 'left') { pmin = crossAx._mainLinePosition; pmax = fullLayout.width; } else { @@ -1720,9 +1783,13 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo } } } else { - if(axisLabelMinX !== undefined && axisLabelMaxX !== undefined && Math.min(labelMax, axisLabelMaxX) - Math.max(labelMin, axisLabelMinX) > 1) { + if ( + axisLabelMinX !== undefined && + axisLabelMaxX !== undefined && + Math.min(labelMax, axisLabelMaxX) - Math.max(labelMin, axisLabelMinX) > 1 + ) { // has at least 1 pixel overlap with axis label - if(crossAx.side === 'top') { + if (crossAx.side === 'top') { pmin = crossAx._mainLinePosition; pmax = fullLayout.height; } else { @@ -1732,25 +1799,29 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo } } - pointgroups[k++] = [{ - datum: d, - traceIndex: d.trace.index, - dp: 0, - pos: d.pos, - posref: d.posref, - size: d.by * (axIsX ? YFACTOR : 1) / 2, - pmin: pmin, - pmax: pmax - }]; + pointgroups[k++] = [ + { + datum: d, + traceIndex: d.trace.index, + dp: 0, + pos: d.pos, + posref: d.posref, + size: (d.by * (axIsX ? YFACTOR : 1)) / 2, + pmin: pmin, + pmax: pmax + } + ]; }); - pointgroups.sort(function(a, b) { - return (a[0].posref - b[0].posref) || + pointgroups.sort(function (a, b) { + return ( + a[0].posref - b[0].posref || // for equal positions, sort trace indices increasing or decreasing // depending on whether the axis is reversed or not... so stacked // traces will generally keep their order even if one trace adds // nothing to the stack. - (axSign * (b[0].traceIndex - a[0].traceIndex)); + axSign * (b[0].traceIndex - a[0].traceIndex) + ); }); var donepositioning, topOverlap, bottomOverlap, i, j, pti, sumdp; @@ -1769,59 +1840,59 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo // see the largest labels // allow for .01px overlap, so we don't get an // infinite loop from rounding errors - if(topOverlap > 0.01) { - for(j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap; + if (topOverlap > 0.01) { + for (j = grp.length - 1; j >= 0; j--) grp[j].dp += topOverlap; donepositioning = false; } - if(bottomOverlap < 0.01) return; - if(topOverlap < -0.01) { + if (bottomOverlap < 0.01) return; + if (topOverlap < -0.01) { // make sure we're not pushing back and forth - for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; + for (j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; donepositioning = false; } - if(!donepositioning) return; + if (!donepositioning) return; // no room to fix positioning, delete off-screen points // first see how many points we need to delete var deleteCount = 0; - for(i = 0; i < grp.length; i++) { + for (i = 0; i < grp.length; i++) { pti = grp[i]; - if(pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++; + if (pti.pos + pti.dp + pti.size > minPt.pmax) deleteCount++; } // start by deleting points whose data is off screen - for(i = grp.length - 1; i >= 0; i--) { - if(deleteCount <= 0) break; + for (i = grp.length - 1; i >= 0; i--) { + if (deleteCount <= 0) break; pti = grp[i]; // pos has already been constrained to [pmin,pmax] // so look for points close to that to delete - if(pti.pos > minPt.pmax - 1) { + if (pti.pos > minPt.pmax - 1) { pti.del = true; deleteCount--; } } - for(i = 0; i < grp.length; i++) { - if(deleteCount <= 0) break; + for (i = 0; i < grp.length; i++) { + if (deleteCount <= 0) break; pti = grp[i]; // pos has already been constrained to [pmin,pmax] // so look for points close to that to delete - if(pti.pos < minPt.pmin + 1) { + if (pti.pos < minPt.pmin + 1) { pti.del = true; deleteCount--; // shift the whole group minus into this new space bottomOverlap = pti.size * 2; - for(j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; + for (j = grp.length - 1; j >= 0; j--) grp[j].dp -= bottomOverlap; } } // then delete points that go off the bottom - for(i = grp.length - 1; i >= 0; i--) { - if(deleteCount <= 0) break; + for (i = grp.length - 1; i >= 0; i--) { + if (deleteCount <= 0) break; pti = grp[i]; - if(pti.pos + pti.dp + pti.size > minPt.pmax) { + if (pti.pos + pti.dp + pti.size > minPt.pmax) { pti.del = true; deleteCount--; } @@ -1830,7 +1901,7 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo // loop through groups, combining them if they overlap, // until nothing moves - while(!donepositioning && nummoves <= nLabels) { + while (!donepositioning && nummoves <= nLabels) { // to avoid infinite loops, don't move more times // than there are traces nummoves++; @@ -1839,7 +1910,7 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo // reverse this if it does donepositioning = true; i = 0; - while(i < pointgroups.length - 1) { + while (i < pointgroups.length - 1) { // the higher (g0) and lower (g1) point group var g0 = pointgroups[i]; var g1 = pointgroups[i + 1]; @@ -1850,9 +1921,9 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo var p1 = g1[0]; topOverlap = p0.pos + p0.dp + p0.size - p1.pos - p1.dp + p1.size; - if(topOverlap > 0.01) { + if (topOverlap > 0.01) { // push the new point(s) added to this group out of the way - for(j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap; + for (j = g1.length - 1; j >= 0; j--) g1[j].dp += topOverlap; // add them to the group g0.push.apply(g0, g1); @@ -1860,9 +1931,9 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo // adjust for minimum average movement sumdp = 0; - for(j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp; + for (j = g0.length - 1; j >= 0; j--) sumdp += g0[j].dp; bottomOverlap = sumdp / g0.length; - for(j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap; + for (j = g0.length - 1; j >= 0; j--) g0[j].dp -= bottomOverlap; donepositioning = false; } else i++; } @@ -1872,9 +1943,9 @@ function hoverAvoidOverlaps(hoverLabels, rotateLabels, fullLayout, commonLabelBo } // now put these offsets into hoverData - for(i = pointgroups.length - 1; i >= 0; i--) { + for (i = pointgroups.length - 1; i >= 0; i--) { var grp = pointgroups[i]; - for(j = grp.length - 1; j >= 0; j--) { + for (j = grp.length - 1; j >= 0; j--) { var pt = grp[j]; var hoverPt = pt.datum; hoverPt.offset = pt.dp; @@ -1887,7 +1958,7 @@ function getHoverLabelOffsets(hoverLabel, rotateLabels) { var offsetX = 0; var offsetY = hoverLabel.offset; - if(rotateLabels) { + if (rotateLabels) { offsetY *= -YSHIFTY; offsetX = hoverLabel.offset * YSHIFTX; } @@ -1902,12 +1973,12 @@ function getHoverLabelOffsets(hoverLabel, rotateLabels) { * Calculate the shift in x for text and text2 elements */ function getTextShiftX(hoverLabel) { - var alignShift = {start: 1, end: -1, middle: 0}[hoverLabel.anchor]; + var alignShift = { start: 1, end: -1, middle: 0 }[hoverLabel.anchor]; var textShiftX = alignShift * (HOVERARROWSIZE + HOVERTEXTPAD); var text2ShiftX = textShiftX + alignShift * (hoverLabel.txwidth + HOVERTEXTPAD); var isMiddle = hoverLabel.anchor === 'middle'; - if(isMiddle) { + if (isMiddle) { textShiftX -= hoverLabel.tx2width / 2; text2ShiftX += hoverLabel.txwidth / 2 + HOVERTEXTPAD; } @@ -1920,14 +1991,18 @@ function getTextShiftX(hoverLabel) { } function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) { - var pX = function(x) { return x * scaleX; }; - var pY = function(y) { return y * scaleY; }; + var pX = function (x) { + return x * scaleX; + }; + var pY = function (y) { + return y * scaleY; + }; // finally set the text positioning relative to the data and draw the // box around it - hoverLabels.each(function(d) { + hoverLabels.each(function (d) { var g = d3.select(this); - if(d.del) return g.remove(); + if (d.del) return g.remove(); var tx = g.select('text.nums'); var anchor = d.anchor; @@ -1943,23 +2018,52 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) { var showArrow = 'hoverlabel' in d.trace ? d.trace.hoverlabel.showarrow : true; var pathStr; - if(isMiddle) { + if (isMiddle) { // middle aligned: rect centered on data - pathStr = 'M-' + pX(d.bx / 2 + d.tx2width / 2) + ',' + pY(offsetY - d.by / 2) + - 'h' + pX(d.bx) + 'v' + pY(d.by) + 'h-' + pX(d.bx) + 'Z'; - } else if(showArrow) { + pathStr = + 'M-' + + pX(d.bx / 2 + d.tx2width / 2) + + ',' + + pY(offsetY - d.by / 2) + + 'h' + + pX(d.bx) + + 'v' + + pY(d.by) + + 'h-' + + pX(d.bx) + + 'Z'; + } else if (showArrow) { // left or right aligned: side rect with arrow to data - pathStr = 'M0,0L' + pX(horzSign * HOVERARROWSIZE + offsetX) + ',' + pY(HOVERARROWSIZE + offsetY) + - 'v' + pY(d.by / 2 - HOVERARROWSIZE) + - 'h' + pX(horzSign * d.bx) + - 'v-' + pY(d.by) + - 'H' + pX(horzSign * HOVERARROWSIZE + offsetX) + - 'V' + pY(offsetY - HOVERARROWSIZE) + - 'Z'; + pathStr = + 'M0,0L' + + pX(horzSign * HOVERARROWSIZE + offsetX) + + ',' + + pY(HOVERARROWSIZE + offsetY) + + 'v' + + pY(d.by / 2 - HOVERARROWSIZE) + + 'h' + + pX(horzSign * d.bx) + + 'v-' + + pY(d.by) + + 'H' + + pX(horzSign * HOVERARROWSIZE + offsetX) + + 'V' + + pY(offsetY - HOVERARROWSIZE) + + 'Z'; } else { // left or right aligned: side rect without arrow - pathStr = 'M' + pX(horzSign * HOVERARROWSIZE + offsetX) + ',' + pY(offsetY - d.by / 2) + - 'h' + pX(horzSign * d.bx) + 'v' + pY(d.by) + 'h' + pX(-horzSign * d.bx) + 'Z'; + pathStr = + 'M' + + pX(horzSign * HOVERARROWSIZE + offsetX) + + ',' + + pY(offsetY - d.by / 2) + + 'h' + + pX(horzSign * d.bx) + + 'v' + + pY(d.by) + + 'h' + + pX(-horzSign * d.bx) + + 'Z'; } g.select('path').attr('d', pathStr); @@ -1967,32 +2071,31 @@ function alignHoverText(hoverLabels, rotateLabels, scaleX, scaleY) { var posY = offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD; var textAlign = d.textAlign || 'auto'; - if(textAlign !== 'auto') { - if(textAlign === 'left' && anchor !== 'start') { + if (textAlign !== 'auto') { + if (textAlign === 'left' && anchor !== 'start') { tx.attr('text-anchor', 'start'); - posX = isMiddle ? - -d.bx / 2 - d.tx2width / 2 + HOVERTEXTPAD : - -d.bx - HOVERTEXTPAD; - } else if(textAlign === 'right' && anchor !== 'end') { + posX = isMiddle ? -d.bx / 2 - d.tx2width / 2 + HOVERTEXTPAD : -d.bx - HOVERTEXTPAD; + } else if (textAlign === 'right' && anchor !== 'end') { tx.attr('text-anchor', 'end'); - posX = isMiddle ? - d.bx / 2 - d.tx2width / 2 - HOVERTEXTPAD : - d.bx + HOVERTEXTPAD; + posX = isMiddle ? d.bx / 2 - d.tx2width / 2 - HOVERTEXTPAD : d.bx + HOVERTEXTPAD; } } tx.call(svgTextUtils.positionText, pX(posX), pY(posY)); - if(d.tx2width) { - g.select('text.name') - .call(svgTextUtils.positionText, - pX(shiftX.text2ShiftX + shiftX.alignShift * HOVERTEXTPAD + offsetX), - pY(offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD)); - g.select('rect') - .call(Drawing.setRect, - pX(shiftX.text2ShiftX + (shiftX.alignShift - 1) * d.tx2width / 2 + offsetX), - pY(offsetY - d.by / 2 - 1), - pX(d.tx2width), pY(d.by + 2)); + if (d.tx2width) { + g.select('text.name').call( + svgTextUtils.positionText, + pX(shiftX.text2ShiftX + shiftX.alignShift * HOVERTEXTPAD + offsetX), + pY(offsetY + d.ty0 - d.by / 2 + HOVERTEXTPAD) + ); + g.select('rect').call( + Drawing.setRect, + pX(shiftX.text2ShiftX + ((shiftX.alignShift - 1) * d.tx2width) / 2 + offsetX), + pY(offsetY - d.by / 2 - 1), + pX(d.tx2width), + pY(d.by + 2) + ); } }); } @@ -2007,18 +2110,18 @@ function cleanPoint(d, hovermode) { return v || (isNumeric(v) && v === 0); } - var getVal = Array.isArray(index) ? - function(calcKey, traceKey) { - var v = Lib.castOption(cd0, index, calcKey); - return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey); - } : - function(calcKey, traceKey) { - return Lib.extractOption(cd, trace, calcKey, traceKey); - }; + var getVal = Array.isArray(index) + ? function (calcKey, traceKey) { + var v = Lib.castOption(cd0, index, calcKey); + return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey); + } + : function (calcKey, traceKey) { + return Lib.extractOption(cd, trace, calcKey, traceKey); + }; function fill(key, calcKey, traceKey) { var val = getVal(calcKey, traceKey); - if(pass(val)) d[key] = val; + if (pass(val)) d[key] = val; } fill('hoverinfo', 'hi', 'hoverinfo'); @@ -2033,9 +2136,10 @@ function cleanPoint(d, hovermode) { fill('nameLength', 'hnl', 'hoverlabel.namelength'); fill('textAlign', 'hta', 'hoverlabel.align'); - d.posref = (hovermode === 'y' || (hovermode === 'closest' && trace.orientation === 'h')) ? - (d.xa._offset + (d.x0 + d.x1) / 2) : - (d.ya._offset + (d.y0 + d.y1) / 2); + d.posref = + hovermode === 'y' || (hovermode === 'closest' && trace.orientation === 'h') + ? d.xa._offset + (d.x0 + d.x1) / 2 + : d.ya._offset + (d.y0 + d.y1) / 2; // then constrain all the positions to be on the plot d.x0 = Lib.constrain(d.x0, 0, d.xa._length); @@ -2044,52 +2148,50 @@ function cleanPoint(d, hovermode) { d.y1 = Lib.constrain(d.y1, 0, d.ya._length); // and convert the x and y label values into formatted text - if(d.xLabelVal !== undefined) { - d.xLabel = ('xLabel' in d) ? d.xLabel : Axes.hoverLabelText(d.xa, d.xLabelVal, trace.xhoverformat); + if (d.xLabelVal !== undefined) { + d.xLabel = 'xLabel' in d ? d.xLabel : Axes.hoverLabelText(d.xa, d.xLabelVal, trace.xhoverformat); d.xVal = d.xa.c2d(d.xLabelVal); } - if(d.yLabelVal !== undefined) { - d.yLabel = ('yLabel' in d) ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal, trace.yhoverformat); + if (d.yLabelVal !== undefined) { + d.yLabel = 'yLabel' in d ? d.yLabel : Axes.hoverLabelText(d.ya, d.yLabelVal, trace.yhoverformat); d.yVal = d.ya.c2d(d.yLabelVal); } // Traces like heatmaps generate the zLabel in their hoverPoints function - if(d.zLabelVal !== undefined && d.zLabel === undefined) { + if (d.zLabelVal !== undefined && d.zLabel === undefined) { d.zLabel = String(d.zLabelVal); } // for box means and error bars, add the range to the label - if(!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) { + if (!isNaN(d.xerr) && !(d.xa.type === 'log' && d.xerr <= 0)) { var xeText = Axes.tickText(d.xa, d.xa.c2l(d.xerr), 'hover').text; - if(d.xerrneg !== undefined) { - d.xLabel += ' +' + xeText + ' / -' + - Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text; + if (d.xerrneg !== undefined) { + d.xLabel += ' +' + xeText + ' / -' + Axes.tickText(d.xa, d.xa.c2l(d.xerrneg), 'hover').text; } else d.xLabel += ' ± ' + xeText; // small distance penalty for error bars, so that if there are // traces with errors and some without, the error bar label will // hoist up to the point - if(hovermode === 'x') d.distance += 1; + if (hovermode === 'x') d.distance += 1; } - if(!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) { + if (!isNaN(d.yerr) && !(d.ya.type === 'log' && d.yerr <= 0)) { var yeText = Axes.tickText(d.ya, d.ya.c2l(d.yerr), 'hover').text; - if(d.yerrneg !== undefined) { - d.yLabel += ' +' + yeText + ' / -' + - Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text; + if (d.yerrneg !== undefined) { + d.yLabel += ' +' + yeText + ' / -' + Axes.tickText(d.ya, d.ya.c2l(d.yerrneg), 'hover').text; } else d.yLabel += ' ± ' + yeText; - if(hovermode === 'y') d.distance += 1; + if (hovermode === 'y') d.distance += 1; } var infomode = d.hoverinfo || d.trace.hoverinfo; - if(infomode && infomode !== 'all') { + if (infomode && infomode !== 'all') { infomode = Array.isArray(infomode) ? infomode : infomode.split('+'); - if(infomode.indexOf('x') === -1) d.xLabel = undefined; - if(infomode.indexOf('y') === -1) d.yLabel = undefined; - if(infomode.indexOf('z') === -1) d.zLabel = undefined; - if(infomode.indexOf('text') === -1) d.text = undefined; - if(infomode.indexOf('name') === -1) d.name = undefined; + if (infomode.indexOf('x') === -1) d.xLabel = undefined; + if (infomode.indexOf('y') === -1) d.yLabel = undefined; + if (infomode.indexOf('z') === -1) d.zLabel = undefined; + if (infomode.indexOf('text') === -1) d.text = undefined; + if (infomode.indexOf('name') === -1) d.name = undefined; } return d; @@ -2108,12 +2210,12 @@ function createSpikelines(gd, closestPoints, opts) { // Remove old spikeline items container.selectAll('.spikeline').remove(); - if(!(showX || showY)) return; + if (!(showX || showY)) return; var contrastColor = Color.combine(fullLayout.plot_bgcolor, fullLayout.paper_bgcolor); // Horizontal line (to y-axis) - if(showY) { + if (showY) { var hLinePoint = closestPoints.hLinePoint; var hLinePointX, hLinePointY; @@ -2121,30 +2223,32 @@ function createSpikelines(gd, closestPoints, opts) { ya = hLinePoint && hLinePoint.ya; var ySnap = ya.spikesnap; - if(ySnap === 'cursor') { + if (ySnap === 'cursor') { hLinePointX = evt.pointerX; hLinePointY = evt.pointerY; } else { hLinePointX = xa._offset + hLinePoint.x; hLinePointY = ya._offset + hLinePoint.y; } - var dfltHLineColor = tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 ? - Color.contrast(contrastColor) : hLinePoint.color; + var dfltHLineColor = + tinycolor.readability(hLinePoint.color, contrastColor) < 1.5 + ? Color.contrast(contrastColor) + : hLinePoint.color; var yMode = ya.spikemode; var yThickness = ya.spikethickness; var yColor = ya.spikecolor || dfltHLineColor; var xEdge = Axes.getPxPosition(gd, ya); var xBase, xEndSpike; - if(yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) { - if(yMode.indexOf('toaxis') !== -1) { + if (yMode.indexOf('toaxis') !== -1 || yMode.indexOf('across') !== -1) { + if (yMode.indexOf('toaxis') !== -1) { xBase = xEdge; xEndSpike = hLinePointX; } - if(yMode.indexOf('across') !== -1) { + if (yMode.indexOf('across') !== -1) { var xAcross0 = ya._counterDomainMin; var xAcross1 = ya._counterDomainMax; - if(ya.anchor === 'free') { + if (ya.anchor === 'free') { xAcross0 = Math.min(xAcross0, ya.position); xAcross1 = Math.max(xAcross1, ya.position); } @@ -2153,7 +2257,8 @@ function createSpikelines(gd, closestPoints, opts) { } // Foreground horizontal line (to y-axis) - container.insert('line', ':first-child') + container + .insert('line', ':first-child') .attr({ x1: xBase, x2: xEndSpike, @@ -2167,7 +2272,8 @@ function createSpikelines(gd, closestPoints, opts) { .classed('crisp', true); // Background horizontal Line (to y-axis) - container.insert('line', ':first-child') + container + .insert('line', ':first-child') .attr({ x1: xBase, x2: xEndSpike, @@ -2180,8 +2286,9 @@ function createSpikelines(gd, closestPoints, opts) { .classed('crisp', true); } // Y axis marker - if(yMode.indexOf('marker') !== -1) { - container.insert('circle', ':first-child') + if (yMode.indexOf('marker') !== -1) { + container + .insert('circle', ':first-child') .attr({ cx: xEdge + (ya.side !== 'right' ? yThickness : -yThickness), cy: hLinePointY, @@ -2192,7 +2299,7 @@ function createSpikelines(gd, closestPoints, opts) { } } - if(showX) { + if (showX) { var vLinePoint = closestPoints.vLinePoint; var vLinePointX, vLinePointY; @@ -2200,30 +2307,32 @@ function createSpikelines(gd, closestPoints, opts) { ya = vLinePoint && vLinePoint.ya; var xSnap = xa.spikesnap; - if(xSnap === 'cursor') { + if (xSnap === 'cursor') { vLinePointX = evt.pointerX; vLinePointY = evt.pointerY; } else { vLinePointX = xa._offset + vLinePoint.x; vLinePointY = ya._offset + vLinePoint.y; } - var dfltVLineColor = tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 ? - Color.contrast(contrastColor) : vLinePoint.color; + var dfltVLineColor = + tinycolor.readability(vLinePoint.color, contrastColor) < 1.5 + ? Color.contrast(contrastColor) + : vLinePoint.color; var xMode = xa.spikemode; var xThickness = xa.spikethickness; var xColor = xa.spikecolor || dfltVLineColor; var yEdge = Axes.getPxPosition(gd, xa); var yBase, yEndSpike; - if(xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) { - if(xMode.indexOf('toaxis') !== -1) { + if (xMode.indexOf('toaxis') !== -1 || xMode.indexOf('across') !== -1) { + if (xMode.indexOf('toaxis') !== -1) { yBase = yEdge; yEndSpike = vLinePointY; } - if(xMode.indexOf('across') !== -1) { + if (xMode.indexOf('across') !== -1) { var yAcross0 = xa._counterDomainMin; var yAcross1 = xa._counterDomainMax; - if(xa.anchor === 'free') { + if (xa.anchor === 'free') { yAcross0 = Math.min(yAcross0, xa.position); yAcross1 = Math.max(yAcross1, xa.position); } @@ -2232,7 +2341,8 @@ function createSpikelines(gd, closestPoints, opts) { } // Foreground vertical line (to x-axis) - container.insert('line', ':first-child') + container + .insert('line', ':first-child') .attr({ x1: vLinePointX, x2: vLinePointX, @@ -2246,7 +2356,8 @@ function createSpikelines(gd, closestPoints, opts) { .classed('crisp', true); // Background vertical line (to x-axis) - container.insert('line', ':first-child') + container + .insert('line', ':first-child') .attr({ x1: vLinePointX, x2: vLinePointX, @@ -2260,8 +2371,9 @@ function createSpikelines(gd, closestPoints, opts) { } // X axis marker - if(xMode.indexOf('marker') !== -1) { - container.insert('circle', ':first-child') + if (xMode.indexOf('marker') !== -1) { + container + .insert('circle', ':first-child') .attr({ cx: vLinePointX, cy: yEdge - (xa.side !== 'top' ? xThickness : -xThickness), @@ -2275,13 +2387,14 @@ function createSpikelines(gd, closestPoints, opts) { function hoverChanged(gd, evt, oldhoverdata) { // don't emit any events if nothing changed - if(!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true; + if (!oldhoverdata || oldhoverdata.length !== gd._hoverdata.length) return true; - for(var i = oldhoverdata.length - 1; i >= 0; i--) { + for (var i = oldhoverdata.length - 1; i >= 0; i--) { var oldPt = oldhoverdata[i]; var newPt = gd._hoverdata[i]; - if(oldPt.curveNumber !== newPt.curveNumber || + if ( + oldPt.curveNumber !== newPt.curveNumber || String(oldPt.pointNumber) !== String(newPt.pointNumber) || String(oldPt.pointNumbers) !== String(newPt.pointNumbers) || oldPt.binNumber !== newPt.binNumber @@ -2294,10 +2407,12 @@ function hoverChanged(gd, evt, oldhoverdata) { function spikesChanged(gd, oldspikepoints) { // don't relayout the plot because of new spikelines if spikelines points didn't change - if(!oldspikepoints) return true; - if(oldspikepoints.vLinePoint !== gd._spikepoints.vLinePoint || + if (!oldspikepoints) return true; + if ( + oldspikepoints.vLinePoint !== gd._spikepoints.vLinePoint || oldspikepoints.hLinePoint !== gd._spikepoints.hLinePoint - ) return true; + ) + return true; return false; } @@ -2315,15 +2430,12 @@ function orderRangePoints(hoverData, hovermode) { var second = []; var last = []; - for(var i = 0; i < hoverData.length; i++) { + for (var i = 0; i < hoverData.length; i++) { var d = hoverData[i]; - if( - Registry.traceIs(d.trace, 'bar-like') || - Registry.traceIs(d.trace, 'box-violin') - ) { + if (Registry.traceIs(d.trace, 'bar-like') || Registry.traceIs(d.trace, 'box-violin')) { last.push(d); - } else if(d.trace[axLetter + 'period']) { + } else if (d.trace[axLetter + 'period']) { second.push(d); } else { first.push(d); @@ -2339,23 +2451,23 @@ function getCoord(axLetter, winningPoint, fullLayout) { var cd0 = winningPoint.cd[0]; - if(ax.type === 'category' || ax.type === 'multicategory') val = ax._categoriesMap[val]; - else if(ax.type === 'date') { + if (ax.type === 'category' || ax.type === 'multicategory') val = ax._categoriesMap[val]; + else if (ax.type === 'date') { var periodalignment = winningPoint.trace[axLetter + 'periodalignment']; - if(periodalignment) { + if (periodalignment) { var d = winningPoint.cd[winningPoint.index]; var start = d[axLetter + 'Start']; - if(start === undefined) start = d[axLetter]; + if (start === undefined) start = d[axLetter]; var end = d[axLetter + 'End']; - if(end === undefined) end = d[axLetter]; + if (end === undefined) end = d[axLetter]; var diff = end - start; - if(periodalignment === 'end') { + if (periodalignment === 'end') { val += diff; - } else if(periodalignment === 'middle') { + } else if (periodalignment === 'middle') { val += diff / 2; } } @@ -2363,11 +2475,8 @@ function getCoord(axLetter, winningPoint, fullLayout) { val = ax.d2c(val); } - if(cd0 && cd0.t && cd0.t.posLetter === ax._id) { - if( - fullLayout.boxmode === 'group' || - fullLayout.violinmode === 'group' - ) { + if (cd0 && cd0.t && cd0.t.posLetter === ax._id) { + if (fullLayout.boxmode === 'group' || fullLayout.violinmode === 'group') { val += cd0.t.dPos; } } @@ -2378,8 +2487,12 @@ function getCoord(axLetter, winningPoint, fullLayout) { // Top/left hover offsets relative to graph div. As long as hover content is // a sibling of the graph div, it will be positioned correctly relative to // the offset parent, whatever that may be. -function getTopOffset(gd) { return gd.offsetTop + gd.clientTop; } -function getLeftOffset(gd) { return gd.offsetLeft + gd.clientLeft; } +function getTopOffset(gd) { + return gd.offsetTop + gd.clientTop; +} +function getLeftOffset(gd) { + return gd.offsetLeft + gd.clientLeft; +} function getBoundingClientRect(gd, node) { var fullLayout = gd._fullLayout; @@ -2407,6 +2520,6 @@ function getBoundingClientRect(gd, node) { top: Math.min(Ay, By), left: Math.min(Ax, Bx), right: Math.max(Ax, Bx), - bottom: Math.max(Ay, By), + bottom: Math.max(Ay, By) }; } diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 71a5475aee0..6902aa4cd74 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -26,10 +26,7 @@ module.exports = templatedArray('shape', { valType: 'boolean', dflt: false, editType: 'calc+arraydraw', - description: [ - 'Determines whether or not this', - 'shape is shown in the legend.' - ].join(' ') + description: ['Determines whether or not this', 'shape is shown in the legend.'].join(' ') }, legend: extendFlat({}, basePlotAttributes.legend, { @@ -57,11 +54,9 @@ module.exports = templatedArray('shape', { }), font: fontAttrs({ editType: 'calc+arraydraw', - description: [ - 'Sets this legend group\'s title font.' - ].join(' '), + description: ["Sets this legend group's title font."].join(' ') }), - editType: 'calc+arraydraw', + editType: 'calc+arraydraw' }, legendrank: extendFlat({}, basePlotAttributes.legendrank, { @@ -80,7 +75,7 @@ module.exports = templatedArray('shape', { legendwidth: extendFlat({}, basePlotAttributes.legendwidth, { editType: 'calc+arraydraw', - description: 'Sets the width (in px or fraction) of the legend for this shape.', + description: 'Sets the width (in px or fraction) of the legend for this shape.' }), type: { @@ -91,20 +86,20 @@ module.exports = templatedArray('shape', { 'Specifies the shape type to be drawn.', 'If *line*, a line is drawn from (`x0`,`y0`) to (`x1`,`y1`)', - 'with respect to the axes\' sizing mode.', + "with respect to the axes' sizing mode.", 'If *circle*, a circle is drawn from', '((`x0`+`x1`)/2, (`y0`+`y1`)/2))', 'with radius', '(|(`x0`+`x1`)/2 - `x0`|, |(`y0`+`y1`)/2 -`y0`)|)', - 'with respect to the axes\' sizing mode.', + "with respect to the axes' sizing mode.", 'If *rect*, a rectangle is drawn linking', '(`x0`,`y0`), (`x1`,`y0`), (`x1`,`y1`), (`x0`,`y1`), (`x0`,`y0`)', - 'with respect to the axes\' sizing mode.', + "with respect to the axes' sizing mode.", 'If *path*, draw a custom SVG path using `path`.', - 'with respect to the axes\' sizing mode.' + "with respect to the axes' sizing mode." ].join(' ') }, @@ -121,7 +116,7 @@ module.exports = templatedArray('shape', { xref: extendFlat({}, annAttrs.xref, { description: [ - 'Sets the shape\'s x coordinate axis.', + "Sets the shape's x coordinate axis.", axisPlaceableObjs.axisRefDescription('x', 'left', 'right') ].join(' ') }), @@ -131,9 +126,9 @@ module.exports = templatedArray('shape', { dflt: 'scaled', editType: 'calc+arraydraw', description: [ - 'Sets the shapes\'s sizing mode along the x axis.', + "Sets the shapes's sizing mode along the x axis.", 'If set to *scaled*, `x0`, `x1` and x coordinates within `path` refer to', - 'data values on the x axis or a fraction of the plot area\'s width', + "data values on the x axis or a fraction of the plot area's width", '(`xref` set to *paper*).', 'If set to *pixel*, `xanchor` specifies the x position in terms', 'of data or plot fraction but `x0`, `x1` and x coordinates within `path`', @@ -156,18 +151,12 @@ module.exports = templatedArray('shape', { x0: { valType: 'any', editType: 'calc+arraydraw', - description: [ - 'Sets the shape\'s starting x position.', - 'See `type` and `xsizemode` for more info.' - ].join(' ') + description: ["Sets the shape's starting x position.", 'See `type` and `xsizemode` for more info.'].join(' ') }, x1: { valType: 'any', editType: 'calc+arraydraw', - description: [ - 'Sets the shape\'s end x position.', - 'See `type` and `xsizemode` for more info.' - ].join(' ') + description: ["Sets the shape's end x position.", 'See `type` and `xsizemode` for more info.'].join(' ') }, x0shift: { valType: 'number', @@ -195,7 +184,7 @@ module.exports = templatedArray('shape', { }, yref: extendFlat({}, annAttrs.yref, { description: [ - 'Sets the shape\'s y coordinate axis.', + "Sets the shape's y coordinate axis.", axisPlaceableObjs.axisRefDescription('y', 'bottom', 'top') ].join(' ') }), @@ -205,9 +194,9 @@ module.exports = templatedArray('shape', { dflt: 'scaled', editType: 'calc+arraydraw', description: [ - 'Sets the shapes\'s sizing mode along the y axis.', + "Sets the shapes's sizing mode along the y axis.", 'If set to *scaled*, `y0`, `y1` and y coordinates within `path` refer to', - 'data values on the y axis or a fraction of the plot area\'s height', + "data values on the y axis or a fraction of the plot area's height", '(`yref` set to *paper*).', 'If set to *pixel*, `yanchor` specifies the y position in terms', 'of data or plot fraction but `y0`, `y1` and y coordinates within `path`', @@ -230,18 +219,12 @@ module.exports = templatedArray('shape', { y0: { valType: 'any', editType: 'calc+arraydraw', - description: [ - 'Sets the shape\'s starting y position.', - 'See `type` and `ysizemode` for more info.' - ].join(' ') + description: ["Sets the shape's starting y position.", 'See `type` and `ysizemode` for more info.'].join(' ') }, y1: { valType: 'any', editType: 'calc+arraydraw', - description: [ - 'Sets the shape\'s end y position.', - 'See `type` and `ysizemode` for more info.' - ].join(' ') + description: ["Sets the shape's end y position.", 'See `type` and `ysizemode` for more info.'].join(' ') }, y0shift: { valType: 'number', @@ -291,8 +274,8 @@ module.exports = templatedArray('shape', { 'of categories because using the categories themselves there would', 'be no way to describe fractional positions', 'On data axes: because space and T are both normal components of path', - 'strings, we can\'t use either to separate date from time parts.', - 'Therefore we\'ll use underscore for this purpose:', + "strings, we can't use either to separate date from time parts.", + "Therefore we'll use underscore for this purpose:", '2015-02-21_13:45:56.789' ].join(' ') }, @@ -306,18 +289,16 @@ module.exports = templatedArray('shape', { description: 'Sets the opacity of the shape.' }, line: { - color: extendFlat({}, scatterLineAttrs.color, {editType: 'arraydraw'}), - width: extendFlat({}, scatterLineAttrs.width, {editType: 'calc+arraydraw'}), - dash: extendFlat({}, dash, {editType: 'arraydraw'}), + color: extendFlat({}, scatterLineAttrs.color, { editType: 'arraydraw' }), + width: extendFlat({}, scatterLineAttrs.width, { editType: 'calc+arraydraw' }), + dash: extendFlat({}, dash, { editType: 'arraydraw' }), editType: 'calc+arraydraw' }, fillcolor: { valType: 'color', dflt: 'rgba(0,0,0,0)', editType: 'arraydraw', - description: [ - 'Sets the color filling the shape\'s interior. Only applies to closed shapes.' - ].join(' ') + description: ["Sets the color filling the shape's interior. Only applies to closed shapes."].join(' ') }, fillrule: { valType: 'enumerated', @@ -349,7 +330,7 @@ module.exports = templatedArray('shape', { 'It is also used for legend item if `name` is not provided.' ].join(' ') }, - texttemplate: shapeTexttemplateAttrs({}, {keys: Object.keys(shapeLabelTexttemplateVars)}), + texttemplate: shapeTexttemplateAttrs({}, { keys: Object.keys(shapeLabelTexttemplateVars) }), font: fontAttrs({ editType: 'calc+arraydraw', colorEditType: 'arraydraw', @@ -358,10 +339,18 @@ module.exports = templatedArray('shape', { textposition: { valType: 'enumerated', values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right', - 'start', 'middle', 'end', + 'top left', + 'top center', + 'top right', + 'middle left', + 'middle center', + 'middle right', + 'bottom left', + 'bottom center', + 'bottom right', + 'start', + 'middle', + 'end' ], editType: 'arraydraw', description: [ @@ -371,7 +360,7 @@ module.exports = templatedArray('shape', { '*middle center*, *middle right*, *bottom left*, *bottom center*,', 'and *bottom right*.', 'Supported values for lines are *start*, *middle*, and *end*.', - 'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.', + 'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.' ].join(' ') }, textangle: { @@ -391,27 +380,27 @@ module.exports = templatedArray('shape', { dflt: 'auto', editType: 'calc+arraydraw', description: [ - 'Sets the label\'s horizontal position anchor', + "Sets the label's horizontal position anchor", 'This anchor binds the specified `textposition` to the *left*, *center*', 'or *right* of the label text.', 'For example, if `textposition` is set to *top right* and', '`xanchor` to *right* then the right-most portion of the', 'label text lines up with the right-most edge of the', - 'shape.', - ].join(' '), + 'shape.' + ].join(' ') }, yanchor: { valType: 'enumerated', values: ['top', 'middle', 'bottom'], editType: 'calc+arraydraw', description: [ - 'Sets the label\'s vertical position anchor', + "Sets the label's vertical position anchor", 'This anchor binds the specified `textposition` to the *top*, *middle*', 'or *bottom* of the label text.', 'For example, if `textposition` is set to *top right* and', '`yanchor` to *top* then the top-most portion of the', 'label text lines up with the top-most edge of the', - 'shape.', + 'shape.' ].join(' ') }, padding: { diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index e725b336678..c5584cffdb3 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -7,7 +7,6 @@ var handleArrayContainerDefaults = require('../../plots/array_container_defaults var attributes = require('./attributes'); var helpers = require('./helpers'); - module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) { handleArrayContainerDefaults(layoutIn, layoutOut, { name: 'shapes', @@ -19,9 +18,13 @@ function dfltLabelYanchor(isLine, labelTextPosition) { // If shape is a line, default y-anchor is 'bottom' (so that text is above line by default) // Otherwise, default y-anchor is equal to y-component of `textposition` // (so that text is positioned inside shape bounding box by default) - return isLine ? 'bottom' : - labelTextPosition.indexOf('top') !== -1 ? 'top' : - labelTextPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle'; + return isLine + ? 'bottom' + : labelTextPosition.indexOf('top') !== -1 + ? 'top' + : labelTextPosition.indexOf('bottom') !== -1 + ? 'bottom' + : 'middle'; } function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { @@ -32,10 +35,10 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { shapeOut._isShape = true; var visible = coerce('visible'); - if(!visible) return; + if (!visible) return; var showlegend = coerce('showlegend'); - if(showlegend) { + if (showlegend) { coerce('legend'); coerce('legendwidth'); coerce('legendgroup'); @@ -48,7 +51,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var dfltType = path ? 'path' : 'rect'; var shapeType = coerce('type', dfltType); var noPath = shapeType !== 'path'; - if(noPath) delete shapeOut.path; + if (noPath) delete shapeOut.path; coerce('editable'); coerce('layer'); @@ -56,7 +59,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { coerce('fillcolor'); coerce('fillrule'); var lineWidth = coerce('line.width'); - if(lineWidth) { + if (lineWidth) { coerce('line.color'); coerce('line.dash'); } @@ -66,26 +69,25 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { // positioning var axLetters = ['x', 'y']; - for(var i = 0; i < 2; i++) { + for (var i = 0; i < 2; i++) { var axLetter = axLetters[i]; var attrAnchor = axLetter + 'anchor'; var sizeMode = axLetter === 'x' ? xSizeMode : ySizeMode; - var gdMock = {_fullLayout: fullLayout}; + var gdMock = { _fullLayout: fullLayout }; var ax; var pos2r; var r2pos; // xref, yref - var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, - 'paper'); + var axRef = Axes.coerceRef(shapeIn, shapeOut, gdMock, axLetter, undefined, 'paper'); var axRefType = Axes.getRefType(axRef); - if(axRefType === 'range') { + if (axRefType === 'range') { ax = Axes.getFromId(gdMock, axRef); ax._shapeIndices.push(shapeOut._index); r2pos = helpers.rangeToShapePosition(ax); pos2r = helpers.shapePositionToRange(ax); - if(ax.type === 'category' || ax.type === 'multicategory') { + if (ax.type === 'category' || ax.type === 'multicategory') { coerce(axLetter + '0shift'); coerce(axLetter + '1shift'); } @@ -94,7 +96,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { } // Coerce x0, x1, y0, y1 - if(noPath) { + if (noPath) { var dflt0 = 0.25; var dflt1 = 0.75; @@ -109,7 +111,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { shapeIn[attr0] = pos2r(shapeIn[attr0], true); shapeIn[attr1] = pos2r(shapeIn[attr1], true); - if(sizeMode === 'pixel') { + if (sizeMode === 'pixel') { coerce(attr0, 0); coerce(attr1, 10); } else { @@ -125,7 +127,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { } // Coerce xanchor and yanchor - if(sizeMode === 'pixel') { + if (sizeMode === 'pixel') { // Hack for log axis described above var inAnchor = shapeIn[attrAnchor]; shapeIn[attrAnchor] = pos2r(shapeIn[attrAnchor], true); @@ -138,16 +140,20 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { } } - if(noPath) { + if (noPath) { Lib.noneOrAll(shapeIn, shapeOut, ['x0', 'x1', 'y0', 'y1']); } // Label options var isLine = shapeType === 'line'; var labelTextTemplate, labelText; - if(noPath) { labelTextTemplate = coerce('label.texttemplate'); } - if(!labelTextTemplate) { labelText = coerce('label.text'); } - if(labelText || labelTextTemplate) { + if (noPath) { + labelTextTemplate = coerce('label.texttemplate'); + } + if (!labelTextTemplate) { + labelText = coerce('label.text'); + } + if (labelText || labelTextTemplate) { coerce('label.textangle'); var labelTextPosition = coerce('label.textposition', isLine ? 'middle' : 'middle center'); coerce('label.xanchor'); diff --git a/src/components/shapes/display_labels.js b/src/components/shapes/display_labels.js index 53c9d96228b..1993956c803 100644 --- a/src/components/shapes/display_labels.js +++ b/src/components/shapes/display_labels.js @@ -13,36 +13,37 @@ var shapeLabelTexttemplateVars = require('./label_texttemplate'); var FROM_TL = require('../../constants/alignment').FROM_TL; - module.exports = function drawLabel(gd, index, options, shapeGroup) { // Remove existing label shapeGroup.selectAll('.shape-label').remove(); // If no label text or texttemplate, return - if(!(options.label.text || options.label.texttemplate)) return; + if (!(options.label.text || options.label.texttemplate)) return; // Text template overrides text var text; - if(options.label.texttemplate) { + if (options.label.texttemplate) { var templateValues = {}; - if(options.type !== 'path') { + if (options.type !== 'path') { var _xa = Axes.getFromId(gd, options.xref); var _ya = Axes.getFromId(gd, options.yref); - for(var key in shapeLabelTexttemplateVars) { + for (var key in shapeLabelTexttemplateVars) { var val = shapeLabelTexttemplateVars[key](options, _xa, _ya); - if(val !== undefined) templateValues[key] = val; + if (val !== undefined) templateValues[key] = val; } } - text = Lib.texttemplateStringForShapes(options.label.texttemplate, + text = Lib.texttemplateStringForShapes( + options.label.texttemplate, {}, gd._fullLayout._d3locale, - templateValues); + templateValues + ); } else { text = options.label.text; } var labelGroupAttrs = { - 'data-index': index, + 'data-index': index }; var font = options.label.font; @@ -50,17 +51,12 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { 'data-notex': 1 }; - var labelGroup = shapeGroup.append('g') - .attr(labelGroupAttrs) - .classed('shape-label', true); - var labelText = labelGroup.append('text') - .attr(labelTextAttrs) - .classed('shape-label-text', true) - .text(text); + var labelGroup = shapeGroup.append('g').attr(labelGroupAttrs).classed('shape-label', true); + var labelText = labelGroup.append('text').attr(labelTextAttrs).classed('shape-label-text', true).text(text); // Get x and y bounds of shape var shapex0, shapex1, shapey0, shapey1; - if(options.path) { + if (options.path) { // If shape is defined as a path, get the // min and max bounds across all polygons in path var d = getPathString(gd, options); @@ -69,10 +65,10 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { shapey0 = Infinity; shapex1 = -Infinity; shapey1 = -Infinity; - for(var i = 0; i < polygons.length; i++) { - for(var j = 0; j < polygons[i].length; j++) { + for (var i = 0; i < polygons.length; i++) { + for (var j = 0; j < polygons[i].length; j++) { var p = polygons[i][j]; - for(var k = 1; k < p.length; k += 2) { + for (var k = 1; k < p.length; k += 2) { var _x = p[k]; var _y = p[k + 1]; @@ -95,11 +91,11 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { var yShiftStart = options.y0shift; var yShiftEnd = options.y1shift; var yRefType = Axes.getRefType(options.yref); - var x2p = function(v, shift) { + var x2p = function (v, shift) { var dataToPixel = helpers.getDataToPixel(gd, xa, shift, false, xRefType); return dataToPixel(v); }; - var y2p = function(v, shift) { + var y2p = function (v, shift) { var dataToPixel = helpers.getDataToPixel(gd, ya, shift, true, yRefType); return dataToPixel(v); }; @@ -111,8 +107,8 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { // Handle `auto` angle var textangle = options.label.textangle; - if(textangle === 'auto') { - if(options.type === 'line') { + if (textangle === 'auto') { + if (options.type === 'line') { // Auto angle for line is same angle as line textangle = calcTextAngle(shapex0, shapey0, shapex1, shapey1); } else { @@ -122,7 +118,7 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { } // Do an initial render so we can get the text bounding box height - labelText.call(function(s) { + labelText.call(function (s) { s.call(Drawing.font, font).attr({}); svgTextUtils.convertToTspans(s, gd); return s; @@ -137,27 +133,29 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { var xanchor = textPos.xanchor; // Update (x,y) position, xanchor, and angle - labelText.attr({ - 'text-anchor': { - left: 'start', - center: 'middle', - right: 'end' - }[xanchor], - y: texty, - x: textx, - transform: 'rotate(' + textangle + ',' + textx + ',' + texty + ')' - }).call(svgTextUtils.positionText, textx, texty); + labelText + .attr({ + 'text-anchor': { + left: 'start', + center: 'middle', + right: 'end' + }[xanchor], + y: texty, + x: textx, + transform: 'rotate(' + textangle + ',' + textx + ',' + texty + ')' + }) + .call(svgTextUtils.positionText, textx, texty); }; function calcTextAngle(shapex0, shapey0, shapex1, shapey1) { var dy, dx; dx = Math.abs(shapex1 - shapex0); - if(shapex1 >= shapex0) { + if (shapex1 >= shapex0) { dy = shapey0 - shapey1; } else { dy = shapey1 - shapey0; } - return -180 / Math.PI * Math.atan2(dy, dx); + return (-180 / Math.PI) * Math.atan2(dy, dx); } function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actualTextAngle, textBB) { @@ -165,7 +163,7 @@ function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actu var textAngle = shapeOptions.label.textangle; var textPadding = shapeOptions.label.padding; var shapeType = shapeOptions.type; - var textAngleRad = Math.PI / 180 * actualTextAngle; + var textAngleRad = (Math.PI / 180) * actualTextAngle; var sinA = Math.sin(textAngleRad); var cosA = Math.cos(textAngleRad); var xanchor = shapeOptions.label.xanchor; @@ -174,39 +172,40 @@ function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actu var textx, texty, paddingX, paddingY; // Text position functions differently for lines vs. other shapes - if(shapeType === 'line') { + if (shapeType === 'line') { // Set base position for start vs. center vs. end of line (default is 'center') - if(textPosition === 'start') { + if (textPosition === 'start') { textx = shapex0; texty = shapey0; - } else if(textPosition === 'end') { + } else if (textPosition === 'end') { textx = shapex1; texty = shapey1; - } else { // Default: center + } else { + // Default: center textx = (shapex0 + shapex1) / 2; texty = (shapey0 + shapey1) / 2; } // Set xanchor if xanchor is 'auto' - if(xanchor === 'auto') { - if(textPosition === 'start') { - if(textAngle === 'auto') { - if(shapex1 > shapex0) xanchor = 'left'; - else if(shapex1 < shapex0) xanchor = 'right'; + if (xanchor === 'auto') { + if (textPosition === 'start') { + if (textAngle === 'auto') { + if (shapex1 > shapex0) xanchor = 'left'; + else if (shapex1 < shapex0) xanchor = 'right'; else xanchor = 'center'; } else { - if(shapex1 > shapex0) xanchor = 'right'; - else if(shapex1 < shapex0) xanchor = 'left'; + if (shapex1 > shapex0) xanchor = 'right'; + else if (shapex1 < shapex0) xanchor = 'left'; else xanchor = 'center'; } - } else if(textPosition === 'end') { - if(textAngle === 'auto') { - if(shapex1 > shapex0) xanchor = 'right'; - else if(shapex1 < shapex0) xanchor = 'left'; + } else if (textPosition === 'end') { + if (textAngle === 'auto') { + if (shapex1 > shapex0) xanchor = 'right'; + else if (shapex1 < shapex0) xanchor = 'left'; else xanchor = 'center'; } else { - if(shapex1 > shapex0) xanchor = 'left'; - else if(shapex1 < shapex0) xanchor = 'right'; + if (shapex1 > shapex0) xanchor = 'left'; + else if (shapex1 < shapex0) xanchor = 'right'; else xanchor = 'center'; } } else { @@ -219,7 +218,7 @@ function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actu // Otherwise, padding is just a simple x and y offset var paddingConstantsX = { left: 1, center: 0, right: -1 }; var paddingConstantsY = { bottom: -1, middle: 0, top: 1 }; - if(textAngle === 'auto') { + if (textAngle === 'auto') { // Set direction to apply padding (based on `yanchor` only) var paddingDirection = paddingConstantsY[yanchor]; paddingX = -textPadding * sinA * paddingDirection; @@ -238,30 +237,31 @@ function calcTextPosition(shapex0, shapey0, shapex1, shapey1, shapeOptions, actu // calc horizontal position // Horizontal needs a little extra padding to look balanced paddingX = textPadding + 3; - if(textPosition.indexOf('right') !== -1) { + if (textPosition.indexOf('right') !== -1) { textx = Math.max(shapex0, shapex1) - paddingX; - if(xanchor === 'auto') xanchor = 'right'; - } else if(textPosition.indexOf('left') !== -1) { + if (xanchor === 'auto') xanchor = 'right'; + } else if (textPosition.indexOf('left') !== -1) { textx = Math.min(shapex0, shapex1) + paddingX; - if(xanchor === 'auto') xanchor = 'left'; - } else { // Default: center + if (xanchor === 'auto') xanchor = 'left'; + } else { + // Default: center textx = (shapex0 + shapex1) / 2; - if(xanchor === 'auto') xanchor = 'center'; + if (xanchor === 'auto') xanchor = 'center'; } // calc vertical position - if(textPosition.indexOf('top') !== -1) { + if (textPosition.indexOf('top') !== -1) { texty = Math.min(shapey0, shapey1); - } else if(textPosition.indexOf('bottom') !== -1) { + } else if (textPosition.indexOf('bottom') !== -1) { texty = Math.max(shapey0, shapey1); } else { texty = (shapey0 + shapey1) / 2; } // Apply padding paddingY = textPadding; - if(yanchor === 'bottom') { + if (yanchor === 'bottom') { texty = texty - paddingY; - } else if(yanchor === 'top') { + } else if (yanchor === 'top') { texty = texty + paddingY; } } diff --git a/src/components/shapes/draw_newshape/attributes.js b/src/components/shapes/draw_newshape/attributes.js index 633870414b6..86f2d25b0c3 100644 --- a/src/components/shapes/draw_newshape/attributes.js +++ b/src/components/shapes/draw_newshape/attributes.js @@ -8,232 +8,238 @@ var extendFlat = require('../../../lib/extend').extendFlat; var shapeTexttemplateAttrs = require('../../../plots/template_attributes').shapeTexttemplateAttrs; var shapeLabelTexttemplateVars = require('../label_texttemplate'); -module.exports = overrideAll({ - newshape: { - visible: extendFlat({}, basePlotAttributes.visible, { - description: [ - 'Determines whether or not new shape is visible.', - 'If *legendonly*, the shape is not drawn,', - 'but can appear as a legend item', - '(provided that the legend itself is visible).' - ].join(' ') - }), - - showlegend: { - valType: 'boolean', - dflt: false, - description: [ - 'Determines whether or not new', - 'shape is shown in the legend.' - ].join(' ') - }, - - legend: extendFlat({}, basePlotAttributes.legend, { - description: [ - 'Sets the reference to a legend to show new shape in.', - 'References to these legends are *legend*, *legend2*, *legend3*, etc.', - 'Settings for these legends are set in the layout, under', - '`layout.legend`, `layout.legend2`, etc.' - ].join(' ') - }), +module.exports = overrideAll( + { + newshape: { + visible: extendFlat({}, basePlotAttributes.visible, { + description: [ + 'Determines whether or not new shape is visible.', + 'If *legendonly*, the shape is not drawn,', + 'but can appear as a legend item', + '(provided that the legend itself is visible).' + ].join(' ') + }), - legendgroup: extendFlat({}, basePlotAttributes.legendgroup, { - description: [ - 'Sets the legend group for new shape.', - 'Traces and shapes part of the same legend group hide/show at the same time', - 'when toggling legend items.' - ].join(' ') - }), + showlegend: { + valType: 'boolean', + dflt: false, + description: ['Determines whether or not new', 'shape is shown in the legend.'].join(' ') + }, - legendgrouptitle: { - text: extendFlat({}, basePlotAttributes.legendgrouptitle.text, { - }), - font: fontAttrs({ + legend: extendFlat({}, basePlotAttributes.legend, { description: [ - 'Sets this legend group\'s title font.' - ].join(' '), - }) - }, + 'Sets the reference to a legend to show new shape in.', + 'References to these legends are *legend*, *legend2*, *legend3*, etc.', + 'Settings for these legends are set in the layout, under', + '`layout.legend`, `layout.legend2`, etc.' + ].join(' ') + }), - legendrank: extendFlat({}, basePlotAttributes.legendrank, { - description: [ - 'Sets the legend rank for new shape.', - 'Items and groups with smaller ranks are presented on top/left side while', - 'with *reversed* `legend.traceorder` they are on bottom/right side.', - 'The default legendrank is 1000,', - 'so that you can use ranks less than 1000 to place certain items before all unranked items,', - 'and ranks greater than 1000 to go after all unranked items.' - ].join(' ') - }), + legendgroup: extendFlat({}, basePlotAttributes.legendgroup, { + description: [ + 'Sets the legend group for new shape.', + 'Traces and shapes part of the same legend group hide/show at the same time', + 'when toggling legend items.' + ].join(' ') + }), - legendwidth: extendFlat({}, basePlotAttributes.legendwidth, { - description: 'Sets the width (in px or fraction) of the legend for new shape.', - }), + legendgrouptitle: { + text: extendFlat({}, basePlotAttributes.legendgrouptitle.text, {}), + font: fontAttrs({ + description: ["Sets this legend group's title font."].join(' ') + }) + }, - line: { - color: { - valType: 'color', + legendrank: extendFlat({}, basePlotAttributes.legendrank, { description: [ - 'Sets the line color.', - 'By default uses either dark grey or white', - 'to increase contrast with background color.' + 'Sets the legend rank for new shape.', + 'Items and groups with smaller ranks are presented on top/left side while', + 'with *reversed* `legend.traceorder` they are on bottom/right side.', + 'The default legendrank is 1000,', + 'so that you can use ranks less than 1000 to place certain items before all unranked items,', + 'and ranks greater than 1000 to go after all unranked items.' ].join(' ') - }, - width: { - valType: 'number', - min: 0, - dflt: 4, - description: 'Sets the line width (in px).' - }, - dash: extendFlat({}, dash, { - dflt: 'solid', - }) - }, - fillcolor: { - valType: 'color', - dflt: 'rgba(0,0,0,0)', - description: [ - 'Sets the color filling new shapes\' interior.', - 'Please note that if using a fillcolor with alpha greater than half,', - 'drag inside the active shape starts moving the shape underneath,', - 'otherwise a new shape could be started over.' - ].join(' ') - }, - fillrule: { - valType: 'enumerated', - values: ['evenodd', 'nonzero'], - dflt: 'evenodd', - description: [ - 'Determines the path\'s interior.', - 'For more info please visit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule' - ].join(' ') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - description: 'Sets the opacity of new shapes.' - }, - layer: { - valType: 'enumerated', - values: ['below', 'above', 'between'], - dflt: 'above', - description: [ - 'Specifies whether new shapes are drawn below gridlines (*below*),', - 'between gridlines and traces (*between*) or above traces (*above*).' - ].join(' ') - }, - drawdirection: { - valType: 'enumerated', - values: ['ortho', 'horizontal', 'vertical', 'diagonal'], - dflt: 'diagonal', - description: [ - 'When `dragmode` is set to *drawrect*, *drawline* or *drawcircle*', - 'this limits the drag to be horizontal, vertical or diagonal.', - 'Using *diagonal* there is no limit e.g. in drawing lines in any direction.', - '*ortho* limits the draw to be either horizontal or vertical.', - '*horizontal* allows horizontal extend.', - '*vertical* allows vertical extend.' - ].join(' ') - }, + }), - name: extendFlat({}, basePlotAttributes.name, { - description: [ - 'Sets new shape name.', - 'The name appears as the legend item.' - ].join(' ') - }), + legendwidth: extendFlat({}, basePlotAttributes.legendwidth, { + description: 'Sets the width (in px or fraction) of the legend for new shape.' + }), - label: { - text: { - valType: 'string', - dflt: '', + line: { + color: { + valType: 'color', + description: [ + 'Sets the line color.', + 'By default uses either dark grey or white', + 'to increase contrast with background color.' + ].join(' ') + }, + width: { + valType: 'number', + min: 0, + dflt: 4, + description: 'Sets the line width (in px).' + }, + dash: extendFlat({}, dash, { + dflt: 'solid' + }) + }, + fillcolor: { + valType: 'color', + dflt: 'rgba(0,0,0,0)', description: [ - 'Sets the text to display with the new shape.', - 'It is also used for legend item if `name` is not provided.' + "Sets the color filling new shapes' interior.", + 'Please note that if using a fillcolor with alpha greater than half,', + 'drag inside the active shape starts moving the shape underneath,', + 'otherwise a new shape could be started over.' ].join(' ') }, - texttemplate: shapeTexttemplateAttrs({newshape: true}, {keys: Object.keys(shapeLabelTexttemplateVars)}), - font: fontAttrs({ - description: 'Sets the new shape label text font.' - }), - textposition: { + fillrule: { valType: 'enumerated', - values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right', - 'start', 'middle', 'end', - ], + values: ['evenodd', 'nonzero'], + dflt: 'evenodd', description: [ - 'Sets the position of the label text relative to the new shape.', - 'Supported values for rectangles, circles and paths are', - '*top left*, *top center*, *top right*, *middle left*,', - '*middle center*, *middle right*, *bottom left*, *bottom center*,', - 'and *bottom right*.', - 'Supported values for lines are *start*, *middle*, and *end*.', - 'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.', + "Determines the path's interior.", + 'For more info please visit https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule' ].join(' ') }, - textangle: { - valType: 'angle', - dflt: 'auto', - description: [ - 'Sets the angle at which the label text is drawn', - 'with respect to the horizontal. For lines, angle *auto*', - 'is the same angle as the line. For all other shapes,', - 'angle *auto* is horizontal.' - ].join(' ') + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + description: 'Sets the opacity of new shapes.' }, - xanchor: { + layer: { valType: 'enumerated', - values: ['auto', 'left', 'center', 'right'], - dflt: 'auto', + values: ['below', 'above', 'between'], + dflt: 'above', description: [ - 'Sets the label\'s horizontal position anchor', - 'This anchor binds the specified `textposition` to the *left*, *center*', - 'or *right* of the label text.', - 'For example, if `textposition` is set to *top right* and', - '`xanchor` to *right* then the right-most portion of the', - 'label text lines up with the right-most edge of the', - 'new shape.', - ].join(' '), + 'Specifies whether new shapes are drawn below gridlines (*below*),', + 'between gridlines and traces (*between*) or above traces (*above*).' + ].join(' ') }, - yanchor: { + drawdirection: { valType: 'enumerated', - values: ['top', 'middle', 'bottom'], + values: ['ortho', 'horizontal', 'vertical', 'diagonal'], + dflt: 'diagonal', description: [ - 'Sets the label\'s vertical position anchor', - 'This anchor binds the specified `textposition` to the *top*, *middle*', - 'or *bottom* of the label text.', - 'For example, if `textposition` is set to *top right* and', - '`yanchor` to *top* then the top-most portion of the', - 'label text lines up with the top-most edge of the', - 'new shape.', + 'When `dragmode` is set to *drawrect*, *drawline* or *drawcircle*', + 'this limits the drag to be horizontal, vertical or diagonal.', + 'Using *diagonal* there is no limit e.g. in drawing lines in any direction.', + '*ortho* limits the draw to be either horizontal or vertical.', + '*horizontal* allows horizontal extend.', + '*vertical* allows vertical extend.' ].join(' ') }, - padding: { + + name: extendFlat({}, basePlotAttributes.name, { + description: ['Sets new shape name.', 'The name appears as the legend item.'].join(' ') + }), + + label: { + text: { + valType: 'string', + dflt: '', + description: [ + 'Sets the text to display with the new shape.', + 'It is also used for legend item if `name` is not provided.' + ].join(' ') + }, + texttemplate: shapeTexttemplateAttrs( + { newshape: true }, + { keys: Object.keys(shapeLabelTexttemplateVars) } + ), + font: fontAttrs({ + description: 'Sets the new shape label text font.' + }), + textposition: { + valType: 'enumerated', + values: [ + 'top left', + 'top center', + 'top right', + 'middle left', + 'middle center', + 'middle right', + 'bottom left', + 'bottom center', + 'bottom right', + 'start', + 'middle', + 'end' + ], + description: [ + 'Sets the position of the label text relative to the new shape.', + 'Supported values for rectangles, circles and paths are', + '*top left*, *top center*, *top right*, *middle left*,', + '*middle center*, *middle right*, *bottom left*, *bottom center*,', + 'and *bottom right*.', + 'Supported values for lines are *start*, *middle*, and *end*.', + 'Default: *middle center* for rectangles, circles, and paths; *middle* for lines.' + ].join(' ') + }, + textangle: { + valType: 'angle', + dflt: 'auto', + description: [ + 'Sets the angle at which the label text is drawn', + 'with respect to the horizontal. For lines, angle *auto*', + 'is the same angle as the line. For all other shapes,', + 'angle *auto* is horizontal.' + ].join(' ') + }, + xanchor: { + valType: 'enumerated', + values: ['auto', 'left', 'center', 'right'], + dflt: 'auto', + description: [ + "Sets the label's horizontal position anchor", + 'This anchor binds the specified `textposition` to the *left*, *center*', + 'or *right* of the label text.', + 'For example, if `textposition` is set to *top right* and', + '`xanchor` to *right* then the right-most portion of the', + 'label text lines up with the right-most edge of the', + 'new shape.' + ].join(' ') + }, + yanchor: { + valType: 'enumerated', + values: ['top', 'middle', 'bottom'], + description: [ + "Sets the label's vertical position anchor", + 'This anchor binds the specified `textposition` to the *top*, *middle*', + 'or *bottom* of the label text.', + 'For example, if `textposition` is set to *top right* and', + '`yanchor` to *top* then the top-most portion of the', + 'label text lines up with the top-most edge of the', + 'new shape.' + ].join(' ') + }, + padding: { + valType: 'number', + dflt: 3, + min: 0, + description: 'Sets padding (in px) between edge of label and edge of new shape.' + } + } + }, + + activeshape: { + fillcolor: { + valType: 'color', + dflt: 'rgb(255,0,255)', + description: "Sets the color filling the active shape' interior." + }, + opacity: { valType: 'number', - dflt: 3, min: 0, - description: 'Sets padding (in px) between edge of label and edge of new shape.' + max: 1, + dflt: 0.5, + description: 'Sets the opacity of the active shape.' } } }, - - activeshape: { - fillcolor: { - valType: 'color', - dflt: 'rgb(255,0,255)', - description: 'Sets the color filling the active shape\' interior.' - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.5, - description: 'Sets the opacity of the active shape.' - } - } -}, 'none', 'from-root'); + 'none', + 'from-root' +); diff --git a/src/components/shapes/draw_newshape/defaults.js b/src/components/shapes/draw_newshape/defaults.js index b13d070efb5..6178c6da71a 100644 --- a/src/components/shapes/draw_newshape/defaults.js +++ b/src/components/shapes/draw_newshape/defaults.js @@ -3,14 +3,17 @@ var Color = require('../../color'); var Lib = require('../../../lib'); - function dfltLabelYanchor(isLine, labelTextPosition) { // If shape is a line, default y-anchor is 'bottom' (so that text is above line by default) // Otherwise, default y-anchor is equal to y-component of `textposition` // (so that text is positioned inside shape bounding box by default) - return isLine ? 'bottom' : - labelTextPosition.indexOf('top') !== -1 ? 'top' : - labelTextPosition.indexOf('bottom') !== -1 ? 'bottom' : 'middle'; + return isLine + ? 'bottom' + : labelTextPosition.indexOf('top') !== -1 + ? 'top' + : labelTextPosition.indexOf('bottom') !== -1 + ? 'bottom' + : 'middle'; } module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce) { @@ -30,7 +33,7 @@ module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce coerce('newshape.fillrule'); coerce('newshape.opacity'); var newshapeLineWidth = coerce('newshape.line.width'); - if(newshapeLineWidth) { + if (newshapeLineWidth) { var bgcolor = (layoutIn || {}).plot_bgcolor || '#FFF'; coerce('newshape.line.color', Color.contrast(bgcolor)); coerce('newshape.line.dash'); @@ -39,7 +42,7 @@ module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce var isLine = layoutIn.dragmode === 'drawline'; var labelText = coerce('newshape.label.text'); var labelTextTemplate = coerce('newshape.label.texttemplate'); - if(labelText || labelTextTemplate) { + if (labelText || labelTextTemplate) { coerce('newshape.label.textangle'); var labelTextPosition = coerce('newshape.label.textposition', isLine ? 'middle' : 'middle center'); coerce('newshape.label.xanchor'); diff --git a/src/lib/index.js b/src/lib/index.js index 3368f86477a..7e460293ef6 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -10,43 +10,39 @@ var MAX_SAFE = numConstants.FP_SAFE; var MIN_SAFE = -MAX_SAFE; var BADNUM = numConstants.BADNUM; -var lib = module.exports = {}; +var lib = (module.exports = {}); lib.adjustFormat = function adjustFormat(formatStr) { - if( - !formatStr || - /^\d[.]\df/.test(formatStr) || - /[.]\d%/.test(formatStr) - ) return formatStr; + if (!formatStr || /^\d[.]\df/.test(formatStr) || /[.]\d%/.test(formatStr)) return formatStr; - if(formatStr === '0.f') return '~f'; - if(/^\d%/.test(formatStr)) return '~%'; - if(/^\ds/.test(formatStr)) return '~s'; + if (formatStr === '0.f') return '~f'; + if (/^\d%/.test(formatStr)) return '~%'; + if (/^\ds/.test(formatStr)) return '~s'; // try adding tilde to the start of format in order to trim - if(!(/^[~,.0$]/.test(formatStr)) && /[&fps]/.test(formatStr)) return '~' + formatStr; + if (!/^[~,.0$]/.test(formatStr) && /[&fps]/.test(formatStr)) return '~' + formatStr; return formatStr; }; var seenBadFormats = {}; -lib.warnBadFormat = function(f) { +lib.warnBadFormat = function (f) { var key = String(f); - if(!seenBadFormats[key]) { + if (!seenBadFormats[key]) { seenBadFormats[key] = 1; lib.warn('encountered bad format: "' + key + '"'); } }; -lib.noFormat = function(value) { +lib.noFormat = function (value) { return String(value); }; -lib.numberFormat = function(formatStr) { +lib.numberFormat = function (formatStr) { var fn; try { fn = d3Format(lib.adjustFormat(formatStr)); - } catch(e) { + } catch (e) { lib.warnBadFormat(formatStr); return lib.noFormat; } @@ -213,9 +209,9 @@ lib.increment = require('./increment'); lib.cleanNumber = require('./clean_number'); lib.ensureNumber = function ensureNumber(v) { - if(!isNumeric(v)) return BADNUM; + if (!isNumeric(v)) return BADNUM; v = Number(v); - return (v > MAX_SAFE || v < MIN_SAFE) ? BADNUM : v; + return v > MAX_SAFE || v < MIN_SAFE ? BADNUM : v; }; /** @@ -226,9 +222,9 @@ lib.ensureNumber = function ensureNumber(v) { * * @return {bool}: v is a valid array index */ -lib.isIndex = function(v, len) { - if(len !== undefined && v >= len) return false; - return isNumeric(v) && (v >= 0) && (v % 1 === 0); +lib.isIndex = function (v, len) { + if (len !== undefined && v >= len) return false; + return isNumeric(v) && v >= 0 && v % 1 === 0; }; lib.noop = require('./noop'); @@ -241,9 +237,9 @@ lib.identity = require('./identity'); * @param {number} cnt * @return {array} */ -lib.repeat = function(v, cnt) { +lib.repeat = function (v, cnt) { var out = new Array(cnt); - for(var i = 0; i < cnt; i++) { + for (var i = 0; i < cnt; i++) { out[i] = v; } return out; @@ -254,10 +250,10 @@ lib.repeat = function(v, cnt) { * specify attr with a ? in place of x/y * you can also swap other things than x/y by providing part1 and part2 */ -lib.swapAttrs = function(cont, attrList, part1, part2) { - if(!part1) part1 = 'x'; - if(!part2) part2 = 'y'; - for(var i = 0; i < attrList.length; i++) { +lib.swapAttrs = function (cont, attrList, part1, part2) { + if (!part1) part1 = 'x'; + if (!part2) part2 = 'y'; + for (var i = 0; i < attrList.length; i++) { var attr = attrList[i]; var xp = lib.nestedProperty(cont, attr.replace('?', part1)); var yp = lib.nestedProperty(cont, attr.replace('?', part2)); @@ -277,13 +273,13 @@ lib.raiseToTop = function raiseToTop(elem) { /** * cancel a possibly pending transition; returned selection may be used by caller */ -lib.cancelTransition = function(selection) { +lib.cancelTransition = function (selection) { return selection.transition().duration(0); }; // constrain - restrict a number v to be between v0 and v1 -lib.constrain = function(v, v0, v1) { - if(v0 > v1) return Math.max(v1, Math.min(v0, v)); +lib.constrain = function (v, v0, v1) { + if (v0 > v1) return Math.max(v1, Math.min(v0, v)); return Math.max(v0, Math.min(v1, v)); }; @@ -292,12 +288,9 @@ lib.constrain = function(v, v0, v1) { * ie {left,right,top,bottom,width,height}, overlap? * takes optional padding pixels */ -lib.bBoxIntersect = function(a, b, pad) { +lib.bBoxIntersect = function (a, b, pad) { pad = pad || 0; - return (a.left <= b.right + pad && - b.left <= a.right + pad && - a.top <= b.bottom + pad && - b.top <= a.bottom + pad); + return a.left <= b.right + pad && b.left <= a.right + pad && a.top <= b.bottom + pad && b.top <= a.bottom + pad; }; /* @@ -309,10 +302,10 @@ lib.bBoxIntersect = function(a, b, pad) { * func: the function to apply * x1, x2: optional extra args */ -lib.simpleMap = function(array, func, x1, x2, opts) { +lib.simpleMap = function (array, func, x1, x2, opts) { var len = array.length; var out = new Array(len); - for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2, opts); + for (var i = 0; i < len; i++) out[i] = func(array[i], x1, x2, opts); return out; }; @@ -327,35 +320,34 @@ lib.simpleMap = function(array, func, x1, x2, opts) { * base of string representation, default 16. Should be a power of 2. */ lib.randstr = function randstr(existing, bits, base, _recursion) { - if(!base) base = 16; - if(bits === undefined) bits = 24; - if(bits <= 0) return '0'; + if (!base) base = 16; + if (bits === undefined) bits = 24; + if (bits <= 0) return '0'; var digits = Math.log(Math.pow(2, bits)) / Math.log(base); var res = ''; var i, b, x; - for(i = 2; digits === Infinity; i *= 2) { - digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i; + for (i = 2; digits === Infinity; i *= 2) { + digits = (Math.log(Math.pow(2, bits / i)) / Math.log(base)) * i; } var rem = digits - Math.floor(digits); - for(i = 0; i < Math.floor(digits); i++) { + for (i = 0; i < Math.floor(digits); i++) { x = Math.floor(Math.random() * base).toString(base); res = x + res; } - if(rem) { + if (rem) { b = Math.pow(base, rem); x = Math.floor(Math.random() * b).toString(base); res = x + res; } var parsed = parseInt(res, base); - if((existing && existing[res]) || - (parsed !== Infinity && parsed >= Math.pow(2, bits))) { - if(_recursion > 10) { + if ((existing && existing[res]) || (parsed !== Infinity && parsed >= Math.pow(2, bits))) { + if (_recursion > 10) { lib.warn('randstr failed uniqueness'); return res; } @@ -363,7 +355,7 @@ lib.randstr = function randstr(existing, bits, base, _recursion) { } else return res; }; -lib.OptionControl = function(opt, optname) { +lib.OptionControl = function (opt, optname) { /* * An environment to contain all option setters and * getters that collectively modify opts. @@ -373,13 +365,13 @@ lib.OptionControl = function(opt, optname) { * * See FitOpts for example of usage */ - if(!opt) opt = {}; - if(!optname) optname = 'opt'; + if (!opt) opt = {}; + if (!optname) optname = 'opt'; var self = {}; self.optionList = []; - self._newoption = function(optObj) { + self._newoption = function (optObj) { optObj[optname] = opt; self[optObj.name] = optObj; self.optionList.push(optObj); @@ -394,9 +386,9 @@ lib.OptionControl = function(opt, optname) { * a hann window with given full width at half max * bounce the ends in, so the output has the same length as the input */ -lib.smooth = function(arrayIn, FWHM) { +lib.smooth = function (arrayIn, FWHM) { FWHM = Math.round(FWHM) || 0; // only makes sense for integers - if(FWHM < 2) return arrayIn; + if (FWHM < 2) return arrayIn; var alen = arrayIn.length; var alen2 = 2 * alen; @@ -409,23 +401,23 @@ lib.smooth = function(arrayIn, FWHM) { var v; // first make the window array - for(i = 0; i < wlen; i++) { - w[i] = (1 - Math.cos(Math.PI * (i + 1) / FWHM)) / (2 * FWHM); + for (i = 0; i < wlen; i++) { + w[i] = (1 - Math.cos((Math.PI * (i + 1)) / FWHM)) / (2 * FWHM); } // now do the convolution - for(i = 0; i < alen; i++) { + for (i = 0; i < alen; i++) { v = 0; - for(j = 0; j < wlen; j++) { + for (j = 0; j < wlen; j++) { k = i + j + 1 - FWHM; // multibounce - if(k < -alen) k -= alen2 * Math.round(k / alen2); - else if(k >= alen2) k -= alen2 * Math.floor(k / alen2); + if (k < -alen) k -= alen2 * Math.round(k / alen2); + else if (k >= alen2) k -= alen2 * Math.floor(k / alen2); // single bounce - if(k < 0) k = - 1 - k; - else if(k >= alen) k = alen2 - 1 - k; + if (k < 0) k = -1 - k; + else if (k >= alen) k = alen2 - 1 - k; v += arrayIn[k] * w[j]; } @@ -446,18 +438,18 @@ lib.smooth = function(arrayIn, FWHM) { * this doesn't happen yet because we want to make sure * that it gets reported */ -lib.syncOrAsync = function(sequence, arg, finalStep) { +lib.syncOrAsync = function (sequence, arg, finalStep) { var ret, fni; function continueAsync() { return lib.syncOrAsync(sequence, arg, finalStep); } - while(sequence.length) { + while (sequence.length) { fni = sequence.splice(0, 1)[0]; ret = fni(arg); - if(ret && ret.then) { + if (ret && ret.then) { return ret.then(continueAsync); } } @@ -465,37 +457,36 @@ lib.syncOrAsync = function(sequence, arg, finalStep) { return finalStep && finalStep(arg); }; - /** * Helper to strip trailing slash, from * http://stackoverflow.com/questions/6680825/return-string-without-trailing-slash */ -lib.stripTrailingSlash = function(str) { - if(str.substr(-1) === '/') return str.substr(0, str.length - 1); +lib.stripTrailingSlash = function (str) { + if (str.substr(-1) === '/') return str.substr(0, str.length - 1); return str; }; -lib.noneOrAll = function(containerIn, containerOut, attrList) { +lib.noneOrAll = function (containerIn, containerOut, attrList) { /** * some attributes come together, so if you have one of them * in the input, you should copy the default values of the others * to the input as well. */ - if(!containerIn) return; + if (!containerIn) return; var hasAny = false; var hasAll = true; var i; var val; - for(i = 0; i < attrList.length; i++) { + for (i = 0; i < attrList.length; i++) { val = containerIn[attrList[i]]; - if(val !== undefined && val !== null) hasAny = true; + if (val !== undefined && val !== null) hasAny = true; else hasAll = false; } - if(hasAny && !hasAll) { - for(i = 0; i < attrList.length; i++) { + if (hasAny && !hasAll) { + for (i = 0; i < attrList.length; i++) { containerIn[attrList[i]] = containerOut[attrList[i]]; } } @@ -510,11 +501,11 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) { * @param {object} cd : calcdata trace * @param {string} cdAttr : calcdata key */ -lib.mergeArray = function(traceAttr, cd, cdAttr, fn) { +lib.mergeArray = function (traceAttr, cd, cdAttr, fn) { var hasFn = typeof fn === 'function'; - if(lib.isArrayOrTypedArray(traceAttr)) { + if (lib.isArrayOrTypedArray(traceAttr)) { var imax = Math.min(traceAttr.length, cd.length); - for(var i = 0; i < imax; i++) { + for (var i = 0; i < imax; i++) { var v = traceAttr[i]; cd[i][cdAttr] = hasFn ? fn(v) : v; } @@ -522,8 +513,8 @@ lib.mergeArray = function(traceAttr, cd, cdAttr, fn) { }; // cast numbers to positive numbers, returns 0 if not greater than 0 -lib.mergeArrayCastPositive = function(traceAttr, cd, cdAttr) { - return lib.mergeArray(traceAttr, cd, cdAttr, function(v) { +lib.mergeArrayCastPositive = function (traceAttr, cd, cdAttr) { + return lib.mergeArray(traceAttr, cd, cdAttr, function (v) { var w = +v; return !isFinite(w) ? 0 : w > 0 ? w : 0; }); @@ -539,11 +530,11 @@ lib.mergeArrayCastPositive = function(traceAttr, cd, cdAttr) { * @param {string} cdAttr : calcdata key * @param {function} [fn] : optional function to apply to each array item */ -lib.fillArray = function(traceAttr, cd, cdAttr, fn) { +lib.fillArray = function (traceAttr, cd, cdAttr, fn) { fn = fn || lib.identity; - if(lib.isArrayOrTypedArray(traceAttr)) { - for(var i = 0; i < cd.length; i++) { + if (lib.isArrayOrTypedArray(traceAttr)) { + for (var i = 0; i < cd.length; i++) { cd[i][cdAttr] = fn(traceAttr[i]); } } @@ -558,13 +549,13 @@ lib.fillArray = function(traceAttr, cd, cdAttr, fn) { * * @return {any} */ -lib.castOption = function(trace, ptNumber, astr, fn) { +lib.castOption = function (trace, ptNumber, astr, fn) { fn = fn || lib.identity; var val = lib.nestedProperty(trace, astr).get(); - if(lib.isArrayOrTypedArray(val)) { - if(Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) { + if (lib.isArrayOrTypedArray(val)) { + if (Array.isArray(ptNumber) && lib.isArrayOrTypedArray(val[ptNumber[0]])) { return fn(val[ptNumber[0]][ptNumber[1]]); } else { return fn(val[ptNumber]); @@ -583,22 +574,22 @@ lib.castOption = function(trace, ptNumber, astr, fn) { * @param {string} traceKey : aka trace attribute string * @return {any} */ -lib.extractOption = function(calcPt, trace, calcKey, traceKey) { - if(calcKey in calcPt) return calcPt[calcKey]; +lib.extractOption = function (calcPt, trace, calcKey, traceKey) { + if (calcKey in calcPt) return calcPt[calcKey]; // fallback to trace value, // must check if value isn't itself an array // which means the trace attribute has a corresponding // calcdata key, but its value is falsy var traceVal = lib.nestedProperty(trace, traceKey).get(); - if(!Array.isArray(traceVal)) return traceVal; + if (!Array.isArray(traceVal)) return traceVal; }; function makePtIndex2PtNumber(indexToPoints) { var ptIndex2ptNumber = {}; - for(var k in indexToPoints) { + for (var k in indexToPoints) { var pts = indexToPoints[k]; - for(var j = 0; j < pts.length; j++) { + for (var j = 0; j < pts.length; j++) { ptIndex2ptNumber[pts[j]] = +k; } } @@ -618,13 +609,13 @@ function makePtIndex2PtNumber(indexToPoints) { * optional map object for trace types that do not have 1-to-1 point number to * calcdata item index correspondence (e.g. histogram) */ -lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) { +lib.tagSelected = function (calcTrace, trace, ptNumber2cdIndex) { var selectedpoints = trace.selectedpoints; var indexToPoints = trace._indexToPoints; var ptIndex2ptNumber; // make pt index-to-number map object, which takes care of transformed traces - if(indexToPoints) { + if (indexToPoints) { ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); } @@ -632,35 +623,36 @@ lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) { return v !== undefined && v < calcTrace.length; } - for(var i = 0; i < selectedpoints.length; i++) { + for (var i = 0; i < selectedpoints.length; i++) { var ptIndex = selectedpoints[i]; - if(lib.isIndex(ptIndex) || - (lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1])) + if ( + lib.isIndex(ptIndex) || + (lib.isArrayOrTypedArray(ptIndex) && lib.isIndex(ptIndex[0]) && lib.isIndex(ptIndex[1])) ) { var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex; var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber; - if(isCdIndexValid(cdIndex)) { + if (isCdIndexValid(cdIndex)) { calcTrace[cdIndex].selected = 1; } } } }; -lib.selIndices2selPoints = function(trace) { +lib.selIndices2selPoints = function (trace) { var selectedpoints = trace.selectedpoints; var indexToPoints = trace._indexToPoints; - if(indexToPoints) { + if (indexToPoints) { var ptIndex2ptNumber = makePtIndex2PtNumber(indexToPoints); var out = []; - for(var i = 0; i < selectedpoints.length; i++) { + for (var i = 0; i < selectedpoints.length; i++) { var ptIndex = selectedpoints[i]; - if(lib.isIndex(ptIndex)) { + if (lib.isIndex(ptIndex)) { var ptNumber = ptIndex2ptNumber[ptIndex]; - if(lib.isIndex(ptNumber)) { + if (lib.isIndex(ptNumber)) { out.push(ptNumber); } } @@ -682,13 +674,13 @@ lib.selIndices2selPoints = function(trace) { * * @return {array or false} : the target array (NOT a copy!!) or false if invalid */ -lib.getTargetArray = function(trace, transformOpts) { +lib.getTargetArray = function (trace, transformOpts) { var target = transformOpts.target; - if(typeof target === 'string' && target) { + if (typeof target === 'string' && target) { var array = lib.nestedProperty(trace, target).get(); return lib.isArrayOrTypedArray(array) ? array : false; - } else if(lib.isArrayOrTypedArray(target)) { + } else if (lib.isArrayOrTypedArray(target)) { return target; } @@ -703,39 +695,39 @@ lib.getTargetArray = function(trace, transformOpts) { */ function minExtend(obj1, obj2, opt) { var objOut = {}; - if(typeof obj2 !== 'object') obj2 = {}; + if (typeof obj2 !== 'object') obj2 = {}; var arrayLen = opt === 'pieLike' ? -1 : 3; var keys = Object.keys(obj1); var i, k, v; - for(i = 0; i < keys.length; i++) { + for (i = 0; i < keys.length; i++) { k = keys[i]; v = obj1[k]; - if(k.charAt(0) === '_' || typeof v === 'function') continue; - else if(k === 'module') objOut[k] = v; - else if(Array.isArray(v)) { - if(k === 'colorscale' || arrayLen === -1) { + if (k.charAt(0) === '_' || typeof v === 'function') continue; + else if (k === 'module') objOut[k] = v; + else if (Array.isArray(v)) { + if (k === 'colorscale' || arrayLen === -1) { objOut[k] = v.slice(); } else { objOut[k] = v.slice(0, arrayLen); } - } else if(lib.isTypedArray(v)) { - if(arrayLen === -1) { + } else if (lib.isTypedArray(v)) { + if (arrayLen === -1) { objOut[k] = v.subarray(); } else { objOut[k] = v.subarray(0, arrayLen); } - } else if(v && (typeof v === 'object')) objOut[k] = minExtend(obj1[k], obj2[k], opt); + } else if (v && typeof v === 'object') objOut[k] = minExtend(obj1[k], obj2[k], opt); else objOut[k] = v; } keys = Object.keys(obj2); - for(i = 0; i < keys.length; i++) { + for (i = 0; i < keys.length; i++) { k = keys[i]; v = obj2[k]; - if(typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') { + if (typeof v !== 'object' || !(k in objOut) || typeof objOut[k] !== 'object') { objOut[k] = v; } } @@ -744,24 +736,24 @@ function minExtend(obj1, obj2, opt) { } lib.minExtend = minExtend; -lib.titleCase = function(s) { +lib.titleCase = function (s) { return s.charAt(0).toUpperCase() + s.substr(1); }; -lib.containsAny = function(s, fragments) { - for(var i = 0; i < fragments.length; i++) { - if(s.indexOf(fragments[i]) !== -1) return true; +lib.containsAny = function (s, fragments) { + for (var i = 0; i < fragments.length; i++) { + if (s.indexOf(fragments[i]) !== -1) return true; } return false; }; var IS_SAFARI_REGEX = /Version\/[\d\.]+.*Safari/; -lib.isSafari = function() { +lib.isSafari = function () { return IS_SAFARI_REGEX.test(window.navigator.userAgent); }; var IS_IOS_REGEX = /iPad|iPhone|iPod/; -lib.isIOS = function() { +lib.isIOS = function () { return IS_IOS_REGEX.test(window.navigator.userAgent); }; @@ -772,18 +764,18 @@ const IS_MAC_WKWEBVIEW_REGEX = /Macintosh.+AppleWebKit.+Gecko\)$/; lib.isMacWKWebView = () => IS_MAC_WKWEBVIEW_REGEX.test(window.navigator.userAgent); var FIREFOX_VERSION_REGEX = /Firefox\/(\d+)\.\d+/; -lib.getFirefoxVersion = function() { +lib.getFirefoxVersion = function () { var match = FIREFOX_VERSION_REGEX.exec(window.navigator.userAgent); - if(match && match.length === 2) { + if (match && match.length === 2) { var versionInt = parseInt(match[1]); - if(!isNaN(versionInt)) { + if (!isNaN(versionInt)) { return versionInt; } } return null; }; -lib.isD3Selection = function(obj) { +lib.isD3Selection = function (obj) { return obj instanceof d3.selection; }; @@ -815,13 +807,13 @@ lib.isD3Selection = function(obj) { * `querySelectorAll`. * */ -lib.ensureSingle = function(parent, nodeType, className, enterFn) { +lib.ensureSingle = function (parent, nodeType, className, enterFn) { var sel = parent.select(nodeType + (className ? '.' + className : '')); - if(sel.size()) return sel; + if (sel.size()) return sel; var layer = parent.append(nodeType); - if(className) layer.classed(className, true); - if(enterFn) layer.call(enterFn); + if (className) layer.classed(className, true); + if (enterFn) layer.call(enterFn); return layer; }; @@ -836,12 +828,12 @@ lib.ensureSingle = function(parent, nodeType, className, enterFn) { * @param {fn} enterFn (optional) : optional fn applied to entering elements only * @return {d3 selection} selection of new layer */ -lib.ensureSingleById = function(parent, nodeType, id, enterFn) { +lib.ensureSingleById = function (parent, nodeType, id, enterFn) { var sel = parent.select(nodeType + '#' + id); - if(sel.size()) return sel; + if (sel.size()) return sel; var layer = parent.append(nodeType).attr('id', id); - if(enterFn) layer.call(enterFn); + if (enterFn) layer.call(enterFn); return layer; }; @@ -861,24 +853,24 @@ lib.ensureSingleById = function(parent, nodeType, id, enterFn) { * * @return {Object} the constructed object with a full nested path */ -lib.objectFromPath = function(path, value) { +lib.objectFromPath = function (path, value) { var keys = path.split('.'); var tmpObj; - var obj = tmpObj = {}; + var obj = (tmpObj = {}); - for(var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { var key = keys[i]; var el = null; var parts = keys[i].match(/(.*)\[([0-9]+)\]/); - if(parts) { + if (parts) { key = parts[1]; el = parts[2]; tmpObj = tmpObj[key] = []; - if(i === keys.length - 1) { + if (i === keys.length - 1) { tmpObj[el] = value; } else { tmpObj[el] = {}; @@ -886,7 +878,7 @@ lib.objectFromPath = function(path, value) { tmpObj = tmpObj[el]; } else { - if(i === keys.length - 1) { + if (i === keys.length - 1) { tmpObj[key] = value; } else { tmpObj[key] = {}; @@ -935,24 +927,27 @@ function notValid(prop) { return prop.slice(0, 2) === '__'; } -lib.expandObjectPaths = function(data) { +lib.expandObjectPaths = function (data) { var match, key, prop, datum, idx, dest, trailingPath; - if(typeof data === 'object' && !Array.isArray(data)) { - for(key in data) { - if(data.hasOwnProperty(key)) { - if((match = key.match(dottedPropertyRegex))) { + if (typeof data === 'object' && !Array.isArray(data)) { + for (key in data) { + if (data.hasOwnProperty(key)) { + if ((match = key.match(dottedPropertyRegex))) { datum = data[key]; prop = match[1]; - if(notValid(prop)) continue; + if (notValid(prop)) continue; delete data[key]; - data[prop] = lib.extendDeepNoArrays(data[prop] || {}, lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop]); - } else if((match = key.match(indexedPropertyRegex))) { + data[prop] = lib.extendDeepNoArrays( + data[prop] || {}, + lib.objectFromPath(key, lib.expandObjectPaths(datum))[prop] + ); + } else if ((match = key.match(indexedPropertyRegex))) { datum = data[key]; prop = match[1]; - if(notValid(prop)) continue; + if (notValid(prop)) continue; idx = parseInt(match[2]); @@ -960,7 +955,7 @@ lib.expandObjectPaths = function(data) { data[prop] = data[prop] || []; - if(match[3] === '.') { + if (match[3] === '.') { // This is the case where theere are subsequent properties into which // we must recurse, e.g. transforms[0].value trailingPath = match[4]; @@ -983,11 +978,11 @@ lib.expandObjectPaths = function(data) { // This is the case where this property is the end of the line, // e.g. xaxis.range[0] - if(notValid(prop)) continue; + if (notValid(prop)) continue; data[prop][idx] = lib.expandObjectPaths(datum); } } else { - if(notValid(key)) continue; + if (notValid(key)) continue; data[key] = lib.expandObjectPaths(data[key]); } } @@ -1018,14 +1013,14 @@ lib.expandObjectPaths = function(data) { * * @return {string} the value that has been separated */ -lib.numSeparate = function(value, separators, separatethousands) { - if(!separatethousands) separatethousands = false; +lib.numSeparate = function (value, separators, separatethousands) { + if (!separatethousands) separatethousands = false; - if(typeof separators !== 'string' || separators.length === 0) { + if (typeof separators !== 'string' || separators.length === 0) { throw new Error('Separator string required for formatting!'); } - if(typeof value === 'number') { + if (typeof value === 'number') { value = String(value); } @@ -1038,8 +1033,8 @@ lib.numSeparate = function(value, separators, separatethousands) { var x2 = x.length > 1 ? decimalSep + x[1] : ''; // Years are ignored for thousands separators - if(thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { - while(thousandsRe.test(x1)) { + if (thouSep && (x.length > 1 || x1.length > 4 || separatethousands)) { + while (thousandsRe.test(x1)) { x1 = x1.replace(thousandsRe, '$1' + thouSep + '$2'); } } @@ -1062,20 +1057,20 @@ var SIMPLE_PROPERTY_REGEX = /^\w*$/; * * @return {string} templated string */ -lib.templateString = function(string, obj) { +lib.templateString = function (string, obj) { // Not all that useful, but cache nestedProperty instantiation // just in case it speeds things up *slightly*: var getterCache = {}; - return string.replace(lib.TEMPLATE_STRING_REGEX, function(dummy, key) { + return string.replace(lib.TEMPLATE_STRING_REGEX, function (dummy, key) { var v; - if(SIMPLE_PROPERTY_REGEX.test(key)) { + if (SIMPLE_PROPERTY_REGEX.test(key)) { v = obj[key]; } else { getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; - v = getterCache[key](true); // true means don't replace undefined with null + v = getterCache[key](true); // true means don't replace undefined with null } - return (v !== undefined) ? v : ''; + return v !== undefined ? v : ''; }); }; @@ -1084,7 +1079,7 @@ var hovertemplateWarnings = { count: 0, name: 'hovertemplate' }; -lib.hovertemplateString = function() { +lib.hovertemplateString = function () { return templateFormatString.apply(hovertemplateWarnings, arguments); }; @@ -1093,7 +1088,7 @@ var texttemplateWarnings = { count: 0, name: 'texttemplate' }; -lib.texttemplateString = function() { +lib.texttemplateString = function () { return templateFormatString.apply(texttemplateWarnings, arguments); }; @@ -1104,16 +1099,16 @@ lib.texttemplateString = function() { var MULT_DIV_REGEX = /^(\S+)([\*\/])(-?\d+(\.\d+)?)$/; function multDivParser(inputStr) { var match = inputStr.match(MULT_DIV_REGEX); - if(match) return { key: match[1], op: match[2], number: Number(match[3]) }; + if (match) return { key: match[1], op: match[2], number: Number(match[3]) }; return { key: inputStr, op: null, number: null }; } var texttemplateWarningsForShapes = { max: 10, count: 0, name: 'texttemplate', - parseMultDiv: true, + parseMultDiv: true }; -lib.texttemplateStringForShapes = function() { +lib.texttemplateStringForShapes = function () { return templateFormatString.apply(texttemplateWarningsForShapes, arguments); }; @@ -1137,36 +1132,28 @@ var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; function templateFormatString(string, labels, d3locale) { var opts = this; var args = arguments; - if(!labels) labels = {}; + if (!labels) labels = {}; - return string.replace(lib.TEMPLATE_STRING_REGEX, function(match, rawKey, format) { - var isOther = - rawKey === 'xother' || - rawKey === 'yother'; + return string.replace(lib.TEMPLATE_STRING_REGEX, function (match, rawKey, format) { + var isOther = rawKey === 'xother' || rawKey === 'yother'; - var isSpaceOther = - rawKey === '_xother' || - rawKey === '_yother'; + var isSpaceOther = rawKey === '_xother' || rawKey === '_yother'; - var isSpaceOtherSpace = - rawKey === '_xother_' || - rawKey === '_yother_'; + var isSpaceOtherSpace = rawKey === '_xother_' || rawKey === '_yother_'; - var isOtherSpace = - rawKey === 'xother_' || - rawKey === 'yother_'; + var isOtherSpace = rawKey === 'xother_' || rawKey === 'yother_'; var hasOther = isOther || isSpaceOther || isOtherSpace || isSpaceOtherSpace; var key = rawKey; - if(isSpaceOther || isSpaceOtherSpace) key = key.substring(1); - if(isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1); + if (isSpaceOther || isSpaceOtherSpace) key = key.substring(1); + if (isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1); // Shape labels support * and / operators in template string // Parse these if the parseMultDiv param is set to true var parsedOp = null; var parsedNumber = null; - if(opts.parseMultDiv) { + if (opts.parseMultDiv) { var _match = multDivParser(key); key = _match.key; parsedOp = _match.op; @@ -1174,40 +1161,40 @@ function templateFormatString(string, labels, d3locale) { } var value; - if(hasOther) { + if (hasOther) { value = labels[key]; - if(value === undefined) return ''; + if (value === undefined) return ''; } else { var obj, i; - for(i = 3; i < args.length; i++) { + for (i = 3; i < args.length; i++) { obj = args[i]; - if(!obj) continue; - if(obj.hasOwnProperty(key)) { + if (!obj) continue; + if (obj.hasOwnProperty(key)) { value = obj[key]; break; } - if(!SIMPLE_PROPERTY_REGEX.test(key)) { + if (!SIMPLE_PROPERTY_REGEX.test(key)) { // true here means don't convert null to undefined value = lib.nestedProperty(obj, key).get(true); } - if(value !== undefined) break; + if (value !== undefined) break; } } // Apply mult/div operation (if applicable) - if(value !== undefined) { - if(parsedOp === '*') value *= parsedNumber; - if(parsedOp === '/') value /= parsedNumber; + if (value !== undefined) { + if (parsedOp === '*') value *= parsedNumber; + if (parsedOp === '/') value /= parsedNumber; } - if(value === undefined && opts) { - if(opts.count < opts.max) { - lib.warn('Variable \'' + key + '\' in ' + opts.name + ' could not be found!'); + if (value === undefined && opts) { + if (opts.count < opts.max) { + lib.warn("Variable '" + key + "' in " + opts.name + ' could not be found!'); value = match; } - if(opts.count === opts.max) { + if (opts.count === opts.max) { lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed'); } opts.count++; @@ -1215,29 +1202,30 @@ function templateFormatString(string, labels, d3locale) { return match; } - if(format) { + if (format) { var fmt; - if(format[0] === ':') { + if (format[0] === ':') { fmt = d3locale ? d3locale.numberFormat : lib.numberFormat; - if(value !== '') { // e.g. skip missing data on heatmap + if (value !== '') { + // e.g. skip missing data on heatmap value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value); } } - if(format[0] === '|') { + if (format[0] === '|') { fmt = d3locale ? d3locale.timeFormat : utcFormat; var ms = lib.dateTime2ms(value); value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt); } } else { var keyLabel = key + 'Label'; - if(labels.hasOwnProperty(keyLabel)) value = labels[keyLabel]; + if (labels.hasOwnProperty(keyLabel)) value = labels[keyLabel]; } - if(hasOther) { + if (hasOther) { value = '(' + value + ')'; - if(isSpaceOther || isSpaceOtherSpace) value = ' ' + value; - if(isOtherSpace || isSpaceOtherSpace) value = value + ' '; + if (isSpaceOther || isSpaceOtherSpace) value = ' ' + value; + if (isOtherSpace || isSpaceOtherSpace) value = value + ' '; } return value; @@ -1249,22 +1237,22 @@ function templateFormatString(string, labels, d3locale) { */ var char0 = 48; var char9 = 57; -lib.subplotSort = function(a, b) { +lib.subplotSort = function (a, b) { var l = Math.min(a.length, b.length) + 1; var numA = 0; var numB = 0; - for(var i = 0; i < l; i++) { + for (var i = 0; i < l; i++) { var charA = a.charCodeAt(i) || 0; var charB = b.charCodeAt(i) || 0; var isNumA = charA >= char0 && charA <= char9; var isNumB = charB >= char0 && charB <= char9; - if(isNumA) numA = 10 * numA + charA - char0; - if(isNumB) numB = 10 * numB + charB - char0; + if (isNumA) numA = 10 * numA + charA - char0; + if (isNumB) numB = 10 * numB + charB - char0; - if(!isNumA || !isNumB) { - if(numA !== numB) return numA - numB; - if(charA !== charB) return charA - charB; + if (!isNumA || !isNumB) { + if (numA !== numB) return numA - numB; + if (charA !== charB) return charA - charB; } } return numB - numA; @@ -1273,20 +1261,19 @@ lib.subplotSort = function(a, b) { // repeatable pseudorandom generator var randSeed = 2000000000; -lib.seedPseudoRandom = function() { +lib.seedPseudoRandom = function () { randSeed = 2000000000; }; -lib.pseudoRandom = function() { +lib.pseudoRandom = function () { var lastVal = randSeed; randSeed = (69069 * randSeed + 1) % 4294967296; // don't let consecutive vals be too close together // gets away from really trying to be random, in favor of better local uniformity - if(Math.abs(randSeed - lastVal) < 429496729) return lib.pseudoRandom(); + if (Math.abs(randSeed - lastVal) < 429496729) return lib.pseudoRandom(); return randSeed / 4294967296; }; - /** Fill hover 'pointData' container with 'correct' hover text value * * - If trace hoverinfo contains a 'text' flag and hovertext is not set, @@ -1300,20 +1287,24 @@ lib.pseudoRandom = function() { * @param {object} trace * @param {object || array} contOut (mutated here) */ -lib.fillText = function(calcPt, trace, contOut) { - var fill = Array.isArray(contOut) ? - function(v) { contOut.push(v); } : - function(v) { contOut.text = v; }; +lib.fillText = function (calcPt, trace, contOut) { + var fill = Array.isArray(contOut) + ? function (v) { + contOut.push(v); + } + : function (v) { + contOut.text = v; + }; var htx = lib.extractOption(calcPt, trace, 'htx', 'hovertext'); - if(lib.isValidTextValue(htx)) return fill(htx); + if (lib.isValidTextValue(htx)) return fill(htx); var tx = lib.extractOption(calcPt, trace, 'tx', 'text'); - if(lib.isValidTextValue(tx)) return fill(tx); + if (lib.isValidTextValue(tx)) return fill(tx); }; // accept all truthy values and 0 (which gets cast to '0' in the hover labels) -lib.isValidTextValue = function(v) { +lib.isValidTextValue = function (v) { return v || v === 0; }; @@ -1321,11 +1312,11 @@ lib.isValidTextValue = function(v) { * @param {number} ratio * @param {number} n (number of decimal places) */ -lib.formatPercent = function(ratio, n) { +lib.formatPercent = function (ratio, n) { n = n || 0; var str = (Math.round(100 * ratio * Math.pow(10, n)) * Math.pow(0.1, n)).toFixed(n) + '%'; - for(var i = 0; i < n; i++) { - if(str.indexOf('.') !== -1) { + for (var i = 0; i < n; i++) { + if (str.indexOf('.') !== -1) { str = str.replace('0%', '%'); str = str.replace('.%', '%'); } @@ -1333,20 +1324,20 @@ lib.formatPercent = function(ratio, n) { return str; }; -lib.isHidden = function(gd) { +lib.isHidden = function (gd) { var display = window.getComputedStyle(gd).display; return !display || display === 'none'; }; -lib.strTranslate = function(x, y) { - return (x || y) ? 'translate(' + x + ',' + y + ')' : ''; +lib.strTranslate = function (x, y) { + return x || y ? 'translate(' + x + ',' + y + ')' : ''; }; -lib.strRotate = function(a) { +lib.strRotate = function (a) { return a ? 'rotate(' + a + ')' : ''; }; -lib.strScale = function(s) { +lib.strScale = function (s) { return s !== 1 ? 'scale(' + s + ')' : ''; }; @@ -1362,7 +1353,7 @@ lib.strScale = function(s) { * - rotate: (optional) rotation applied after scale * - noCenter: when defined no extra arguments needed in rotation */ -lib.getTextTransform = function(transform) { +lib.getTextTransform = function (transform) { var noCenter = transform.noCenter; var textX = transform.textX; var textY = transform.textY; @@ -1372,34 +1363,24 @@ lib.getTextTransform = function(transform) { var anchorY = transform.anchorY || 0; var rotate = transform.rotate; var scale = transform.scale; - if(!scale) scale = 0; - else if(scale > 1) scale = 1; + if (!scale) scale = 0; + else if (scale > 1) scale = 1; return ( - lib.strTranslate( - targetX - scale * (textX + anchorX), - targetY - scale * (textY + anchorY) - ) + + lib.strTranslate(targetX - scale * (textX + anchorX), targetY - scale * (textY + anchorY)) + lib.strScale(scale) + - (rotate ? - 'rotate(' + rotate + - (noCenter ? '' : ' ' + textX + ' ' + textY) + - ')' : '' - ) + (rotate ? 'rotate(' + rotate + (noCenter ? '' : ' ' + textX + ' ' + textY) + ')' : '') ); }; -lib.setTransormAndDisplay = function(s, transform) { +lib.setTransormAndDisplay = function (s, transform) { s.attr('transform', lib.getTextTransform(transform)); s.style('display', transform.scale ? null : 'none'); }; -lib.ensureUniformFontSize = function(gd, baseFont) { +lib.ensureUniformFontSize = function (gd, baseFont) { var out = lib.extendFlat({}, baseFont); - out.size = Math.max( - baseFont.size, - gd._fullLayout.uniformtext.minsize || 0 - ); + out.size = Math.max(baseFont.size, gd._fullLayout.uniformtext.minsize || 0); return out; }; @@ -1412,15 +1393,15 @@ lib.ensureUniformFontSize = function(gd, baseFont) { * * @return {string} : joined list */ -lib.join2 = function(arr, mainSeparator, lastSeparator) { +lib.join2 = function (arr, mainSeparator, lastSeparator) { var len = arr.length; - if(len > 1) { + if (len > 1) { return arr.slice(0, -1).join(mainSeparator) + lastSeparator + arr[len - 1]; } return arr.join(mainSeparator); }; -lib.bigFont = function(size) { +lib.bigFont = function (size) { return Math.round(1.2 * size); }; @@ -1433,17 +1414,11 @@ var isProblematicFirefox = firefoxVersion !== null && firefoxVersion < 86; * @returns An array with two numbers, representing the x and y coordinates of the mouse pointer * at the event relative to the targeted node. */ -lib.getPositionFromD3Event = function() { - if(isProblematicFirefox) { +lib.getPositionFromD3Event = function () { + if (isProblematicFirefox) { // layerX and layerY are non-standard, so we only fallback to them when we have to: - return [ - d3.event.layerX, - d3.event.layerY - ]; + return [d3.event.layerX, d3.event.layerY]; } else { - return [ - d3.event.offsetX, - d3.event.offsetY - ]; + return [d3.event.offsetX, d3.event.offsetY]; } }; diff --git a/src/plots/template_attributes.js b/src/plots/template_attributes.js index 9cae0b73aa4..fff57bc8388 100644 --- a/src/plots/template_attributes.js +++ b/src/plots/template_attributes.js @@ -9,11 +9,10 @@ function templateFormatStringDescription(opts) { return [ 'Variables are inserted using %{variable},', - 'for example "y: %{y}"' + ( - supportOther ? - ' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.' : - '.' - ), + 'for example "y: %{y}"' + + (supportOther + ? ' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.' + : '.'), 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', FORMAT_LINK, 'for details on the formatting syntax.', @@ -37,29 +36,30 @@ function shapeTemplateFormatStringDescription() { 'A single multiplication or division operation may be applied to numeric variables, and combined with', 'd3 number formatting, for example "Length in cm: %{x0*2.54}", "%{slope*60:.1f} meters per second."', 'For log axes, variable values are given in log units.', - 'For date axes, x/y coordinate variables and center variables use datetimes, while all other variable values use values in ms.', + 'For date axes, x/y coordinate variables and center variables use datetimes, while all other variable values use values in ms.' ].join(' '); } function describeVariables(extra) { var descPart = extra.description ? ' ' + extra.description : ''; var keys = extra.keys || []; - if(keys.length > 0) { + if (keys.length > 0) { var quotedKeys = []; - for(var i = 0; i < keys.length; i++) { + for (var i = 0; i < keys.length; i++) { quotedKeys[i] = '`' + keys[i] + '`'; } descPart = descPart + 'Finally, the template string has access to '; - if(keys.length === 1) { + if (keys.length === 1) { descPart = descPart + 'variable ' + quotedKeys[0]; } else { - descPart = descPart + 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.'; + descPart = + descPart + 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.'; } } return descPart; } -exports.hovertemplateAttrs = function(opts, extra) { +exports.hovertemplateAttrs = function (opts, extra) { opts = opts || {}; extra = extra || {}; @@ -72,7 +72,7 @@ exports.hovertemplateAttrs = function(opts, extra) { description: [ 'Template string used for rendering the information that appear on hover box.', 'Note that this will override `hoverinfo`.', - templateFormatStringDescription({supportOther: true}), + templateFormatStringDescription({ supportOther: true }), 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.', 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', descPart, @@ -81,14 +81,14 @@ exports.hovertemplateAttrs = function(opts, extra) { ].join(' ') }; - if(opts.arrayOk !== false) { + if (opts.arrayOk !== false) { hovertemplate.arrayOk = true; } return hovertemplate; }; -exports.texttemplateAttrs = function(opts, extra) { +exports.texttemplateAttrs = function (opts, extra) { opts = opts || {}; extra = extra || {}; @@ -107,14 +107,13 @@ exports.texttemplateAttrs = function(opts, extra) { ].join(' ') }; - if(opts.arrayOk !== false) { + if (opts.arrayOk !== false) { texttemplate.arrayOk = true; } return texttemplate; }; - -exports.shapeTexttemplateAttrs = function(opts, extra) { +exports.shapeTexttemplateAttrs = function (opts, extra) { opts = opts || {}; extra = extra || {}; @@ -127,10 +126,10 @@ exports.shapeTexttemplateAttrs = function(opts, extra) { dflt: '', editType: opts.editType || 'arraydraw', description: [ - 'Template string used for rendering the ' + newStr + 'shape\'s label.', + 'Template string used for rendering the ' + newStr + "shape's label.", 'Note that this will override `text`.', shapeTemplateFormatStringDescription(), - descPart, + descPart ].join(' ') }; return texttemplate; diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 04f778ec2aa..50981430039 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -21,38 +21,44 @@ var textFontAttrs = fontAttrs({ var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var markerLineWidth = extendFlat({}, - scatterMarkerLineAttrs.width, { dflt: 0 }); - -var markerLine = extendFlat({ - width: markerLineWidth, - editType: 'calc' -}, colorScaleAttrs('marker.line')); - -var marker = extendFlat({ - line: markerLine, - editType: 'calc' -}, colorScaleAttrs('marker'), { - opacity: { - valType: 'number', - arrayOk: true, - dflt: 1, - min: 0, - max: 1, - editType: 'style', - description: 'Sets the opacity of the bars.' +var markerLineWidth = extendFlat({}, scatterMarkerLineAttrs.width, { dflt: 0 }); + +var markerLine = extendFlat( + { + width: markerLineWidth, + editType: 'calc' }, - pattern: pattern, - cornerradius: { - valType: 'any', - editType: 'calc', - description: [ - 'Sets the rounding of corners. May be an integer number of pixels,', - 'or a percentage of bar width (as a string ending in %). Defaults to `layout.barcornerradius`.', - 'In stack or relative barmode, the first trace to set cornerradius is used for the whole stack.' - ].join(' ') + colorScaleAttrs('marker.line') +); + +var marker = extendFlat( + { + line: markerLine, + editType: 'calc' }, -}); + colorScaleAttrs('marker'), + { + opacity: { + valType: 'number', + arrayOk: true, + dflt: 1, + min: 0, + max: 1, + editType: 'style', + description: 'Sets the opacity of the bars.' + }, + pattern: pattern, + cornerradius: { + valType: 'any', + editType: 'calc', + description: [ + 'Sets the rounding of corners. May be an integer number of pixels,', + 'or a percentage of bar width (as a string ending in %). Defaults to `layout.barcornerradius`.', + 'In stack or relative barmode, the first trace to set cornerradius is used for the whole stack.' + ].join(' ') + } + } +); module.exports = { x: scatterAttrs.x, @@ -72,13 +78,19 @@ module.exports = { yhoverformat: axisHoverFormat('y'), text: scatterAttrs.text, - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys + } + ), hovertext: scatterAttrs.hovertext, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), textposition: { valType: 'enumerated', @@ -198,9 +210,7 @@ module.exports = { min: 0, arrayOk: true, editType: 'calc', - description: [ - 'Sets the bar width (in position axis units).' - ].join(' ') + description: ['Sets the bar width (in position axis units).'].join(' ') }, marker: marker, @@ -226,5 +236,5 @@ module.exports = { textfont: scatterAttrs.unselected.textfont, editType: 'style' }, - zorder: scatterAttrs.zorder, + zorder: scatterAttrs.zorder }; diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 66cd408b06f..843f8ce7a14 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -20,7 +20,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { } var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -31,7 +31,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('zorder'); - coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v'); + coerce('orientation', traceOut.x && !traceOut.y ? 'h' : 'v'); coerce('base'); coerce('offset'); coerce('width'); @@ -55,8 +55,8 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { // override defaultColor for error bars with defaultLine var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, { axis: 'y' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, { axis: 'x', inherit: 'y' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); } @@ -68,15 +68,15 @@ function crossTraceDefaults(fullData, fullLayout) { return Lib.coerce(traceOut._input, traceOut, attributes, attr, dflt); } - for(var i = 0; i < fullData.length; i++) { + for (var i = 0; i < fullData.length; i++) { traceOut = fullData[i]; - if(traceOut.type === 'bar') { + if (traceOut.type === 'bar') { traceIn = traceOut._input; // `marker.cornerradius` needs to be coerced here rather than in handleStyleDefaults() // because it needs to happen after `layout.barcornerradius` has been coerced var r = coerce('marker.cornerradius', fullLayout.barcornerradius); - if(traceOut.marker) { + if (traceOut.marker) { traceOut.marker.cornerradius = validateCornerradius(r); } @@ -93,14 +93,14 @@ function crossTraceDefaults(fullData, fullLayout) { // If the given cornerradius value is a numeric string, it will be converted // to a number. function validateCornerradius(r) { - if(isNumeric(r)) { + if (isNumeric(r)) { r = +r; - if(r >= 0) return r; - } else if(typeof r === 'string') { + if (r >= 0) return r; + } else if (typeof r === 'string') { r = r.trim(); - if(r.slice(-1) === '%' && isNumeric(r.slice(0, -1))) { + if (r.slice(-1) === '%' && isNumeric(r.slice(0, -1))) { r = +r.slice(0, -1); - if(r >= 0) return r + '%'; + if (r >= 0) return r + '%'; } } return undefined; @@ -120,7 +120,7 @@ function handleText(traceIn, traceOut, layout, coerce, textposition, opts) { var hasInside = hasBoth || textposition === 'inside'; var hasOutside = hasBoth || textposition === 'outside'; - if(hasInside || hasOutside) { + if (hasInside || hasOutside) { var dfltFont = coerceFont(coerce, 'textfont', layout.font); // Note that coercing `insidetextfont` is always needed – @@ -130,32 +130,32 @@ function handleText(traceIn, traceOut, layout, coerce, textposition, opts) { var insideTextFontDefault = Lib.extendFlat({}, dfltFont); var isTraceTextfontColorSet = traceIn.textfont && traceIn.textfont.color; var isColorInheritedFromLayoutFont = !isTraceTextfontColorSet; - if(isColorInheritedFromLayoutFont) { + if (isColorInheritedFromLayoutFont) { delete insideTextFontDefault.color; } coerceFont(coerce, 'insidetextfont', insideTextFontDefault); - if(hasPathbar) { + if (hasPathbar) { var pathbarTextFontDefault = Lib.extendFlat({}, dfltFont); - if(isColorInheritedFromLayoutFont) { + if (isColorInheritedFromLayoutFont) { delete pathbarTextFontDefault.color; } coerceFont(coerce, 'pathbar.textfont', pathbarTextFontDefault); } - if(hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont); + if (hasOutside) coerceFont(coerce, 'outsidetextfont', dfltFont); - if(moduleHasSelected) coerce('selected.textfont.color'); - if(moduleHasUnselected) coerce('unselected.textfont.color'); - if(moduleHasConstrain) coerce('constraintext'); - if(moduleHasCliponaxis) coerce('cliponaxis'); - if(moduleHasTextangle) coerce('textangle'); + if (moduleHasSelected) coerce('selected.textfont.color'); + if (moduleHasUnselected) coerce('unselected.textfont.color'); + if (moduleHasConstrain) coerce('constraintext'); + if (moduleHasCliponaxis) coerce('cliponaxis'); + if (moduleHasTextangle) coerce('textangle'); coerce('texttemplate'); } - if(hasInside) { - if(moduleHasInsideanchor) coerce('insidetextanchor'); + if (hasInside) { + if (moduleHasInsideanchor) coerce('insidetextanchor'); } } @@ -163,5 +163,5 @@ module.exports = { supplyDefaults: supplyDefaults, crossTraceDefaults: crossTraceDefaults, handleText: handleText, - validateCornerradius: validateCornerradius, + validateCornerradius: validateCornerradius }; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 485d9a3dc55..9a34835376b 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -27,9 +27,11 @@ var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPo var TEXTPAD = constants.TEXTPAD; -function keyFunc(d) {return d.id;} +function keyFunc(d) { + return d.id; +} function getKeyFunc(trace) { - if(trace.ids) { + if (trace.ids) { return keyFunc; } } @@ -42,7 +44,7 @@ function sign(v) { // Returns 1 if a < b and -1 otherwise // (For the purposes of this module we don't care about the case where a == b) function dirSign(a, b) { - return (a < b) ? 1 : -1; + return a < b ? 1 : -1; } function getXY(di, xa, ya, isHorizontal) { @@ -62,17 +64,21 @@ function getXY(di, xa, ya, isHorizontal) { } function transition(selection, fullLayout, opts, makeOnCompleteCallback) { - if(!fullLayout.uniformtext.mode && hasTransition(opts)) { + if (!fullLayout.uniformtext.mode && hasTransition(opts)) { var onComplete; - if(makeOnCompleteCallback) { + if (makeOnCompleteCallback) { onComplete = makeOnCompleteCallback(); } return selection - .transition() - .duration(opts.duration) - .ease(opts.easing) - .each('end', function() { onComplete && onComplete(); }) - .each('interrupt', function() { onComplete && onComplete(); }); + .transition() + .duration(opts.duration) + .ease(opts.easing) + .each('end', function () { + onComplete && onComplete(); + }) + .each('interrupt', function () { + onComplete && onComplete(); + }); } else { return selection; } @@ -88,7 +94,7 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) var fullLayout = gd._fullLayout; var isStatic = gd._context.staticPlot; - if(!opts) { + if (!opts) { opts = { mode: fullLayout.barmode, norm: fullLayout.barmode, @@ -100,21 +106,21 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) clearMinTextSize('bar', fullLayout); } - var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function(cd) { + var bartraces = Lib.makeTraceGroups(traceLayer, cdModule, 'trace bars').each(function (cd) { var plotGroup = d3.select(this); var trace = cd[0].trace; var t = cd[0].t; - var isWaterfall = (trace.type === 'waterfall'); - var isFunnel = (trace.type === 'funnel'); - var isHistogram = (trace.type === 'histogram'); - var isBar = (trace.type === 'bar'); - var shouldDisplayZeros = (isBar || isFunnel); + var isWaterfall = trace.type === 'waterfall'; + var isFunnel = trace.type === 'funnel'; + var isHistogram = trace.type === 'histogram'; + var isBar = trace.type === 'bar'; + var shouldDisplayZeros = isBar || isFunnel; var adjustPixel = 0; - if(isWaterfall && trace.connector.visible && trace.connector.mode === 'between') { + if (isWaterfall && trace.connector.visible && trace.connector.mode === 'between') { adjustPixel = trace.connector.line.width / 2; } - var isHorizontal = (trace.orientation === 'h'); + var isHorizontal = trace.orientation === 'h'; var withTransition = hasTransition(opts); var pointGroup = Lib.ensureSingle(plotGroup, 'g', 'points'); @@ -122,12 +128,11 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) var keyFunc = getKeyFunc(trace); var bars = pointGroup.selectAll('g.point').data(Lib.identity, keyFunc); - bars.enter().append('g') - .classed('point', true); + bars.enter().append('g').classed('point', true); bars.exit().remove(); - bars.each(function(di, i) { + bars.each(function (di, i) { var bar = d3.select(this); // now display the bar @@ -145,26 +150,21 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) var isBlank = (isHorizontal ? x1 - x0 : y1 - y0) === 0; // display zeros if line.width > 0 - if(isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di)) { + if (isBlank && shouldDisplayZeros && helpers.getLineWidth(trace, di)) { isBlank = false; } // skip nulls - if(!isBlank) { - isBlank = ( - !isNumeric(x0) || - !isNumeric(x1) || - !isNumeric(y0) || - !isNumeric(y1) - ); + if (!isBlank) { + isBlank = !isNumeric(x0) || !isNumeric(x1) || !isNumeric(y0) || !isNumeric(y1); } // record isBlank di.isBlank = isBlank; // for blank bars, ensure start and end positions are equal - important for smooth transitions - if(isBlank) { - if(isHorizontal) { + if (isBlank) { + if (isHorizontal) { x1 = x0; } else { y1 = y0; @@ -172,8 +172,8 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) } // in waterfall mode `between` we need to adjust bar end points to match the connector width - if(adjustPixel && !isBlank) { - if(isHorizontal) { + if (adjustPixel && !isBlank) { + if (isHorizontal) { x0 -= dirSign(x0, x1) * adjustPixel; x1 += dirSign(x0, x1) * adjustPixel; } else { @@ -185,8 +185,8 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) var lw; var mc; - if(trace.type === 'waterfall') { - if(!isBlank) { + if (trace.type === 'waterfall') { + if (!isBlank) { var cont = trace[di.dir].marker; lw = cont.line.width; mc = cont.color; @@ -201,12 +201,11 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) // if there are explicit gaps, don't round, // it can make the gaps look crappy - return (opts.gap === 0 && opts.groupgap === 0) ? - d3.round(Math.round(v) - offset, 2) : v; + return opts.gap === 0 && opts.groupgap === 0 ? d3.round(Math.round(v) - offset, 2) : v; } function expandToVisible(v, vc, hideZeroSpan) { - if(hideZeroSpan && v === vc) { + if (hideZeroSpan && v === vc) { // should not expand zero span bars // when start and end positions are identical // i.e. for vertical when y0 === y1 @@ -216,16 +215,19 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) // if it's not in danger of disappearing entirely, // round more precisely - return Math.abs(v - vc) >= 2 ? roundWithLine(v) : - // but if it's very thin, expand it so it's - // necessarily visible, even if it might overlap - // its neighbor - (v > vc ? Math.ceil(v) : Math.floor(v)); + return Math.abs(v - vc) >= 2 + ? roundWithLine(v) + : // but if it's very thin, expand it so it's + // necessarily visible, even if it might overlap + // its neighbor + v > vc + ? Math.ceil(v) + : Math.floor(v); } var op = Color.opacity(mc); - var fixpx = (op < 1 || lw > 0.01) ? roundWithLine : expandToVisible; - if(!gd._context.staticPlot) { + var fixpx = op < 1 || lw > 0.01 ? roundWithLine : expandToVisible; + if (!gd._context.staticPlot) { // if bars are not fully opaque or they have a line // around them, round to integer pixels, mainly for // safari so we prevent overlaps from its expansive @@ -244,9 +246,9 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) // Decide whether to use upper or lower bound of current bar stack // as reference point for rounding var outerBound; - if(di.s0 > 0) { + if (di.s0 > 0) { outerBound = di._sMax; - } else if(di.s0 < 0) { + } else if (di.s0 < 0) { outerBound = di._sMin; } else { outerBound = di.s1 > 0 ? di._sMax : di._sMin; @@ -254,15 +256,17 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) // Calculate corner radius of bar in pixels function calcCornerRadius(crValue, crForm) { - if(!crValue) return 0; + if (!crValue) return 0; var barWidth = isHorizontal ? Math.abs(y1 - y0) : Math.abs(x1 - x0); var barLength = isHorizontal ? Math.abs(x1 - x0) : Math.abs(y1 - y0); var stackedBarTotalLength = fixpx(Math.abs(c2p(outerBound, true) - c2p(0, true))); - var maxRadius = di.hasB ? Math.min(barWidth / 2, barLength / 2) : Math.min(barWidth / 2, stackedBarTotalLength); + var maxRadius = di.hasB + ? Math.min(barWidth / 2, barLength / 2) + : Math.min(barWidth / 2, stackedBarTotalLength); var crPx; - if(crForm === '%') { + if (crForm === '%') { // If radius is given as a % string, convert to number of pixels var crPercent = Math.min(50, crValue); crPx = barWidth * (crPercent / 100); @@ -273,82 +277,219 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) return fixpx(Math.max(Math.min(crPx, maxRadius), 0)); } // Exclude anything which is not explicitly a bar or histogram chart from rounding - var r = (isBar || isHistogram) ? calcCornerRadius(t.cornerradiusvalue, t.cornerradiusform) : 0; + var r = isBar || isHistogram ? calcCornerRadius(t.cornerradiusvalue, t.cornerradiusform) : 0; // Construct path string for bar var path, h; // Default rectangular path (used if no rounding) var rectanglePath = 'M' + x0 + ',' + y0 + 'V' + y1 + 'H' + x1 + 'V' + y0 + 'Z'; var overhead = 0; - if(r && di.s) { + if (r && di.s) { // Bar has cornerradius, and nonzero size // Check amount of 'overhead' (bars stacked above this one) // to see whether we need to round or not var refPoint = sign(di.s0) === 0 || sign(di.s) === sign(di.s0) ? di.s1 : di.s0; overhead = fixpx(!di.hasB ? Math.abs(c2p(outerBound, true) - c2p(refPoint, true)) : 0); - if(overhead < r) { + if (overhead < r) { // Calculate parameters for rounded corners var xdir = dirSign(x0, x1); var ydir = dirSign(y0, y1); // Sweep direction for rounded corner arcs - var cornersweep = (xdir === -ydir) ? 1 : 0; - if(isHorizontal) { + var cornersweep = xdir === -ydir ? 1 : 0; + if (isHorizontal) { // Horizontal bars - if(di.hasB) { + if (di.hasB) { // Floating base: Round 1st & 2nd, and 3rd & 4th corners - path = 'M' + (x0 + r * xdir) + ',' + y0 + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + x0 + ',' + (y0 + r * ydir) + - 'V' + (y1 - r * ydir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x0 + r * xdir) + ',' + y1 + - 'H' + (x1 - r * xdir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + x1 + ',' + (y1 - r * ydir) + - 'V' + (y0 + r * ydir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x1 - r * xdir) + ',' + y0 + + path = + 'M' + + (x0 + r * xdir) + + ',' + + y0 + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + x0 + + ',' + + (y0 + r * ydir) + + 'V' + + (y1 - r * ydir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x0 + r * xdir) + + ',' + + y1 + + 'H' + + (x1 - r * xdir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + x1 + + ',' + + (y1 - r * ydir) + + 'V' + + (y0 + r * ydir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x1 - r * xdir) + + ',' + + y0 + 'Z'; } else { // Base on axis: Round 3rd and 4th corners // Helper variables to help with extending rounding down to lower bars h = Math.abs(x1 - x0) + overhead; - var dy1 = (h < r) ? r - Math.sqrt(h * (2 * r - h)) : 0; - var dy2 = (overhead > 0) ? Math.sqrt(overhead * (2 * r - overhead)) : 0; + var dy1 = h < r ? r - Math.sqrt(h * (2 * r - h)) : 0; + var dy2 = overhead > 0 ? Math.sqrt(overhead * (2 * r - overhead)) : 0; var xminfunc = xdir > 0 ? Math.max : Math.min; - path = 'M' + x0 + ',' + y0 + - 'V' + (y1 - dy1 * ydir) + - 'H' + xminfunc(x1 - (r - overhead) * xdir, x0) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + x1 + ',' + (y1 - r * ydir - dy2) + - 'V' + (y0 + r * ydir + dy2) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + xminfunc(x1 - (r - overhead) * xdir, x0) + ',' + (y0 + dy1 * ydir) + + path = + 'M' + + x0 + + ',' + + y0 + + 'V' + + (y1 - dy1 * ydir) + + 'H' + + xminfunc(x1 - (r - overhead) * xdir, x0) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + x1 + + ',' + + (y1 - r * ydir - dy2) + + 'V' + + (y0 + r * ydir + dy2) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + xminfunc(x1 - (r - overhead) * xdir, x0) + + ',' + + (y0 + dy1 * ydir) + 'Z'; } } else { // Vertical bars - if(di.hasB) { + if (di.hasB) { // Floating base: Round 1st & 4th, and 2nd & 3rd corners - path = 'M' + (x0 + r * xdir) + ',' + y0 + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + x0 + ',' + (y0 + r * ydir) + - 'V' + (y1 - r * ydir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x0 + r * xdir) + ',' + y1 + - 'H' + (x1 - r * xdir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + x1 + ',' + (y1 - r * ydir) + - 'V' + (y0 + r * ydir) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x1 - r * xdir) + ',' + y0 + + path = + 'M' + + (x0 + r * xdir) + + ',' + + y0 + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + x0 + + ',' + + (y0 + r * ydir) + + 'V' + + (y1 - r * ydir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x0 + r * xdir) + + ',' + + y1 + + 'H' + + (x1 - r * xdir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + x1 + + ',' + + (y1 - r * ydir) + + 'V' + + (y0 + r * ydir) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x1 - r * xdir) + + ',' + + y0 + 'Z'; } else { // Base on axis: Round 2nd and 3rd corners // Helper variables to help with extending rounding down to lower bars h = Math.abs(y1 - y0) + overhead; - var dx1 = (h < r) ? r - Math.sqrt(h * (2 * r - h)) : 0; - var dx2 = (overhead > 0) ? Math.sqrt(overhead * (2 * r - overhead)) : 0; + var dx1 = h < r ? r - Math.sqrt(h * (2 * r - h)) : 0; + var dx2 = overhead > 0 ? Math.sqrt(overhead * (2 * r - overhead)) : 0; var yminfunc = ydir > 0 ? Math.max : Math.min; - path = 'M' + (x0 + dx1 * xdir) + ',' + y0 + - 'V' + yminfunc(y1 - (r - overhead) * ydir, y0) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x0 + r * xdir - dx2) + ',' + y1 + - 'H' + (x1 - r * xdir + dx2) + - 'A ' + r + ',' + r + ' 0 0 ' + cornersweep + ' ' + (x1 - dx1 * xdir) + ',' + yminfunc(y1 - (r - overhead) * ydir, y0) + - 'V' + y0 + 'Z'; + path = + 'M' + + (x0 + dx1 * xdir) + + ',' + + y0 + + 'V' + + yminfunc(y1 - (r - overhead) * ydir, y0) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x0 + r * xdir - dx2) + + ',' + + y1 + + 'H' + + (x1 - r * xdir + dx2) + + 'A ' + + r + + ',' + + r + + ' 0 0 ' + + cornersweep + + ' ' + + (x1 - dx1 * xdir) + + ',' + + yminfunc(y1 - (r - overhead) * ydir, y0) + + 'V' + + y0 + + 'Z'; } } } else { @@ -361,19 +502,18 @@ function plot(gd, plotinfo, cdModule, traceLayer, opts, makeOnCompleteCallback) } var sel = transition(Lib.ensureSingle(bar, 'path'), fullLayout, opts, makeOnCompleteCallback); - sel - .style('vector-effect', isStatic ? 'none' : 'non-scaling-stroke') - .attr('d', (isNaN((x1 - x0) * (y1 - y0)) || (isBlank && gd._context.staticPlot)) ? 'M0,0Z' : path) + sel.style('vector-effect', isStatic ? 'none' : 'non-scaling-stroke') + .attr('d', isNaN((x1 - x0) * (y1 - y0)) || (isBlank && gd._context.staticPlot) ? 'M0,0Z' : path) .call(Drawing.setClipUrl, plotinfo.layerClipId, gd); - if(!fullLayout.uniformtext.mode && withTransition) { + if (!fullLayout.uniformtext.mode && withTransition) { var styleFns = Drawing.makePointStyleFns(trace); Drawing.singlePointStyle(di, sel, trace, styleFns, gd); } appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, opts, makeOnCompleteCallback); - if(plotinfo.layerClipId) { + if (plotinfo.layerClipId) { Drawing.hideOutsideRangePoint(di, bar.select('text'), xa, ya, trace.xcalendar, trace.ycalendar); } }); @@ -413,27 +553,25 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op // get trace attributes var trace = cd[0].trace; - var isHorizontal = (trace.orientation === 'h'); + var isHorizontal = trace.orientation === 'h'; var text = getText(fullLayout, cd, i, xa, ya); textPosition = getTextPosition(trace, i); // compute text position - var inStackOrRelativeMode = - opts.mode === 'stack' || - opts.mode === 'relative'; + var inStackOrRelativeMode = opts.mode === 'stack' || opts.mode === 'relative'; var calcBar = cd[i]; var isOutmostBar = !inStackOrRelativeMode || calcBar._outmost; var hasB = calcBar.hasB; - var barIsRounded = r && (r - overhead) > TEXTPAD; + var barIsRounded = r && r - overhead > TEXTPAD; - if(!text || + if ( + !text || textPosition === 'none' || - ((calcBar.isBlank || x0 === x1 || y0 === y1) && ( - textPosition === 'auto' || - textPosition === 'inside'))) { + ((calcBar.isBlank || x0 === x1 || y0 === y1) && (textPosition === 'auto' || textPosition === 'inside')) + ) { bar.select('text').remove(); return; } @@ -447,17 +585,17 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op // Special case: don't use the c2p(v, true) value on log size axes, // so that we can get correctly inside text scaling var di = bar.datum(); - if(isHorizontal) { - if(xa.type === 'log' && di.s0 <= 0) { - if(xa.range[0] < xa.range[1]) { + if (isHorizontal) { + if (xa.type === 'log' && di.s0 <= 0) { + if (xa.range[0] < xa.range[1]) { x0 = 0; } else { x0 = xa._length; } } } else { - if(ya.type === 'log' && di.s0 <= 0) { - if(ya.range[0] < ya.range[1]) { + if (ya.type === 'log' && di.s0 <= 0) { + if (ya.range[0] < ya.range[1]) { y0 = ya._length; } else { y0 = 0; @@ -480,12 +618,12 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op var textHeight; var font; - if(textPosition === 'outside') { - if(!isOutmostBar && !calcBar.hasB) textPosition = 'inside'; + if (textPosition === 'outside') { + if (!isOutmostBar && !calcBar.hasB) textPosition = 'inside'; } - if(textPosition === 'auto') { - if(isOutmostBar) { + if (textPosition === 'auto') { + if (isOutmostBar) { // draw text using insideTextFont and check if it fits inside bar textPosition = 'inside'; @@ -497,32 +635,41 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op textWidth = textBB.width; textHeight = textBB.height; - var textHasSize = (textWidth > 0 && textHeight > 0); + var textHasSize = textWidth > 0 && textHeight > 0; var fitsInside; - if(barIsRounded) { + if (barIsRounded) { // If bar is rounded, check if text fits between rounded corners - if(hasB) { - fitsInside = ( + if (hasB) { + fitsInside = textfitsInsideBar(barWidth - 2 * r, barHeight, textWidth, textHeight, isHorizontal) || - textfitsInsideBar(barWidth, barHeight - 2 * r, textWidth, textHeight, isHorizontal) - ); - } else if(isHorizontal) { - fitsInside = ( + textfitsInsideBar(barWidth, barHeight - 2 * r, textWidth, textHeight, isHorizontal); + } else if (isHorizontal) { + fitsInside = textfitsInsideBar(barWidth - (r - overhead), barHeight, textWidth, textHeight, isHorizontal) || - textfitsInsideBar(barWidth, barHeight - 2 * (r - overhead), textWidth, textHeight, isHorizontal) - ); + textfitsInsideBar( + barWidth, + barHeight - 2 * (r - overhead), + textWidth, + textHeight, + isHorizontal + ); } else { - fitsInside = ( + fitsInside = textfitsInsideBar(barWidth, barHeight - (r - overhead), textWidth, textHeight, isHorizontal) || - textfitsInsideBar(barWidth - 2 * (r - overhead), barHeight, textWidth, textHeight, isHorizontal) - ); + textfitsInsideBar( + barWidth - 2 * (r - overhead), + barHeight, + textWidth, + textHeight, + isHorizontal + ); } } else { fitsInside = textfitsInsideBar(barWidth, barHeight, textWidth, textHeight, isHorizontal); } - if(textHasSize && fitsInside) { + if (textHasSize && fitsInside) { textPosition = 'inside'; } else { textPosition = 'outside'; @@ -534,19 +681,17 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op } } - if(!textSelection) { - font = Lib.ensureUniformFontSize(gd, (textPosition === 'outside') ? outsideTextFont : insideTextFont); + if (!textSelection) { + font = Lib.ensureUniformFontSize(gd, textPosition === 'outside' ? outsideTextFont : insideTextFont); textSelection = appendTextNode(bar, text, font); var currentTransform = textSelection.attr('transform'); textSelection.attr('transform', ''); - textBB = Drawing.bBox(textSelection.node()), - textWidth = textBB.width, - textHeight = textBB.height; + (textBB = Drawing.bBox(textSelection.node())), (textWidth = textBB.width), (textHeight = textBB.height); textSelection.attr('transform', currentTransform); - if(textWidth <= 0 || textHeight <= 0) { + if (textWidth <= 0 || textHeight <= 0) { textSelection.remove(); return; } @@ -556,10 +701,8 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op // compute text transform var transform, constrained; - if(textPosition === 'outside') { - constrained = - trace.constraintext === 'both' || - trace.constraintext === 'outside'; + if (textPosition === 'outside') { + constrained = trace.constraintext === 'both' || trace.constraintext === 'outside'; transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, { isHorizontal: isHorizontal, @@ -567,9 +710,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op angle: angle }); } else { - constrained = - trace.constraintext === 'both' || - trace.constraintext === 'inside'; + constrained = trace.constraintext === 'both' || trace.constraintext === 'inside'; transform = toMoveInsideBar(x0, x1, y0, y1, textBB, { isHorizontal: isHorizontal, @@ -578,7 +719,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op anchor: insidetextanchor, hasB: hasB, r: r, - overhead: overhead, + overhead: overhead }); } @@ -591,21 +732,21 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op } function textfitsInsideBar(barWidth, barHeight, textWidth, textHeight, isHorizontal) { - if(barWidth < 0 || barHeight < 0) return false; - var fitsInside = (textWidth <= barWidth && textHeight <= barHeight); - var fitsInsideIfRotated = (textWidth <= barHeight && textHeight <= barWidth); - var fitsInsideIfShrunk = (isHorizontal) ? - (barWidth >= textWidth * (barHeight / textHeight)) : - (barHeight >= textHeight * (barWidth / textWidth)); + if (barWidth < 0 || barHeight < 0) return false; + var fitsInside = textWidth <= barWidth && textHeight <= barHeight; + var fitsInsideIfRotated = textWidth <= barHeight && textHeight <= barWidth; + var fitsInsideIfShrunk = isHorizontal + ? barWidth >= textWidth * (barHeight / textHeight) + : barHeight >= textHeight * (barWidth / textWidth); return fitsInside || fitsInsideIfRotated || fitsInsideIfShrunk; } function getRotateFromAngle(angle) { - return (angle === 'auto') ? 0 : angle; + return angle === 'auto' ? 0 : angle; } function getRotatedTextSize(textBB, rotate) { - var a = Math.PI / 180 * rotate; + var a = (Math.PI / 180) * rotate; var absSin = Math.abs(Math.sin(a)); var absCos = Math.abs(Math.cos(a)); @@ -636,21 +777,18 @@ function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) { var ly = Math.abs(y1 - y0); // compute remaining space - var textpad = ( - lx > (2 * TEXTPAD) && - ly > (2 * TEXTPAD) - ) ? TEXTPAD : 0; + var textpad = lx > 2 * TEXTPAD && ly > 2 * TEXTPAD ? TEXTPAD : 0; lx -= 2 * textpad; ly -= 2 * textpad; var rotate = getRotateFromAngle(angle); - if((angle === 'auto') && + if ( + angle === 'auto' && !(textWidth <= lx && textHeight <= ly) && - (textWidth > lx || textHeight > ly) && ( - !(textWidth > ly || textHeight > lx) || - ((textWidth < textHeight) !== (lx < ly)) - )) { + (textWidth > lx || textHeight > ly) && + (!(textWidth > ly || textHeight > lx) || textWidth < textHeight !== lx < ly) + ) { rotate += 90; } @@ -658,47 +796,37 @@ function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) { var scale, padForRounding; // Scale text for rounded bars - if(r && (r - overhead) > TEXTPAD) { + if (r && r - overhead > TEXTPAD) { var scaleAndPad = scaleTextForRoundedBar(x0, x1, y0, y1, t, r, overhead, isHorizontal, hasB); scale = scaleAndPad.scale; padForRounding = scaleAndPad.pad; - // Scale text for non-rounded bars + // Scale text for non-rounded bars } else { scale = 1; - if(constrained) { - scale = Math.min( - 1, - lx / t.x, - ly / t.y - ); + if (constrained) { + scale = Math.min(1, lx / t.x, ly / t.y); } padForRounding = 0; } // compute text and target positions - var textX = ( - textBB.left * toLeft + - textBB.right * toRight - ); + var textX = textBB.left * toLeft + textBB.right * toRight; var textY = (textBB.top + textBB.bottom) / 2; - var targetX = ( - (x0 + TEXTPAD) * toLeft + - (x1 - TEXTPAD) * toRight - ); + var targetX = (x0 + TEXTPAD) * toLeft + (x1 - TEXTPAD) * toRight; var targetY = (y0 + y1) / 2; var anchorX = 0; var anchorY = 0; - if(isStart || isEnd) { + if (isStart || isEnd) { var extrapad = (isHorizontal ? t.x : t.y) / 2; - if(r && (isEnd || hasB)) { + if (r && (isEnd || hasB)) { textpad += padForRounding; } var dir = isHorizontal ? dirSign(x0, x1) : dirSign(y0, y1); - if(isHorizontal) { - if(isStart) { + if (isHorizontal) { + if (isStart) { targetX = x0 + dir * textpad; anchorX = -dir * extrapad; } else { @@ -706,7 +834,7 @@ function toMoveInsideBar(x0, x1, y0, y1, textBB, opts) { anchorX = dir * extrapad; } } else { - if(isStart) { + if (isStart) { targetY = y0 + dir * textpad; anchorY = -dir * extrapad; } else { @@ -733,28 +861,27 @@ function scaleTextForRoundedBar(x0, x1, y0, y1, t, r, overhead, isHorizontal, ha var barHeight = Math.max(0, Math.abs(y1 - y0) - 2 * TEXTPAD); var R = r - TEXTPAD; var clippedR = overhead ? R - Math.sqrt(R * R - (R - overhead) * (R - overhead)) : R; - var rX = hasB ? R * 2 : (isHorizontal ? R - overhead : 2 * clippedR); - var rY = hasB ? R * 2 : (isHorizontal ? 2 * clippedR : R - overhead); + var rX = hasB ? R * 2 : isHorizontal ? R - overhead : 2 * clippedR; + var rY = hasB ? R * 2 : isHorizontal ? 2 * clippedR : R - overhead; var a, b, c; var scale, pad; - - if(t.y / t.x >= barHeight / (barWidth - rX)) { + if (t.y / t.x >= barHeight / (barWidth - rX)) { // Case 1 (Tall text) scale = barHeight / t.y; - } else if(t.y / t.x <= (barHeight - rY) / barWidth) { + } else if (t.y / t.x <= (barHeight - rY) / barWidth) { // Case 2 (Wide text) scale = barWidth / t.x; - } else if(!hasB && isHorizontal) { + } else if (!hasB && isHorizontal) { // Case 3a (Quadratic case, two side corners are rounded) - a = t.x * t.x + t.y * t.y / 4; + a = t.x * t.x + (t.y * t.y) / 4; b = -2 * t.x * (barWidth - R) - t.y * (barHeight / 2 - R); c = (barWidth - R) * (barWidth - R) + (barHeight / 2 - R) * (barHeight / 2 - R) - R * R; scale = (-b + Math.sqrt(b * b - 4 * a * c)) / (2 * a); - } else if(!hasB) { + } else if (!hasB) { // Case 3b (Quadratic case, two top/bottom corners are rounded) - a = t.x * t.x / 4 + t.y * t.y; + a = (t.x * t.x) / 4 + t.y * t.y; b = -t.x * (barWidth / 2 - R) - 2 * t.y * (barHeight - R); c = (barWidth / 2 - R) * (barWidth / 2 - R) + (barHeight - R) * (barHeight - R) - R * R; @@ -770,10 +897,24 @@ function scaleTextForRoundedBar(x0, x1, y0, y1, t, r, overhead, isHorizontal, ha // Scale should not be larger than 1 scale = Math.min(1, scale); - if(isHorizontal) { - pad = Math.max(0, R - Math.sqrt(Math.max(0, R * R - (R - (barHeight - t.y * scale) / 2) * (R - (barHeight - t.y * scale) / 2))) - overhead); + if (isHorizontal) { + pad = Math.max( + 0, + R - + Math.sqrt( + Math.max(0, R * R - (R - (barHeight - t.y * scale) / 2) * (R - (barHeight - t.y * scale) / 2)) + ) - + overhead + ); } else { - pad = Math.max(0, R - Math.sqrt(Math.max(0, R * R - (R - (barWidth - t.x * scale) / 2) * (R - (barWidth - t.x * scale) / 2))) - overhead); + pad = Math.max( + 0, + R - + Math.sqrt( + Math.max(0, R * R - (R - (barWidth - t.x * scale) / 2) * (R - (barWidth - t.x * scale) / 2)) + ) - + overhead + ); } return { scale: scale, pad: pad }; @@ -792,18 +933,16 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { var textpad; // Keep the padding so the text doesn't sit right against // the bars, but don't factor it into barWidth - if(isHorizontal) { - textpad = (ly > 2 * TEXTPAD) ? TEXTPAD : 0; + if (isHorizontal) { + textpad = ly > 2 * TEXTPAD ? TEXTPAD : 0; } else { - textpad = (lx > 2 * TEXTPAD) ? TEXTPAD : 0; + textpad = lx > 2 * TEXTPAD ? TEXTPAD : 0; } // compute rotate and scale var scale = 1; - if(constrained) { - scale = (isHorizontal) ? - Math.min(1, ly / textHeight) : - Math.min(1, lx / textWidth); + if (constrained) { + scale = isHorizontal ? Math.min(1, ly / textHeight) : Math.min(1, lx / textWidth); } var rotate = getRotateFromAngle(angle); @@ -819,7 +958,7 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { var anchorY = 0; var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); - if(isHorizontal) { + if (isHorizontal) { targetX = x1 - dir * textpad; anchorX = dir * extrapad; } else { @@ -844,9 +983,9 @@ function getText(fullLayout, cd, index, xa, ya) { var texttemplate = trace.texttemplate; var value; - if(texttemplate) { + if (texttemplate) { value = calcTexttemplate(fullLayout, cd, index, xa, ya); - } else if(trace.textinfo) { + } else if (trace.textinfo) { value = calcTextinfo(cd, index, xa, ya); } else { value = helpers.getValue(trace.text, index); @@ -863,15 +1002,15 @@ function getTextPosition(trace, index) { function calcTexttemplate(fullLayout, cd, index, xa, ya) { var trace = cd[0].trace; var texttemplate = Lib.castOption(trace, index, 'texttemplate'); - if(!texttemplate) return ''; - var isHistogram = (trace.type === 'histogram'); - var isWaterfall = (trace.type === 'waterfall'); - var isFunnel = (trace.type === 'funnel'); + if (!texttemplate) return ''; + var isHistogram = trace.type === 'histogram'; + var isWaterfall = trace.type === 'waterfall'; + var isFunnel = trace.type === 'funnel'; var isHorizontal = trace.orientation === 'h'; var pLetter, pAxis; var vLetter, vAxis; - if(isHorizontal) { + if (isHorizontal) { pLetter = 'y'; pAxis = ya; vLetter = 'x'; @@ -898,7 +1037,7 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { obj.labelLabel = obj[pLetter + 'Label'] = formatLabel(cdi.p); var tx = Lib.castOption(trace, cdi.i, 'text'); - if(tx === 0 || tx) obj.text = tx; + if (tx === 0 || tx) obj.text = tx; obj.value = cdi.s; obj.valueLabel = obj[vLetter + 'Label'] = formatNumber(cdi.s); @@ -906,12 +1045,12 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { var pt = {}; appendArrayPointValue(pt, trace, cdi.i); - if(isHistogram || pt.x === undefined) pt.x = isHorizontal ? obj.value : obj.label; - if(isHistogram || pt.y === undefined) pt.y = isHorizontal ? obj.label : obj.value; - if(isHistogram || pt.xLabel === undefined) pt.xLabel = isHorizontal ? obj.valueLabel : obj.labelLabel; - if(isHistogram || pt.yLabel === undefined) pt.yLabel = isHorizontal ? obj.labelLabel : obj.valueLabel; + if (isHistogram || pt.x === undefined) pt.x = isHorizontal ? obj.value : obj.label; + if (isHistogram || pt.y === undefined) pt.y = isHorizontal ? obj.label : obj.value; + if (isHistogram || pt.xLabel === undefined) pt.xLabel = isHorizontal ? obj.valueLabel : obj.labelLabel; + if (isHistogram || pt.yLabel === undefined) pt.yLabel = isHorizontal ? obj.labelLabel : obj.valueLabel; - if(isWaterfall) { + if (isWaterfall) { obj.delta = +cdi.rawS || cdi.s; obj.deltaLabel = formatNumber(obj.delta); obj.final = cdi.v; @@ -920,7 +1059,7 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { obj.initialLabel = formatNumber(obj.initial); } - if(isFunnel) { + if (isFunnel) { obj.value = cdi.s; obj.valueLabel = formatNumber(obj.value); @@ -933,15 +1072,15 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { } var customdata = Lib.castOption(trace, cdi.i, 'customdata'); - if(customdata) obj.customdata = customdata; + if (customdata) obj.customdata = customdata; return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {}); } function calcTextinfo(cd, index, xa, ya) { var trace = cd[0].trace; - var isHorizontal = (trace.orientation === 'h'); - var isWaterfall = (trace.type === 'waterfall'); - var isFunnel = (trace.type === 'funnel'); + var isHorizontal = trace.orientation === 'h'; + var isWaterfall = trace.type === 'waterfall'; + var isFunnel = trace.type === 'funnel'; function formatLabel(u) { var pAxis = isHorizontal ? ya : xa; @@ -960,50 +1099,52 @@ function calcTextinfo(cd, index, xa, ya) { var text = []; var tx; - var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; }; + var hasFlag = function (flag) { + return parts.indexOf(flag) !== -1; + }; - if(hasFlag('label')) { + if (hasFlag('label')) { text.push(formatLabel(cd[index].p)); } - if(hasFlag('text')) { + if (hasFlag('text')) { tx = Lib.castOption(trace, cdi.i, 'text'); - if(tx === 0 || tx) text.push(tx); + if (tx === 0 || tx) text.push(tx); } - if(isWaterfall) { + if (isWaterfall) { var delta = +cdi.rawS || cdi.s; var final = cdi.v; var initial = final - delta; - if(hasFlag('initial')) text.push(formatNumber(initial)); - if(hasFlag('delta')) text.push(formatNumber(delta)); - if(hasFlag('final')) text.push(formatNumber(final)); + if (hasFlag('initial')) text.push(formatNumber(initial)); + if (hasFlag('delta')) text.push(formatNumber(delta)); + if (hasFlag('final')) text.push(formatNumber(final)); } - if(isFunnel) { - if(hasFlag('value')) text.push(formatNumber(cdi.s)); + if (isFunnel) { + if (hasFlag('value')) text.push(formatNumber(cdi.s)); var nPercent = 0; - if(hasFlag('percent initial')) nPercent++; - if(hasFlag('percent previous')) nPercent++; - if(hasFlag('percent total')) nPercent++; + if (hasFlag('percent initial')) nPercent++; + if (hasFlag('percent previous')) nPercent++; + if (hasFlag('percent total')) nPercent++; var hasMultiplePercents = nPercent > 1; - if(hasFlag('percent initial')) { + if (hasFlag('percent initial')) { tx = Lib.formatPercent(cdi.begR); - if(hasMultiplePercents) tx += ' of initial'; + if (hasMultiplePercents) tx += ' of initial'; text.push(tx); } - if(hasFlag('percent previous')) { + if (hasFlag('percent previous')) { tx = Lib.formatPercent(cdi.difR); - if(hasMultiplePercents) tx += ' of previous'; + if (hasMultiplePercents) tx += ' of previous'; text.push(tx); } - if(hasFlag('percent total')) { + if (hasFlag('percent total')) { tx = Lib.formatPercent(cdi.sumR); - if(hasMultiplePercents) tx += ' of total'; + if (hasMultiplePercents) tx += ' of total'; text.push(tx); } } diff --git a/src/traces/barpolar/attributes.js b/src/traces/barpolar/attributes.js index 0c0349ec4f2..c7d41c74a5a 100644 --- a/src/traces/barpolar/attributes.js +++ b/src/traces/barpolar/attributes.js @@ -5,7 +5,6 @@ var extendFlat = require('../../lib/extend').extendFlat; var scatterPolarAttrs = require('../scatterpolar/attributes'); var barAttrs = require('../bar/attributes'); - module.exports = { r: scatterPolarAttrs.r, theta: scatterPolarAttrs.theta, @@ -31,15 +30,10 @@ module.exports = { ].join(' ') }), offset: extendFlat({}, barAttrs.offset, { - description: [ - 'Shifts the angular position where the bar is drawn', - '(in *thetatunit* units).' - ].join(' ') + description: ['Shifts the angular position where the bar is drawn', '(in *thetatunit* units).'].join(' ') }), width: extendFlat({}, barAttrs.width, { - description: [ - 'Sets the bar angular width (in *thetaunit* units).' - ].join(' ') + description: ['Sets the bar angular width (in *thetaunit* units).'].join(' ') }), text: extendFlat({}, barAttrs.text, { @@ -47,7 +41,7 @@ module.exports = { 'Sets hover text elements associated with each bar.', 'If a single string, the same string appears over all bars.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s coordinates.' + "this trace's coordinates." ].join(' ') }), hovertext: extendFlat({}, barAttrs.hovertext, { diff --git a/src/traces/barpolar/defaults.js b/src/traces/barpolar/defaults.js index bca5588817b..b8d1677f9fd 100644 --- a/src/traces/barpolar/defaults.js +++ b/src/traces/barpolar/defaults.js @@ -12,7 +12,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js index 6acfb11b7db..f2e90228f0e 100644 --- a/src/traces/box/attributes.js +++ b/src/traces/box/attributes.js @@ -15,18 +15,12 @@ module.exports = { y: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the y sample data or coordinates.', - 'See overview for more info.' - ].join(' ') + description: 'Sets the y sample data or coordinates. See overview for more info.' }, x: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the x sample data or coordinates.', - 'See overview for more info.' - ].join(' ') + description: 'Sets the x sample data or coordinates. See overview for more info.' }, x0: { valType: 'any', @@ -52,18 +46,12 @@ module.exports = { dx: { valType: 'number', editType: 'calc', - description: [ - 'Sets the x coordinate step for multi-box traces', - 'set using q1/median/q3.' - ].join(' ') + description: 'Sets the x coordinate step for multi-box traces set using q1/median/q3.' }, dy: { valType: 'number', editType: 'calc', - description: [ - 'Sets the y coordinate step for multi-box traces', - 'set using q1/median/q3.' - ].join(' ') + description: 'Sets the y coordinate step for multi-box traces set using q1/median/q3.' }, xperiod: scatterAttrs.xperiod, @@ -92,24 +80,18 @@ module.exports = { editType: 'calc+clearAxisTypes', description: [ 'Sets the Quartile 1 values.', - 'There should be as many items as the number of boxes desired.', + 'There should be as many items as the number of boxes desired.' ].join(' ') }, median: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the median values.', - 'There should be as many items as the number of boxes desired.', - ].join(' ') + description: 'Sets the median values. There should be as many items as the number of boxes desired.' }, q3: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the Quartile 3 values.', - 'There should be as many items as the number of boxes desired.', - ].join(' ') + description: 'Sets the Quartile 3 values. There should be as many items as the number of boxes desired.' }, lowerfence: { valType: 'data_array', @@ -142,7 +124,7 @@ module.exports = { 'Notches displays a confidence interval around the median.', 'We compute the confidence interval as median +/- 1.57 * IQR / sqrt(N),', 'where IQR is the interquartile range and N is the sample size.', - 'If two boxes\' notches do not overlap there is 95% confidence their medians differ.', + "If two boxes' notches do not overlap there is 95% confidence their medians differ.", 'See https://sites.google.com/site/davidsstatistics/home/notched-box-plots for more info.', 'Defaults to *false* unless `notchwidth` or `notchspan` is set.' ].join(' ') @@ -155,7 +137,7 @@ module.exports = { editType: 'calc', description: [ 'Sets the width of the notches relative to', - 'the box\' width.', + 'the box width.', 'For example, with 0, the notches are as wide as the box(es).' ].join(' ') }, @@ -163,7 +145,7 @@ module.exports = { valType: 'data_array', editType: 'calc', description: [ - 'Sets the notch span from the boxes\' `median` values.', + "Sets the notch span from the boxes' `median` values.", 'There should be as many items as the number of boxes desired.', 'This attribute has effect only under the q1/median/q3 signature.', 'If `notchspan` is not provided but a sample (in `y` or `x`) is set,', @@ -192,7 +174,7 @@ module.exports = { 'Defaults to *suspectedoutliers* when `marker.outliercolor` or', '`marker.line.outliercolor` is set.', 'Defaults to *all* under the q1/median/q3 signature.', - 'Otherwise defaults to *outliers*.', + 'Otherwise defaults to *outliers*.' ].join(' ') }, jitter: { @@ -227,7 +209,7 @@ module.exports = { description: [ 'Scales the box size when sizemode=sd', 'Allowing boxes to be drawn across any stddev range', - 'For example 1-stddev, 3-stddev, 5-stddev', + 'For example 1-stddev, 3-stddev, 5-stddev' ].join(' ') }, sizemode: { @@ -240,7 +222,7 @@ module.exports = { 'quartiles means box is drawn between Q1 and Q3', 'SD means the box is drawn between Mean +- Standard Deviation', 'Argument sdmultiple (default 1) to scale the box size', - 'So it could be drawn 1-stddev, 3-stddev etc', + 'So it could be drawn 1-stddev, 3-stddev etc' ].join(' ') }, boxmean: { @@ -248,7 +230,7 @@ module.exports = { values: [true, 'sd', false], editType: 'calc', description: [ - 'If *true*, the mean of the box(es)\' underlying distribution is', + "If *true*, the mean of the box(es)' underlying distribution is", 'drawn as a dashed line inside the box(es).', 'If *sd* the standard deviation is also drawn.', 'Defaults to *true* when `mean` is set.', @@ -296,7 +278,7 @@ module.exports = { dflt: 'linear', editType: 'calc', description: [ - 'Sets the method used to compute the sample\'s Q1 and Q3 quartiles.', + "Sets the method used to compute the sample's Q1 and Q3 quartiles.", 'The *linear* method uses the 25th percentile for Q1 and 75th percentile for Q3', 'as computed using method #10 (listed on http://jse.amstat.org/v14n3/langford.html).', @@ -332,23 +314,18 @@ module.exports = { editType: 'style', description: 'Sets the color of the outlier sample points.' }, - symbol: extendFlat({}, scatterMarkerAttrs.symbol, - {arrayOk: false, editType: 'plot'}), - opacity: extendFlat({}, scatterMarkerAttrs.opacity, - {arrayOk: false, dflt: 1, editType: 'style'}), - angle: extendFlat({}, scatterMarkerAttrs.angle, - {arrayOk: false, editType: 'calc'}), - size: extendFlat({}, scatterMarkerAttrs.size, - {arrayOk: false, editType: 'calc'}), - color: extendFlat({}, scatterMarkerAttrs.color, - {arrayOk: false, editType: 'style'}), + symbol: extendFlat({}, scatterMarkerAttrs.symbol, { arrayOk: false, editType: 'plot' }), + opacity: extendFlat({}, scatterMarkerAttrs.opacity, { arrayOk: false, dflt: 1, editType: 'style' }), + angle: extendFlat({}, scatterMarkerAttrs.angle, { arrayOk: false, editType: 'calc' }), + size: extendFlat({}, scatterMarkerAttrs.size, { arrayOk: false, editType: 'calc' }), + color: extendFlat({}, scatterMarkerAttrs.color, { arrayOk: false, editType: 'style' }), line: { - color: extendFlat({}, scatterMarkerLineAttrs.color, - {arrayOk: false, dflt: colorAttrs.defaultLine, editType: 'style'} - ), - width: extendFlat({}, scatterMarkerLineAttrs.width, - {arrayOk: false, dflt: 0, editType: 'style'} - ), + color: extendFlat({}, scatterMarkerLineAttrs.color, { + arrayOk: false, + dflt: colorAttrs.defaultLine, + editType: 'style' + }), + width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false, dflt: 0, editType: 'style' }), outliercolor: { valType: 'color', editType: 'style', @@ -362,9 +339,7 @@ module.exports = { min: 0, dflt: 1, editType: 'style', - description: [ - 'Sets the border line width (in px) of the outlier sample points.' - ].join(' ') + description: 'Sets the border line width (in px) of the outlier sample points.' }, editType: 'style' }, @@ -397,7 +372,7 @@ module.exports = { editType: 'calc', description: [ 'Sets the width of the whiskers relative to', - 'the box\' width.', + 'the box width.', 'For example, with 1, the whiskers are as wide as the box(es).' ].join(' ') }, @@ -429,28 +404,19 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.', + "this trace's (x,y) coordinates.", 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }), - hovertext: extendFlat({}, scatterAttrs.hovertext, { - description: 'Same as `text`.' - }), - hovertemplate: hovertemplateAttrs({ - description: [ - 'N.B. This only has an effect when hovering on points.' - ].join(' ') - }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { description: 'Same as `text`.' }), + hovertemplate: hovertemplateAttrs({ description: 'N.B. This only has an effect when hovering on points.' }), hoveron: { valType: 'flaglist', flags: ['boxes', 'points'], dflt: 'boxes+points', editType: 'style', - description: [ - 'Do the hover effects highlight individual boxes ', - 'or sample points or both?' - ].join(' ') + description: 'Do the hover effects highlight individual boxes or sample points or both?' }, zorder: scatterAttrs.zorder }; diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index 8241e0cb140..132957dc816 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -14,7 +14,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { } handleSampleDefaults(traceIn, traceOut, coerce, layout); - if(traceOut.visible === false) return; + if (traceOut.visible === false) return; handlePeriodDefaults(traceIn, traceOut, layout, coerce); coerce('xhoverformat'); @@ -22,7 +22,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var hasPreCompStats = traceOut._hasPreCompStats; - if(hasPreCompStats) { + if (hasPreCompStats) { coerce('lowerfence'); coerce('upperfence'); } @@ -32,50 +32,50 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('fillcolor', Color.addOpacity(traceOut.line.color, 0.5)); var boxmeanDflt = false; - if(hasPreCompStats) { + if (hasPreCompStats) { var mean = coerce('mean'); var sd = coerce('sd'); - if(mean && mean.length) { + if (mean && mean.length) { boxmeanDflt = true; - if(sd && sd.length) boxmeanDflt = 'sd'; + if (sd && sd.length) boxmeanDflt = 'sd'; } } coerce('whiskerwidth'); var sizemode = coerce('sizemode'); var boxmean; - if(sizemode === 'quartiles') { + if (sizemode === 'quartiles') { boxmean = coerce('boxmean', boxmeanDflt); } coerce('showwhiskers', sizemode === 'quartiles'); - if((sizemode === 'sd') || (boxmean === 'sd')) { + if (sizemode === 'sd' || boxmean === 'sd') { coerce('sdmultiple'); } coerce('width'); coerce('quartilemethod'); var notchedDflt = false; - if(hasPreCompStats) { + if (hasPreCompStats) { var notchspan = coerce('notchspan'); - if(notchspan && notchspan.length) { + if (notchspan && notchspan.length) { notchedDflt = true; } - } else if(Lib.validate(traceIn.notchwidth, attributes.notchwidth)) { + } else if (Lib.validate(traceIn.notchwidth, attributes.notchwidth)) { notchedDflt = true; } var notched = coerce('notched', notchedDflt); - if(notched) coerce('notchwidth'); + if (notched) coerce('notchwidth'); - handlePointsDefaults(traceIn, traceOut, coerce, {prefix: 'box'}); + handlePointsDefaults(traceIn, traceOut, coerce, { prefix: 'box' }); coerce('zorder'); } function handleSampleDefaults(traceIn, traceOut, coerce, layout) { function getDims(arr) { var dims = 0; - if(arr && arr.length) { + if (arr && arr.length) { dims += 1; - if(Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) { + if (Lib.isArrayOrTypedArray(arr[0]) && arr[0].length) { dims += 1; } } @@ -90,21 +90,13 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { var x = coerce('x'); var sLen; - if(traceOut.type === 'box') { + if (traceOut.type === 'box') { var q1 = coerce('q1'); var median = coerce('median'); var q3 = coerce('q3'); - traceOut._hasPreCompStats = ( - q1 && q1.length && - median && median.length && - q3 && q3.length - ); - sLen = Math.min( - Lib.minRowLength(q1), - Lib.minRowLength(median), - Lib.minRowLength(q3) - ); + traceOut._hasPreCompStats = q1 && q1.length && median && median.length && q3 && q3.length; + sLen = Math.min(Lib.minRowLength(q1), Lib.minRowLength(median), Lib.minRowLength(q3)); } var yDims = getDims(y); @@ -118,14 +110,14 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { }; var defaultOrientation, len; - if(traceOut._hasPreCompStats) { - switch(String(xDims) + String(yDims)) { + if (traceOut._hasPreCompStats) { + switch (String(xDims) + String(yDims)) { // no x / no y case '00': var setInX = valid('x0') || valid('dx'); var setInY = valid('y0') || valid('dy'); - if(setInY && !setInX) { + if (setInY && !setInX) { defaultOrientation = 'h'; } else { defaultOrientation = 'v'; @@ -167,25 +159,25 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { case '22': var hasCategories = false; var i; - for(i = 0; i < x.length; i++) { - if(autoType(x[i], calendar, opts) === 'category') { + for (i = 0; i < x.length; i++) { + if (autoType(x[i], calendar, opts) === 'category') { hasCategories = true; break; } } - if(hasCategories) { + if (hasCategories) { defaultOrientation = 'v'; len = Math.min(sLen, xLen, y.length); } else { - for(i = 0; i < y.length; i++) { - if(autoType(y[i], calendar, opts) === 'category') { + for (i = 0; i < y.length; i++) { + if (autoType(y[i], calendar, opts) === 'category') { hasCategories = true; break; } } - if(hasCategories) { + if (hasCategories) { defaultOrientation = 'h'; len = Math.min(sLen, x.length, yLen); } else { @@ -195,21 +187,21 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { } break; } - } else if(yDims > 0) { + } else if (yDims > 0) { defaultOrientation = 'v'; - if(xDims > 0) { + if (xDims > 0) { len = Math.min(xLen, yLen); } else { len = Math.min(yLen); } - } else if(xDims > 0) { + } else if (xDims > 0) { defaultOrientation = 'h'; len = Math.min(xLen); } else { len = 0; } - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -218,18 +210,18 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { var orientation = coerce('orientation', defaultOrientation); // these are just used for positioning, they never define the sample - if(traceOut._hasPreCompStats) { - if(orientation === 'v' && xDims === 0) { + if (traceOut._hasPreCompStats) { + if (orientation === 'v' && xDims === 0) { coerce('x0', 0); coerce('dx', 1); - } else if(orientation === 'h' && yDims === 0) { + } else if (orientation === 'h' && yDims === 0) { coerce('y0', 0); coerce('dy', 1); } } else { - if(orientation === 'v' && xDims === 0) { + if (orientation === 'v' && xDims === 0) { coerce('x0'); - } else if(orientation === 'h' && yDims === 0) { + } else if (orientation === 'h' && yDims === 0) { coerce('y0'); } } @@ -245,15 +237,15 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) { var lineoutliercolor = coerce('marker.line.outliercolor'); var modeDflt = 'outliers'; - if(traceOut._hasPreCompStats) { + if (traceOut._hasPreCompStats) { modeDflt = 'all'; - } else if(outlierColorDflt || lineoutliercolor) { + } else if (outlierColorDflt || lineoutliercolor) { modeDflt = 'suspectedoutliers'; } var mode = coerce(prefix + 'points', modeDflt); - if(mode) { + if (mode) { coerce('jitter', mode === 'all' ? 0.3 : 0); coerce('pointpos', mode === 'all' ? -1.5 : 0); @@ -266,7 +258,7 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) { coerce('marker.line.color'); coerce('marker.line.width'); - if(mode === 'suspectedoutliers') { + if (mode === 'suspectedoutliers') { coerce('marker.line.outliercolor', traceOut.marker.color); coerce('marker.line.outlierwidth'); } @@ -283,7 +275,7 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) { } var hoveron = coerce('hoveron'); - if(hoveron === 'all' || hoveron.indexOf('points') !== -1) { + if (hoveron === 'all' || hoveron.indexOf('points') !== -1) { coerce('hovertemplate'); } @@ -297,14 +289,14 @@ function crossTraceDefaults(fullData, fullLayout) { return Lib.coerce(traceOut._input, traceOut, attributes, attr); } - for(var i = 0; i < fullData.length; i++) { + for (var i = 0; i < fullData.length; i++) { traceOut = fullData[i]; var traceType = traceOut.type; - if(traceType === 'box' || traceType === 'violin') { + if (traceType === 'box' || traceType === 'violin') { traceIn = traceOut._input; - var mode = fullLayout[traceType + 'mode']; - if(mode === 'group') { + var mode = fullLayout[traceType + 'mode']; + if (mode === 'group') { handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce, mode); } } diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index 03e2d8b6bb5..d601a4e918a 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -10,82 +10,82 @@ var extendFlat = require('../../lib/extend').extendFlat; var scatterGeoMarkerLineAttrs = scatterGeoAttrs.marker.line; -module.exports = extendFlat({ - locations: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the coordinates via location IDs or names.', - 'See `locationmode` for more info.' - ].join(' ') - }, - locationmode: scatterGeoAttrs.locationmode, - z: { - valType: 'data_array', - editType: 'calc', - description: 'Sets the color values.' - }, - geojson: extendFlat({}, scatterGeoAttrs.geojson, { - description: [ - 'Sets optional GeoJSON data associated with this trace.', - 'If not given, the features on the base map are used.', +module.exports = extendFlat( + { + locations: { + valType: 'data_array', + editType: 'calc', + description: ['Sets the coordinates via location IDs or names.', 'See `locationmode` for more info.'].join( + ' ' + ) + }, + locationmode: scatterGeoAttrs.locationmode, + z: { + valType: 'data_array', + editType: 'calc', + description: 'Sets the color values.' + }, + geojson: extendFlat({}, scatterGeoAttrs.geojson, { + description: [ + 'Sets optional GeoJSON data associated with this trace.', + 'If not given, the features on the base map are used.', - 'It can be set as a valid GeoJSON object or as a URL string.', - 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', - 'with geometries of type *Polygon* or *MultiPolygon*.' + 'It can be set as a valid GeoJSON object or as a URL string.', + 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', + 'with geometries of type *Polygon* or *MultiPolygon*.' - // TODO add topojson support with additional 'topojsonobject' attr? - // https://github.com/topojson/topojson-specification/blob/master/README.md - ].join(' ') - }), - featureidkey: scatterGeoAttrs.featureidkey, + // TODO add topojson support with additional 'topojsonobject' attr? + // https://github.com/topojson/topojson-specification/blob/master/README.md + ].join(' ') + }), + featureidkey: scatterGeoAttrs.featureidkey, - text: extendFlat({}, scatterGeoAttrs.text, { - description: 'Sets the text elements associated with each location.' - }), - hovertext: extendFlat({}, scatterGeoAttrs.hovertext, { - description: 'Same as `text`.' - }), - marker: { - line: { - color: extendFlat({}, scatterGeoMarkerLineAttrs.color, {dflt: defaultLine}), - width: extendFlat({}, scatterGeoMarkerLineAttrs.width, {dflt: 1}), + text: extendFlat({}, scatterGeoAttrs.text, { + description: 'Sets the text elements associated with each location.' + }), + hovertext: extendFlat({}, scatterGeoAttrs.hovertext, { + description: 'Same as `text`.' + }), + marker: { + line: { + color: extendFlat({}, scatterGeoMarkerLineAttrs.color, { dflt: defaultLine }), + width: extendFlat({}, scatterGeoMarkerLineAttrs.width, { dflt: 1 }), + editType: 'calc' + }, + opacity: { + valType: 'number', + arrayOk: true, + min: 0, + max: 1, + dflt: 1, + editType: 'style', + description: 'Sets the opacity of the locations.' + }, editType: 'calc' }, - opacity: { - valType: 'number', - arrayOk: true, - min: 0, - max: 1, - dflt: 1, - editType: 'style', - description: 'Sets the opacity of the locations.' - }, - editType: 'calc' - }, - selected: { - marker: { - opacity: scatterGeoAttrs.selected.marker.opacity, + selected: { + marker: { + opacity: scatterGeoAttrs.selected.marker.opacity, + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - unselected: { - marker: { - opacity: scatterGeoAttrs.unselected.marker.opacity, + unselected: { + marker: { + opacity: scatterGeoAttrs.unselected.marker.opacity, + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - editType: 'calc', - flags: ['location', 'z', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + editType: 'calc', + flags: ['location', 'z', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs(), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, colorScaleAttrs('', { cLetter: 'z', diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index af94097d5a0..097233b813a 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -7,7 +7,7 @@ var attributes = require('./attributes'); const locationmodeBreakingChangeWarning = [ 'The library used by the *country names* `locationmode` option is changing in the next major version.', 'Some country names in existing plots may not work in the new version.', - 'To ensure consistent behavior, consider setting `locationmode` to *ISO-3*.', + 'To ensure consistent behavior, consider setting `locationmode` to *ISO-3*.' ].join(' '); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { @@ -18,7 +18,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var locations = coerce('locations'); var z = coerce('z'); - if(!(locations && locations.length && Lib.isArrayOrTypedArray(z) && z.length)) { + if (!(locations && locations.length && Lib.isArrayOrTypedArray(z) && z.length)) { traceOut.visible = false; return; } @@ -28,17 +28,17 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var geojson = coerce('geojson'); var locationmodeDflt; - if((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) { + if ((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) { locationmodeDflt = 'geojson-id'; } var locationMode = coerce('locationmode', locationmodeDflt); - if(locationMode === 'country names') { + if (locationMode === 'country names') { Lib.warn(locationmodeBreakingChangeWarning); } - if(locationMode === 'geojson-id') { + if (locationMode === 'geojson-id') { coerce('featureidkey'); } @@ -47,10 +47,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertemplate'); var mlw = coerce('marker.line.width'); - if(mlw) coerce('marker.line.color'); + if (mlw) coerce('marker.line.color'); coerce('marker.opacity'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/choroplethmap/attributes.js b/src/traces/choroplethmap/attributes.js index d9ecb585332..5d1d64def74 100644 --- a/src/traces/choroplethmap/attributes.js +++ b/src/traces/choroplethmap/attributes.js @@ -6,102 +6,100 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({ - locations: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets which features found in *geojson* to plot using', - 'their feature `id` field.' - ].join(' ') - }, +module.exports = extendFlat( + { + locations: { + valType: 'data_array', + editType: 'calc', + description: ['Sets which features found in *geojson* to plot using', 'their feature `id` field.'].join(' ') + }, - // TODO - // Maybe start with only one value (that we could name e.g. 'geojson-id'), - // but eventually: - // - we could also support for our own dist/topojson/* - // .. and locationmode: choroplethAttrs.locationmode, + // TODO + // Maybe start with only one value (that we could name e.g. 'geojson-id'), + // but eventually: + // - we could also support for our own dist/topojson/* + // .. and locationmode: choroplethAttrs.locationmode, - z: { - valType: 'data_array', - editType: 'calc', - description: 'Sets the color values.' - }, + z: { + valType: 'data_array', + editType: 'calc', + description: 'Sets the color values.' + }, - // TODO maybe we could also set a "key" to dig out values out of the - // GeoJSON feature `properties` fields? + // TODO maybe we could also set a "key" to dig out values out of the + // GeoJSON feature `properties` fields? - geojson: { - valType: 'any', - editType: 'calc', - description: [ - 'Sets the GeoJSON data associated with this trace.', + geojson: { + valType: 'any', + editType: 'calc', + description: [ + 'Sets the GeoJSON data associated with this trace.', - 'It can be set as a valid GeoJSON object or as a URL string.', - 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', - 'with geometries of type *Polygon* or *MultiPolygon*.' - ].join(' ') - }, - featureidkey: extendFlat({}, choroplethAttrs.featureidkey, { - description: [ - 'Sets the key in GeoJSON features which is used as id to match the items', - 'included in the `locations` array.', - 'Support nested property, for example *properties.name*.' - ].join(' ') - }), + 'It can be set as a valid GeoJSON object or as a URL string.', + 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', + 'with geometries of type *Polygon* or *MultiPolygon*.' + ].join(' ') + }, + featureidkey: extendFlat({}, choroplethAttrs.featureidkey, { + description: [ + 'Sets the key in GeoJSON features which is used as id to match the items', + 'included in the `locations` array.', + 'Support nested property, for example *properties.name*.' + ].join(' ') + }), - // TODO agree on name / behaviour - // - // 'below' is used currently for layout.map.layers, - // even though it's not very plotly-esque. - // - // Note also, that the map-gl style don't all have the same layers, - // see https://codepen.io/etpinard/pen/ydVMwM for full list - below: { - valType: 'string', - editType: 'plot', - description: [ - 'Determines if the choropleth polygons will be inserted', - 'before the layer with the specified ID.', - 'By default, choroplethmap traces are placed above the water layers.', - 'If set to \'\',', - 'the layer will be inserted above every existing layer.' - ].join(' ') - }, + // TODO agree on name / behaviour + // + // 'below' is used currently for layout.map.layers, + // even though it's not very plotly-esque. + // + // Note also, that the map-gl style don't all have the same layers, + // see https://codepen.io/etpinard/pen/ydVMwM for full list + below: { + valType: 'string', + editType: 'plot', + description: [ + 'Determines if the choropleth polygons will be inserted', + 'before the layer with the specified ID.', + 'By default, choroplethmap traces are placed above the water layers.', + "If set to '',", + 'the layer will be inserted above every existing layer.' + ].join(' ') + }, - text: choroplethAttrs.text, - hovertext: choroplethAttrs.hovertext, + text: choroplethAttrs.text, + hovertext: choroplethAttrs.hovertext, - marker: { - line: { - color: extendFlat({}, choroplethAttrs.marker.line.color, {editType: 'plot'}), - width: extendFlat({}, choroplethAttrs.marker.line.width, {editType: 'plot'}), + marker: { + line: { + color: extendFlat({}, choroplethAttrs.marker.line.color, { editType: 'plot' }), + width: extendFlat({}, choroplethAttrs.marker.line.width, { editType: 'plot' }), + editType: 'calc' + }, + // TODO maybe having a dflt less than 1, together with `below:''` would be better? + opacity: extendFlat({}, choroplethAttrs.marker.opacity, { editType: 'plot' }), editType: 'calc' }, - // TODO maybe having a dflt less than 1, together with `below:''` would be better? - opacity: extendFlat({}, choroplethAttrs.marker.opacity, {editType: 'plot'}), - editType: 'calc' - }, - selected: { - marker: { - opacity: extendFlat({}, choroplethAttrs.selected.marker.opacity, {editType: 'plot'}), + selected: { + marker: { + opacity: extendFlat({}, choroplethAttrs.selected.marker.opacity, { editType: 'plot' }), + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - unselected: { - marker: { - opacity: extendFlat({}, choroplethAttrs.unselected.marker.opacity, {editType: 'plot'}), + unselected: { + marker: { + opacity: extendFlat({}, choroplethAttrs.unselected.marker.opacity, { editType: 'plot' }), + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - hoverinfo: choroplethAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs({}, {keys: ['properties']}), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + hoverinfo: choroplethAttrs.hoverinfo, + hovertemplate: hovertemplateAttrs({}, { keys: ['properties'] }), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, colorScaleAttrs('', { cLetter: 'z', diff --git a/src/traces/choroplethmap/defaults.js b/src/traces/choroplethmap/defaults.js index 943670f8a88..7eef2b0b02b 100644 --- a/src/traces/choroplethmap/defaults.js +++ b/src/traces/choroplethmap/defaults.js @@ -13,8 +13,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var z = coerce('z'); var geojson = coerce('geojson'); - if(!Lib.isArrayOrTypedArray(locations) || !locations.length || - !Lib.isArrayOrTypedArray(z) || !z.length || + if ( + !Lib.isArrayOrTypedArray(locations) || + !locations.length || + !Lib.isArrayOrTypedArray(z) || + !z.length || !((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) ) { traceOut.visible = false; @@ -32,10 +35,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertemplate'); var mlw = coerce('marker.line.width'); - if(mlw) coerce('marker.line.color'); + if (mlw) coerce('marker.line.color'); coerce('marker.opacity'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/choroplethmapbox/attributes.js b/src/traces/choroplethmapbox/attributes.js index 6affbc98350..ee39ac4cf7b 100644 --- a/src/traces/choroplethmapbox/attributes.js +++ b/src/traces/choroplethmapbox/attributes.js @@ -6,102 +6,100 @@ var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplat var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({ - locations: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets which features found in *geojson* to plot using', - 'their feature `id` field.' - ].join(' ') - }, +module.exports = extendFlat( + { + locations: { + valType: 'data_array', + editType: 'calc', + description: ['Sets which features found in *geojson* to plot using', 'their feature `id` field.'].join(' ') + }, - // TODO - // Maybe start with only one value (that we could name e.g. 'geojson-id'), - // but eventually: - // - we could also support for our own dist/topojson/* - // .. and locationmode: choroplethAttrs.locationmode, + // TODO + // Maybe start with only one value (that we could name e.g. 'geojson-id'), + // but eventually: + // - we could also support for our own dist/topojson/* + // .. and locationmode: choroplethAttrs.locationmode, - z: { - valType: 'data_array', - editType: 'calc', - description: 'Sets the color values.' - }, + z: { + valType: 'data_array', + editType: 'calc', + description: 'Sets the color values.' + }, - // TODO maybe we could also set a "key" to dig out values out of the - // GeoJSON feature `properties` fields? + // TODO maybe we could also set a "key" to dig out values out of the + // GeoJSON feature `properties` fields? - geojson: { - valType: 'any', - editType: 'calc', - description: [ - 'Sets the GeoJSON data associated with this trace.', + geojson: { + valType: 'any', + editType: 'calc', + description: [ + 'Sets the GeoJSON data associated with this trace.', - 'It can be set as a valid GeoJSON object or as a URL string.', - 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', - 'with geometries of type *Polygon* or *MultiPolygon*.' - ].join(' ') - }, - featureidkey: extendFlat({}, choroplethAttrs.featureidkey, { - description: [ - 'Sets the key in GeoJSON features which is used as id to match the items', - 'included in the `locations` array.', - 'Support nested property, for example *properties.name*.' - ].join(' ') - }), + 'It can be set as a valid GeoJSON object or as a URL string.', + 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', + 'with geometries of type *Polygon* or *MultiPolygon*.' + ].join(' ') + }, + featureidkey: extendFlat({}, choroplethAttrs.featureidkey, { + description: [ + 'Sets the key in GeoJSON features which is used as id to match the items', + 'included in the `locations` array.', + 'Support nested property, for example *properties.name*.' + ].join(' ') + }), - // TODO agree on name / behaviour - // - // 'below' is used currently for layout.mapbox.layers, - // even though it's not very plotly-esque. - // - // Note also, that the mapbox-gl style don't all have the same layers, - // see https://codepen.io/etpinard/pen/ydVMwM for full list - below: { - valType: 'string', - editType: 'plot', - description: [ - 'Determines if the choropleth polygons will be inserted', - 'before the layer with the specified ID.', - 'By default, choroplethmapbox traces are placed above the water layers.', - 'If set to \'\',', - 'the layer will be inserted above every existing layer.' - ].join(' ') - }, + // TODO agree on name / behaviour + // + // 'below' is used currently for layout.mapbox.layers, + // even though it's not very plotly-esque. + // + // Note also, that the mapbox-gl style don't all have the same layers, + // see https://codepen.io/etpinard/pen/ydVMwM for full list + below: { + valType: 'string', + editType: 'plot', + description: [ + 'Determines if the choropleth polygons will be inserted', + 'before the layer with the specified ID.', + 'By default, choroplethmapbox traces are placed above the water layers.', + "If set to '',", + 'the layer will be inserted above every existing layer.' + ].join(' ') + }, - text: choroplethAttrs.text, - hovertext: choroplethAttrs.hovertext, + text: choroplethAttrs.text, + hovertext: choroplethAttrs.hovertext, - marker: { - line: { - color: extendFlat({}, choroplethAttrs.marker.line.color, {editType: 'plot'}), - width: extendFlat({}, choroplethAttrs.marker.line.width, {editType: 'plot'}), + marker: { + line: { + color: extendFlat({}, choroplethAttrs.marker.line.color, { editType: 'plot' }), + width: extendFlat({}, choroplethAttrs.marker.line.width, { editType: 'plot' }), + editType: 'calc' + }, + // TODO maybe having a dflt less than 1, together with `below:''` would be better? + opacity: extendFlat({}, choroplethAttrs.marker.opacity, { editType: 'plot' }), editType: 'calc' }, - // TODO maybe having a dflt less than 1, together with `below:''` would be better? - opacity: extendFlat({}, choroplethAttrs.marker.opacity, {editType: 'plot'}), - editType: 'calc' - }, - selected: { - marker: { - opacity: extendFlat({}, choroplethAttrs.selected.marker.opacity, {editType: 'plot'}), + selected: { + marker: { + opacity: extendFlat({}, choroplethAttrs.selected.marker.opacity, { editType: 'plot' }), + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - unselected: { - marker: { - opacity: extendFlat({}, choroplethAttrs.unselected.marker.opacity, {editType: 'plot'}), + unselected: { + marker: { + opacity: extendFlat({}, choroplethAttrs.unselected.marker.opacity, { editType: 'plot' }), + editType: 'plot' + }, editType: 'plot' }, - editType: 'plot' - }, - hoverinfo: choroplethAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs({}, {keys: ['properties']}), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + hoverinfo: choroplethAttrs.hoverinfo, + hovertemplate: hovertemplateAttrs({}, { keys: ['properties'] }), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, colorScaleAttrs('', { cLetter: 'z', diff --git a/src/traces/choroplethmapbox/defaults.js b/src/traces/choroplethmapbox/defaults.js index 943670f8a88..7eef2b0b02b 100644 --- a/src/traces/choroplethmapbox/defaults.js +++ b/src/traces/choroplethmapbox/defaults.js @@ -13,8 +13,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var z = coerce('z'); var geojson = coerce('geojson'); - if(!Lib.isArrayOrTypedArray(locations) || !locations.length || - !Lib.isArrayOrTypedArray(z) || !z.length || + if ( + !Lib.isArrayOrTypedArray(locations) || + !locations.length || + !Lib.isArrayOrTypedArray(z) || + !z.length || !((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) ) { traceOut.visible = false; @@ -32,10 +35,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertemplate'); var mlw = coerce('marker.line.width'); - if(mlw) coerce('marker.line.color'); + if (mlw) coerce('marker.line.color'); coerce('marker.opacity'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/cone/attributes.js b/src/traces/cone/attributes.js index c85ce94c8c6..7d121e3e57d 100644 --- a/src/traces/cone/attributes.js +++ b/src/traces/cone/attributes.js @@ -12,26 +12,17 @@ var attrs = { x: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the x coordinates of the vector field', - 'and of the displayed cones.' - ].join(' ') + description: ['Sets the x coordinates of the vector field', 'and of the displayed cones.'].join(' ') }, y: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the y coordinates of the vector field', - 'and of the displayed cones.' - ].join(' ') + description: ['Sets the y coordinates of the vector field', 'and of the displayed cones.'].join(' ') }, z: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the z coordinates of the vector field', - 'and of the displayed cones.' - ].join(' ') + description: ['Sets the z coordinates of the vector field', 'and of the displayed cones.'].join(' ') }, u: { @@ -119,7 +110,7 @@ var attrs = { 'With `sizemode` set to *raw*, its default value is *1*.', 'With `sizemode` set to *scaled*, `sizeref` is unitless, its default value is *0.5*.', 'With `sizemode` set to *absolute*, `sizeref` has the same units as the u/v/w vector field,', - 'its the default value is half the sample\'s maximum vector norm.' + "its the default value is half the sample's maximum vector norm." ].join(' ') }, @@ -129,8 +120,8 @@ var attrs = { values: ['tip', 'tail', 'cm', 'center'], dflt: 'cm', description: [ - 'Sets the cones\' anchor with respect to their x/y/z positions.', - 'Note that *cm* denote the cone\'s center of mass which corresponds to', + "Sets the cones' anchor with respect to their x/y/z positions.", + "Note that *cm* denote the cone's center of mass which corresponds to", '1/4 from the tail to tip.' ].join(' ') }, @@ -154,7 +145,7 @@ var attrs = { description: 'Same as `text`.' }, - hovertemplate: hovertemplateAttrs({editType: 'calc'}, {keys: ['norm']}), + hovertemplate: hovertemplateAttrs({ editType: 'calc' }, { keys: ['norm'] }), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), @@ -162,18 +153,21 @@ var attrs = { yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z'), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }; -extendFlat(attrs, colorScaleAttrs('', { - colorAttr: 'u/v/w norm', - showScaleDflt: true, - editTypeOverride: 'calc' -})); +extendFlat( + attrs, + colorScaleAttrs('', { + colorAttr: 'u/v/w norm', + showScaleDflt: true, + editTypeOverride: 'calc' + }) +); var fromMesh3d = ['opacity', 'lightposition', 'lighting']; -fromMesh3d.forEach(function(k) { +fromMesh3d.forEach(function (k) { attrs[k] = mesh3dAttrs[k]; }); diff --git a/src/traces/cone/defaults.js b/src/traces/cone/defaults.js index 6aa4249d6ec..da9385fea39 100644 --- a/src/traces/cone/defaults.js +++ b/src/traces/cone/defaults.js @@ -18,9 +18,19 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var y = coerce('y'); var z = coerce('z'); - if( - !u || !u.length || !v || !v.length || !w || !w.length || - !x || !x.length || !y || !y.length || !z || !z.length + if ( + !u || + !u.length || + !v || + !v.length || + !w || + !w.length || + !x || + !x.length || + !y || + !y.length || + !z || + !z.length ) { traceOut.visible = false; return; @@ -40,7 +50,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('lightposition.y'); coerce('lightposition.z'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'c' }); coerce('text'); coerce('hovertext'); diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 191cfa7f17c..1c6be4e85b8 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -14,260 +14,249 @@ var filterOps = require('../../constants/filter_ops'); var COMPARISON_OPS2 = filterOps.COMPARISON_OPS2; var INTERVAL_OPS = filterOps.INTERVAL_OPS; - var scatterLineAttrs = scatterAttrs.line; -module.exports = extendFlat({ - z: heatmapAttrs.z, - x: heatmapAttrs.x, - x0: heatmapAttrs.x0, - dx: heatmapAttrs.dx, - y: heatmapAttrs.y, - y0: heatmapAttrs.y0, - dy: heatmapAttrs.dy, - - xperiod: heatmapAttrs.xperiod, - yperiod: heatmapAttrs.yperiod, - xperiod0: scatterAttrs.xperiod0, - yperiod0: scatterAttrs.yperiod0, - xperiodalignment: heatmapAttrs.xperiodalignment, - yperiodalignment: heatmapAttrs.yperiodalignment, - - text: heatmapAttrs.text, - hovertext: heatmapAttrs.hovertext, - transpose: heatmapAttrs.transpose, - xtype: heatmapAttrs.xtype, - ytype: heatmapAttrs.ytype, - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z', 1), - hovertemplate: heatmapAttrs.hovertemplate, - texttemplate: extendFlat({}, heatmapAttrs.texttemplate, { - description: [ - 'For this trace it only has an effect if `coloring` is set to *heatmap*.', - heatmapAttrs.texttemplate.description - ].join(' ') - }), - textfont: extendFlat({}, heatmapAttrs.textfont, { - description: [ - 'For this trace it only has an effect if `coloring` is set to *heatmap*.', - heatmapAttrs.textfont.description - ].join(' ') - }), - hoverongaps: heatmapAttrs.hoverongaps, - connectgaps: extendFlat({}, heatmapAttrs.connectgaps, { - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data are filled in.', - 'It is defaulted to true if `z` is a', - 'one dimensional array', - 'otherwise it is defaulted to false.' - ].join(' ') - }), +module.exports = extendFlat( + { + z: heatmapAttrs.z, + x: heatmapAttrs.x, + x0: heatmapAttrs.x0, + dx: heatmapAttrs.dx, + y: heatmapAttrs.y, + y0: heatmapAttrs.y0, + dy: heatmapAttrs.dy, - fillcolor: { - valType: 'color', - editType: 'calc', - description: [ - 'Sets the fill color if `contours.type` is *constraint*.', - 'Defaults to a half-transparent variant of the line color,', - 'marker color, or marker line color, whichever is available.' - ].join(' ') - }, + xperiod: heatmapAttrs.xperiod, + yperiod: heatmapAttrs.yperiod, + xperiod0: scatterAttrs.xperiod0, + yperiod0: scatterAttrs.yperiod0, + xperiodalignment: heatmapAttrs.xperiodalignment, + yperiodalignment: heatmapAttrs.yperiodalignment, - autocontour: { - valType: 'boolean', - dflt: true, - editType: 'calc', - impliedEdits: { - 'contours.start': undefined, - 'contours.end': undefined, - 'contours.size': undefined - }, - description: [ - 'Determines whether or not the contour level attributes are', - 'picked by an algorithm.', - 'If *true*, the number of contour levels can be set in `ncontours`.', - 'If *false*, set the contour level attributes in `contours`.' - ].join(' ') - }, - ncontours: { - valType: 'integer', - dflt: 15, - min: 1, - editType: 'calc', - description: [ - 'Sets the maximum number of contour levels. The actual number', - 'of contours will be chosen automatically to be less than or', - 'equal to the value of `ncontours`.', - 'Has an effect only if `autocontour` is *true* or if', - '`contours.size` is missing.' - ].join(' ') - }, - - contours: { - type: { - valType: 'enumerated', - values: ['levels', 'constraint'], - dflt: 'levels', - editType: 'calc', - description: [ - 'If `levels`, the data is represented as a contour plot with multiple', - 'levels displayed. If `constraint`, the data is represented as constraints', - 'with the invalid region shaded as specified by the `operation` and', - '`value` parameters.' - ].join(' ') - }, - start: { - valType: 'number', - dflt: null, - editType: 'plot', - impliedEdits: {'^autocontour': false}, + text: heatmapAttrs.text, + hovertext: heatmapAttrs.hovertext, + transpose: heatmapAttrs.transpose, + xtype: heatmapAttrs.xtype, + ytype: heatmapAttrs.ytype, + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z', 1), + hovertemplate: heatmapAttrs.hovertemplate, + texttemplate: extendFlat({}, heatmapAttrs.texttemplate, { description: [ - 'Sets the starting contour level value.', - 'Must be less than `contours.end`' + 'For this trace it only has an effect if `coloring` is set to *heatmap*.', + heatmapAttrs.texttemplate.description ].join(' ') - }, - end: { - valType: 'number', - dflt: null, - editType: 'plot', - impliedEdits: {'^autocontour': false}, + }), + textfont: extendFlat({}, heatmapAttrs.textfont, { description: [ - 'Sets the end contour level value.', - 'Must be more than `contours.start`' + 'For this trace it only has an effect if `coloring` is set to *heatmap*.', + heatmapAttrs.textfont.description ].join(' ') - }, - size: { - valType: 'number', - dflt: null, - min: 0, - editType: 'plot', - impliedEdits: {'^autocontour': false}, + }), + hoverongaps: heatmapAttrs.hoverongaps, + connectgaps: extendFlat({}, heatmapAttrs.connectgaps, { description: [ - 'Sets the step between each contour level.', - 'Must be positive.' + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.', + 'It is defaulted to true if `z` is a', + 'one dimensional array', + 'otherwise it is defaulted to false.' ].join(' ') - }, - coloring: { - valType: 'enumerated', - values: ['fill', 'heatmap', 'lines', 'none'], - dflt: 'fill', + }), + + fillcolor: { + valType: 'color', editType: 'calc', description: [ - 'Determines the coloring method showing the contour values.', - 'If *fill*, coloring is done evenly between each contour level', - 'If *heatmap*, a heatmap gradient coloring is applied', - 'between each contour level.', - 'If *lines*, coloring is done on the contour lines.', - 'If *none*, no coloring is applied on this trace.' + 'Sets the fill color if `contours.type` is *constraint*.', + 'Defaults to a half-transparent variant of the line color,', + 'marker color, or marker line color, whichever is available.' ].join(' ') }, - showlines: { + + autocontour: { valType: 'boolean', dflt: true, - editType: 'plot', + editType: 'calc', + impliedEdits: { + 'contours.start': undefined, + 'contours.end': undefined, + 'contours.size': undefined + }, description: [ - 'Determines whether or not the contour lines are drawn.', - 'Has an effect only if `contours.coloring` is set to *fill*.' + 'Determines whether or not the contour level attributes are', + 'picked by an algorithm.', + 'If *true*, the number of contour levels can be set in `ncontours`.', + 'If *false*, set the contour level attributes in `contours`.' ].join(' ') }, - showlabels: { - valType: 'boolean', - dflt: false, - editType: 'plot', + ncontours: { + valType: 'integer', + dflt: 15, + min: 1, + editType: 'calc', description: [ - 'Determines whether to label the contour lines with their values.' + 'Sets the maximum number of contour levels. The actual number', + 'of contours will be chosen automatically to be less than or', + 'equal to the value of `ncontours`.', + 'Has an effect only if `autocontour` is *true* or if', + '`contours.size` is missing.' ].join(' ') }, - labelfont: fontAttrs({ - editType: 'plot', - colorEditType: 'style', - description: [ - 'Sets the font used for labeling the contour levels.', - 'The default color comes from the lines, if shown.', - 'The default family and size come from `layout.font`.' - ].join(' '), - }), - labelformat: { - valType: 'string', - dflt: '', - editType: 'plot', - description: descriptionOnlyNumbers('contour label') - }, - operation: { - valType: 'enumerated', - values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS), - dflt: '=', - editType: 'calc', - description: [ - 'Sets the constraint operation.', - '*=* keeps regions equal to `value`', + contours: { + type: { + valType: 'enumerated', + values: ['levels', 'constraint'], + dflt: 'levels', + editType: 'calc', + description: [ + 'If `levels`, the data is represented as a contour plot with multiple', + 'levels displayed. If `constraint`, the data is represented as constraints', + 'with the invalid region shaded as specified by the `operation` and', + '`value` parameters.' + ].join(' ') + }, + start: { + valType: 'number', + dflt: null, + editType: 'plot', + impliedEdits: { '^autocontour': false }, + description: ['Sets the starting contour level value.', 'Must be less than `contours.end`'].join(' ') + }, + end: { + valType: 'number', + dflt: null, + editType: 'plot', + impliedEdits: { '^autocontour': false }, + description: ['Sets the end contour level value.', 'Must be more than `contours.start`'].join(' ') + }, + size: { + valType: 'number', + dflt: null, + min: 0, + editType: 'plot', + impliedEdits: { '^autocontour': false }, + description: ['Sets the step between each contour level.', 'Must be positive.'].join(' ') + }, + coloring: { + valType: 'enumerated', + values: ['fill', 'heatmap', 'lines', 'none'], + dflt: 'fill', + editType: 'calc', + description: [ + 'Determines the coloring method showing the contour values.', + 'If *fill*, coloring is done evenly between each contour level', + 'If *heatmap*, a heatmap gradient coloring is applied', + 'between each contour level.', + 'If *lines*, coloring is done on the contour lines.', + 'If *none*, no coloring is applied on this trace.' + ].join(' ') + }, + showlines: { + valType: 'boolean', + dflt: true, + editType: 'plot', + description: [ + 'Determines whether or not the contour lines are drawn.', + 'Has an effect only if `contours.coloring` is set to *fill*.' + ].join(' ') + }, + showlabels: { + valType: 'boolean', + dflt: false, + editType: 'plot', + description: ['Determines whether to label the contour lines with their values.'].join(' ') + }, + labelfont: fontAttrs({ + editType: 'plot', + colorEditType: 'style', + description: [ + 'Sets the font used for labeling the contour levels.', + 'The default color comes from the lines, if shown.', + 'The default family and size come from `layout.font`.' + ].join(' ') + }), + labelformat: { + valType: 'string', + dflt: '', + editType: 'plot', + description: descriptionOnlyNumbers('contour label') + }, + operation: { + valType: 'enumerated', + values: [].concat(COMPARISON_OPS2).concat(INTERVAL_OPS), + dflt: '=', + editType: 'calc', + description: [ + 'Sets the constraint operation.', - '*<* and *<=* keep regions less than `value`', + '*=* keeps regions equal to `value`', - '*>* and *>=* keep regions greater than `value`', + '*<* and *<=* keep regions less than `value`', - '*[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]`', + '*>* and *>=* keep regions greater than `value`', - '*][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]`', + '*[]*, *()*, *[)*, and *(]* keep regions inside `value[0]` to `value[1]`', - 'Open vs. closed intervals make no difference to constraint display, but', - 'all versions are allowed for consistency with filter transforms.' - ].join(' ') - }, - value: { - valType: 'any', - dflt: 0, - editType: 'calc', - description: [ - 'Sets the value or values of the constraint boundary.', + '*][*, *)(*, *](*, *)[* keep regions outside `value[0]` to value[1]`', - 'When `operation` is set to one of the comparison values', - '(`' + COMPARISON_OPS2 + '`)', - '*value* is expected to be a number.', + 'Open vs. closed intervals make no difference to constraint display, but', + 'all versions are allowed for consistency with filter transforms.' + ].join(' ') + }, + value: { + valType: 'any', + dflt: 0, + editType: 'calc', + description: [ + 'Sets the value or values of the constraint boundary.', - 'When `operation` is set to one of the interval values', - '(`' + INTERVAL_OPS + '`)', - '*value* is expected to be an array of two numbers where the first', - 'is the lower bound and the second is the upper bound.', - ].join(' ') + 'When `operation` is set to one of the comparison values', + '(`' + COMPARISON_OPS2 + '`)', + '*value* is expected to be a number.', + + 'When `operation` is set to one of the interval values', + '(`' + INTERVAL_OPS + '`)', + '*value* is expected to be an array of two numbers where the first', + 'is the lower bound and the second is the upper bound.' + ].join(' ') + }, + editType: 'calc', + impliedEdits: { autocontour: false } }, - editType: 'calc', - impliedEdits: {autocontour: false} - }, - line: { - color: extendFlat({}, scatterLineAttrs.color, { - editType: 'style+colorbars', - description: [ - 'Sets the color of the contour level.', - 'Has no effect if `contours.coloring` is set to *lines*.' - ].join(' ') - }), - width: { - valType: 'number', - min: 0, - editType: 'style+colorbars', - description: [ - 'Sets the contour line width in (in px)', - 'Defaults to *0.5* when `contours.type` is *levels*.', - 'Defaults to *2* when `contour.type` is *constraint*.' - ].join(' ') + line: { + color: extendFlat({}, scatterLineAttrs.color, { + editType: 'style+colorbars', + description: [ + 'Sets the color of the contour level.', + 'Has no effect if `contours.coloring` is set to *lines*.' + ].join(' ') + }), + width: { + valType: 'number', + min: 0, + editType: 'style+colorbars', + description: [ + 'Sets the contour line width in (in px)', + 'Defaults to *0.5* when `contours.type` is *levels*.', + 'Defaults to *2* when `contour.type` is *constraint*.' + ].join(' ') + }, + dash: dash, + smoothing: extendFlat({}, scatterLineAttrs.smoothing, { + description: [ + 'Sets the amount of smoothing for the contour lines,', + 'where *0* corresponds to no smoothing.' + ].join(' ') + }), + editType: 'plot' }, - dash: dash, - smoothing: extendFlat({}, scatterLineAttrs.smoothing, { - description: [ - 'Sets the amount of smoothing for the contour lines,', - 'where *0* corresponds to no smoothing.' - ].join(' ') - }), - editType: 'plot' + zorder: scatterAttrs.zorder }, - zorder: scatterAttrs.zorder -}, colorScaleAttrs('', { cLetter: 'z', autoColorDflt: false, diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js index 0d45eca75e6..aced53a1de1 100644 --- a/src/traces/contour/defaults.js +++ b/src/traces/contour/defaults.js @@ -10,7 +10,6 @@ var handleStyleDefaults = require('./style_defaults'); var handleHeatmapLabelDefaults = require('../heatmap/label_defaults'); var attributes = require('./attributes'); - module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); @@ -21,7 +20,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -35,20 +34,17 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hoverongaps'); coerce('hovertemplate'); - var isConstraint = (coerce('contours.type') === 'constraint'); + var isConstraint = coerce('contours.type') === 'constraint'; coerce('connectgaps', Lib.isArray1D(traceOut.z)); - if(isConstraint) { + if (isConstraint) { handleConstraintDefaults(traceIn, traceOut, coerce, layout, defaultColor); } else { handleContoursDefaults(traceIn, traceOut, coerce, coerce2); handleStyleDefaults(traceIn, traceOut, coerce, layout); } - if( - traceOut.contours && - traceOut.contours.coloring === 'heatmap' - ) { + if (traceOut.contours && traceOut.contours.coloring === 'heatmap') { handleHeatmapLabelDefaults(coerce, layout); } coerce('zorder'); diff --git a/src/traces/densitymap/attributes.js b/src/traces/densitymap/attributes.js index a9daac8f7f5..11088da3350 100644 --- a/src/traces/densitymap/attributes.js +++ b/src/traces/densitymap/attributes.js @@ -29,54 +29,55 @@ var extendFlat = require('../../lib/extend').extendFlat; * */ -module.exports = extendFlat({ - lon: scatterMapAttrs.lon, - lat: scatterMapAttrs.lat, +module.exports = extendFlat( + { + lon: scatterMapAttrs.lon, + lat: scatterMapAttrs.lat, - z: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the points\' weight.', - 'For example, a value of 10 would be equivalent to having 10 points of weight 1', - 'in the same spot' - ].join(' ') - }, + z: { + valType: 'data_array', + editType: 'calc', + description: [ + "Sets the points' weight.", + 'For example, a value of 10 would be equivalent to having 10 points of weight 1', + 'in the same spot' + ].join(' ') + }, - radius: { - valType: 'number', - editType: 'plot', - arrayOk: true, - min: 1, - dflt: 30, - description: [ - 'Sets the radius of influence of one `lon` / `lat` point in pixels.', - 'Increasing the value makes the densitymap trace smoother, but less detailed.' - ].join(' ') - }, + radius: { + valType: 'number', + editType: 'plot', + arrayOk: true, + min: 1, + dflt: 30, + description: [ + 'Sets the radius of influence of one `lon` / `lat` point in pixels.', + 'Increasing the value makes the densitymap trace smoother, but less detailed.' + ].join(' ') + }, - below: { - valType: 'string', - editType: 'plot', - description: [ - 'Determines if the densitymap trace will be inserted', - 'before the layer with the specified ID.', - 'By default, densitymap traces are placed below the first', - 'layer of type symbol', - 'If set to \'\',', - 'the layer will be inserted above every existing layer.' - ].join(' ') - }, + below: { + valType: 'string', + editType: 'plot', + description: [ + 'Determines if the densitymap trace will be inserted', + 'before the layer with the specified ID.', + 'By default, densitymap traces are placed below the first', + 'layer of type symbol', + "If set to '',", + 'the layer will be inserted above every existing layer.' + ].join(' ') + }, - text: scatterMapAttrs.text, - hovertext: scatterMapAttrs.hovertext, + text: scatterMapAttrs.text, + hovertext: scatterMapAttrs.hovertext, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: ['lon', 'lat', 'z', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: ['lon', 'lat', 'z', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs(), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, colorScaleAttrs('', { cLetter: 'z', editTypeOverride: 'calc' diff --git a/src/traces/densitymap/defaults.js b/src/traces/densitymap/defaults.js index 67a467ff59d..38652d515a0 100644 --- a/src/traces/densitymap/defaults.js +++ b/src/traces/densitymap/defaults.js @@ -13,7 +13,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var lat = coerce('lat') || []; var len = Math.min(lon.length, lat.length); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -28,5 +28,5 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hovertemplate'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/densitymapbox/attributes.js b/src/traces/densitymapbox/attributes.js index 7de62ad55ae..8e21730551f 100644 --- a/src/traces/densitymapbox/attributes.js +++ b/src/traces/densitymapbox/attributes.js @@ -29,54 +29,55 @@ var extendFlat = require('../../lib/extend').extendFlat; * */ -module.exports = extendFlat({ - lon: scatterMapboxAttrs.lon, - lat: scatterMapboxAttrs.lat, +module.exports = extendFlat( + { + lon: scatterMapboxAttrs.lon, + lat: scatterMapboxAttrs.lat, - z: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the points\' weight.', - 'For example, a value of 10 would be equivalent to having 10 points of weight 1', - 'in the same spot' - ].join(' ') - }, + z: { + valType: 'data_array', + editType: 'calc', + description: [ + "Sets the points' weight.", + 'For example, a value of 10 would be equivalent to having 10 points of weight 1', + 'in the same spot' + ].join(' ') + }, - radius: { - valType: 'number', - editType: 'plot', - arrayOk: true, - min: 1, - dflt: 30, - description: [ - 'Sets the radius of influence of one `lon` / `lat` point in pixels.', - 'Increasing the value makes the densitymapbox trace smoother, but less detailed.' - ].join(' ') - }, + radius: { + valType: 'number', + editType: 'plot', + arrayOk: true, + min: 1, + dflt: 30, + description: [ + 'Sets the radius of influence of one `lon` / `lat` point in pixels.', + 'Increasing the value makes the densitymapbox trace smoother, but less detailed.' + ].join(' ') + }, - below: { - valType: 'string', - editType: 'plot', - description: [ - 'Determines if the densitymapbox trace will be inserted', - 'before the layer with the specified ID.', - 'By default, densitymapbox traces are placed below the first', - 'layer of type symbol', - 'If set to \'\',', - 'the layer will be inserted above every existing layer.' - ].join(' ') - }, + below: { + valType: 'string', + editType: 'plot', + description: [ + 'Determines if the densitymapbox trace will be inserted', + 'before the layer with the specified ID.', + 'By default, densitymapbox traces are placed below the first', + 'layer of type symbol', + "If set to '',", + 'the layer will be inserted above every existing layer.' + ].join(' ') + }, - text: scatterMapboxAttrs.text, - hovertext: scatterMapboxAttrs.hovertext, + text: scatterMapboxAttrs.text, + hovertext: scatterMapboxAttrs.hovertext, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: ['lon', 'lat', 'z', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: ['lon', 'lat', 'z', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs(), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, colorScaleAttrs('', { cLetter: 'z', editTypeOverride: 'calc' diff --git a/src/traces/densitymapbox/defaults.js b/src/traces/densitymapbox/defaults.js index 67a467ff59d..38652d515a0 100644 --- a/src/traces/densitymapbox/defaults.js +++ b/src/traces/densitymapbox/defaults.js @@ -13,7 +13,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var lat = coerce('lat') || []; var len = Math.min(lon.length, lat.length); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -28,5 +28,5 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hovertemplate'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/funnel/attributes.js b/src/traces/funnel/attributes.js index 86a52007b13..19b04dc76d7 100644 --- a/src/traces/funnel/attributes.js +++ b/src/traces/funnel/attributes.js @@ -28,9 +28,12 @@ module.exports = { yhoverformat: axisHoverFormat('y'), hovertext: barAttrs.hovertext, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['name', 'x', 'y', 'text', 'percent initial', 'percent previous', 'percent total'] @@ -49,14 +52,17 @@ module.exports = { ].join(' ') }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys.concat(['label', 'value']) - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys.concat(['label', 'value']) + } + ), text: barAttrs.text, textposition: barAttrs.textposition, - insidetextanchor: extendFlat({}, barAttrs.insidetextanchor, {dflt: 'middle'}), - textangle: extendFlat({}, barAttrs.textangle, {dflt: 0}), + insidetextanchor: extendFlat({}, barAttrs.insidetextanchor, { dflt: 'middle' }), + textangle: extendFlat({}, barAttrs.textangle, { dflt: 0 }), textfont: barAttrs.textfont, insidetextfont: barAttrs.insidetextfont, outsidetextfont: barAttrs.outsidetextfont, @@ -70,13 +76,13 @@ module.exports = { 'along the vertical (horizontal).', 'By default funnels are tend to be oriented horizontally;', 'unless only *y* array is presented or orientation is set to *v*.', - 'Also regarding graphs including only \'horizontal\' funnels,', + "Also regarding graphs including only 'horizontal' funnels,", '*autorange* on the *y-axis* are set to *reversed*.' ].join(' ') }), - offset: extendFlat({}, barAttrs.offset, {arrayOk: false}), - width: extendFlat({}, barAttrs.width, {arrayOk: false}), + offset: extendFlat({}, barAttrs.offset, { arrayOk: false }), + width: extendFlat({}, barAttrs.width, { arrayOk: false }), marker: funnelMarker(), @@ -84,15 +90,13 @@ module.exports = { fillcolor: { valType: 'color', editType: 'style', - description: [ - 'Sets the fill color.' - ].join(' ') + description: ['Sets the fill color.'].join(' ') }, line: { - color: extendFlat({}, lineAttrs.color, {dflt: Color.defaultLine}), + color: extendFlat({}, lineAttrs.color, { dflt: Color.defaultLine }), width: extendFlat({}, lineAttrs.width, { dflt: 0, - editType: 'plot', + editType: 'plot' }), dash: lineAttrs.dash, editType: 'style' @@ -101,9 +105,7 @@ module.exports = { valType: 'boolean', dflt: true, editType: 'plot', - description: [ - 'Determines if connector regions and lines are drawn.' - ].join(' ') + description: ['Determines if connector regions and lines are drawn.'].join(' ') }, editType: 'plot' }, diff --git a/src/traces/funnel/defaults.js b/src/traces/funnel/defaults.js index 02a7ad47efa..a2f6c79a503 100644 --- a/src/traces/funnel/defaults.js +++ b/src/traces/funnel/defaults.js @@ -15,7 +15,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { } var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -24,7 +24,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('xhoverformat'); coerce('yhoverformat'); - coerce('orientation', (traceOut.y && !traceOut.x) ? 'v' : 'h'); + coerce('orientation', traceOut.y && !traceOut.x ? 'v' : 'h'); coerce('offset'); coerce('width'); @@ -43,7 +43,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { moduleHasInsideanchor: true }); - if(traceOut.textposition !== 'none' && !traceOut.texttemplate) { + if (traceOut.textposition !== 'none' && !traceOut.texttemplate) { coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+value' : 'value'); } @@ -52,11 +52,11 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('marker.line.width'); var connectorVisible = coerce('connector.visible'); - if(connectorVisible) { + if (connectorVisible) { coerce('connector.fillcolor', defaultFillColor(markerColor)); var connectorLineWidth = coerce('connector.line.width'); - if(connectorLineWidth) { + if (connectorLineWidth) { coerce('connector.line.color'); coerce('connector.line.dash'); } @@ -77,9 +77,9 @@ function crossTraceDefaults(fullData, fullLayout) { return Lib.coerce(traceOut._input, traceOut, attributes, attr); } - for(var i = 0; i < fullData.length; i++) { + for (var i = 0; i < fullData.length; i++) { traceOut = fullData[i]; - if(traceOut.type === 'funnel') { + if (traceOut.type === 'funnel') { traceIn = traceOut._input; handleGroupingDefaults(traceIn, traceOut, fullLayout, coerce, fullLayout.funnelmode); } diff --git a/src/traces/funnelarea/attributes.js b/src/traces/funnelarea/attributes.js index a95c6d2969b..ff0024bd866 100644 --- a/src/traces/funnelarea/attributes.js +++ b/src/traces/funnelarea/attributes.js @@ -25,7 +25,7 @@ module.exports = { 'Defaults to the `paper_bgcolor` value.' ].join(' ') }), - width: extendFlat({}, pieAttrs.marker.line.width, {dflt: 1}), + width: extendFlat({}, pieAttrs.marker.line.width, { dflt: 1 }), editType: 'calc' }, pattern: pieAttrs.marker.pattern, @@ -47,17 +47,23 @@ module.exports = { flags: ['label', 'text', 'value', 'percent'] }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['label', 'color', 'value', 'text', 'percent'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['label', 'color', 'value', 'text', 'percent'] + } + ), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: hovertemplateAttrs({}, { - keys: ['label', 'color', 'value', 'text', 'percent'] - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: ['label', 'color', 'value', 'text', 'percent'] + } + ), textposition: extendFlat({}, pieAttrs.textposition, { values: ['inside', 'none'], @@ -77,16 +83,14 @@ module.exports = { editType: 'plot' }, - domain: domainAttrs({name: 'funnelarea', trace: true, editType: 'calc'}), + domain: domainAttrs({ name: 'funnelarea', trace: true, editType: 'calc' }), aspectratio: { valType: 'number', min: 0, dflt: 1, editType: 'plot', - description: [ - 'Sets the ratio between height and width' - ].join(' ') + description: ['Sets the ratio between height and width'].join(' ') }, baseratio: { @@ -95,8 +99,6 @@ module.exports = { max: 1, dflt: 0.333, editType: 'plot', - description: [ - 'Sets the ratio between bottom length and maximum top length.' - ].join(' ') + description: ['Sets the ratio between bottom length and maximum top length.'].join(' ') } }; diff --git a/src/traces/funnelarea/defaults.js b/src/traces/funnelarea/defaults.js index 9067c6f2542..dbf132044ba 100644 --- a/src/traces/funnelarea/defaults.js +++ b/src/traces/funnelarea/defaults.js @@ -20,14 +20,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout traceOut._hasLabels = res.hasLabels; traceOut._hasValues = res.hasValues; - if(!traceOut._hasLabels && - traceOut._hasValues - ) { + if (!traceOut._hasLabels && traceOut._hasValues) { coerce('label0'); coerce('dlabel'); } - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -40,12 +38,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var textData = coerce('text'); var textTemplate = coerce('texttemplate'); var textInfo; - if(!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); + if (!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); - if(textTemplate || (textInfo && textInfo !== 'none')) { + if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { moduleHasSelected: false, @@ -55,14 +53,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout moduleHasTextangle: false, moduleHasInsideanchor: false }); - } else if(textInfo === 'none') { + } else if (textInfo === 'none') { coerce('textposition', 'none'); } handleDomainDefaults(traceOut, layout, coerce); var title = coerce('title.text'); - if(title) { + if (title) { coerce('title.position'); Lib.coerceFont(coerce, 'title.font', layout.font); } diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 60cd9204297..08433890ed0 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -10,130 +10,132 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({ - z: { - valType: 'data_array', - editType: 'calc', - description: 'Sets the z data.' - }, - x: extendFlat({}, scatterAttrs.x, {impliedEdits: {xtype: 'array'}}), - x0: extendFlat({}, scatterAttrs.x0, {impliedEdits: {xtype: 'scaled'}}), - dx: extendFlat({}, scatterAttrs.dx, {impliedEdits: {xtype: 'scaled'}}), - y: extendFlat({}, scatterAttrs.y, {impliedEdits: {ytype: 'array'}}), - y0: extendFlat({}, scatterAttrs.y0, {impliedEdits: {ytype: 'scaled'}}), - dy: extendFlat({}, scatterAttrs.dy, {impliedEdits: {ytype: 'scaled'}}), +module.exports = extendFlat( + { + z: { + valType: 'data_array', + editType: 'calc', + description: 'Sets the z data.' + }, + x: extendFlat({}, scatterAttrs.x, { impliedEdits: { xtype: 'array' } }), + x0: extendFlat({}, scatterAttrs.x0, { impliedEdits: { xtype: 'scaled' } }), + dx: extendFlat({}, scatterAttrs.dx, { impliedEdits: { xtype: 'scaled' } }), + y: extendFlat({}, scatterAttrs.y, { impliedEdits: { ytype: 'array' } }), + y0: extendFlat({}, scatterAttrs.y0, { impliedEdits: { ytype: 'scaled' } }), + dy: extendFlat({}, scatterAttrs.dy, { impliedEdits: { ytype: 'scaled' } }), - xperiod: extendFlat({}, scatterAttrs.xperiod, {impliedEdits: {xtype: 'scaled'}}), - yperiod: extendFlat({}, scatterAttrs.yperiod, {impliedEdits: {ytype: 'scaled'}}), - xperiod0: extendFlat({}, scatterAttrs.xperiod0, {impliedEdits: {xtype: 'scaled'}}), - yperiod0: extendFlat({}, scatterAttrs.yperiod0, {impliedEdits: {ytype: 'scaled'}}), - xperiodalignment: extendFlat({}, scatterAttrs.xperiodalignment, {impliedEdits: {xtype: 'scaled'}}), - yperiodalignment: extendFlat({}, scatterAttrs.yperiodalignment, {impliedEdits: {ytype: 'scaled'}}), + xperiod: extendFlat({}, scatterAttrs.xperiod, { impliedEdits: { xtype: 'scaled' } }), + yperiod: extendFlat({}, scatterAttrs.yperiod, { impliedEdits: { ytype: 'scaled' } }), + xperiod0: extendFlat({}, scatterAttrs.xperiod0, { impliedEdits: { xtype: 'scaled' } }), + yperiod0: extendFlat({}, scatterAttrs.yperiod0, { impliedEdits: { ytype: 'scaled' } }), + xperiodalignment: extendFlat({}, scatterAttrs.xperiodalignment, { impliedEdits: { xtype: 'scaled' } }), + yperiodalignment: extendFlat({}, scatterAttrs.yperiodalignment, { impliedEdits: { ytype: 'scaled' } }), - text: { - valType: 'data_array', - editType: 'calc', - description: 'Sets the text elements associated with each z value.' - }, - hovertext: { - valType: 'data_array', - editType: 'calc', - description: 'Same as `text`.' - }, - transpose: { - valType: 'boolean', - dflt: false, - editType: 'calc', - description: 'Transposes the z data.' - }, - xtype: { - valType: 'enumerated', - values: ['array', 'scaled'], - editType: 'calc+clearAxisTypes', - description: [ - 'If *array*, the heatmap\'s x coordinates are given by *x*', - '(the default behavior when `x` is provided).', - 'If *scaled*, the heatmap\'s x coordinates are given by *x0* and *dx*', - '(the default behavior when `x` is not provided).' - ].join(' ') - }, - ytype: { - valType: 'enumerated', - values: ['array', 'scaled'], - editType: 'calc+clearAxisTypes', - description: [ - 'If *array*, the heatmap\'s y coordinates are given by *y*', - '(the default behavior when `y` is provided)', - 'If *scaled*, the heatmap\'s y coordinates are given by *y0* and *dy*', - '(the default behavior when `y` is not provided)' - ].join(' ') - }, - zsmooth: { - valType: 'enumerated', - values: ['fast', 'best', false], - dflt: false, - editType: 'calc', - description: [ - 'Picks a smoothing algorithm use to smooth `z` data.' - ].join(' ') - }, - hoverongaps: { - valType: 'boolean', - dflt: true, - editType: 'none', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data have hover labels associated with them.' - ].join(' ') - }, - connectgaps: { - valType: 'boolean', - editType: 'calc', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data are filled in.', - 'It is defaulted to true if `z` is a', - 'one dimensional array and `zsmooth` is not false;', - 'otherwise it is defaulted to false.' - ].join(' ') - }, - xgap: { - valType: 'number', - dflt: 0, - min: 0, - editType: 'plot', - description: 'Sets the horizontal gap (in pixels) between bricks.' - }, - ygap: { - valType: 'number', - dflt: 0, - min: 0, - editType: 'plot', - description: 'Sets the vertical gap (in pixels) between bricks.' - }, - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z', 1), + text: { + valType: 'data_array', + editType: 'calc', + description: 'Sets the text elements associated with each z value.' + }, + hovertext: { + valType: 'data_array', + editType: 'calc', + description: 'Same as `text`.' + }, + transpose: { + valType: 'boolean', + dflt: false, + editType: 'calc', + description: 'Transposes the z data.' + }, + xtype: { + valType: 'enumerated', + values: ['array', 'scaled'], + editType: 'calc+clearAxisTypes', + description: [ + "If *array*, the heatmap's x coordinates are given by *x*", + '(the default behavior when `x` is provided).', + "If *scaled*, the heatmap's x coordinates are given by *x0* and *dx*", + '(the default behavior when `x` is not provided).' + ].join(' ') + }, + ytype: { + valType: 'enumerated', + values: ['array', 'scaled'], + editType: 'calc+clearAxisTypes', + description: [ + "If *array*, the heatmap's y coordinates are given by *y*", + '(the default behavior when `y` is provided)', + "If *scaled*, the heatmap's y coordinates are given by *y0* and *dy*", + '(the default behavior when `y` is not provided)' + ].join(' ') + }, + zsmooth: { + valType: 'enumerated', + values: ['fast', 'best', false], + dflt: false, + editType: 'calc', + description: ['Picks a smoothing algorithm use to smooth `z` data.'].join(' ') + }, + hoverongaps: { + valType: 'boolean', + dflt: true, + editType: 'none', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data have hover labels associated with them.' + ].join(' ') + }, + connectgaps: { + valType: 'boolean', + editType: 'calc', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.', + 'It is defaulted to true if `z` is a', + 'one dimensional array and `zsmooth` is not false;', + 'otherwise it is defaulted to false.' + ].join(' ') + }, + xgap: { + valType: 'number', + dflt: 0, + min: 0, + editType: 'plot', + description: 'Sets the horizontal gap (in pixels) between bricks.' + }, + ygap: { + valType: 'number', + dflt: 0, + min: 0, + editType: 'plot', + description: 'Sets the vertical gap (in pixels) between bricks.' + }, + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z', 1), - hovertemplate: hovertemplateAttrs(), - texttemplate: texttemplateAttrs({ - arrayOk: false, - editType: 'plot' - }, { - keys: ['x', 'y', 'z', 'text'] - }), - textfont: fontAttrs({ - editType: 'plot', - autoSize: true, - autoColor: true, - colorEditType: 'style', - description: 'Sets the text font.' - }), + hovertemplate: hovertemplateAttrs(), + texttemplate: texttemplateAttrs( + { + arrayOk: false, + editType: 'plot' + }, + { + keys: ['x', 'y', 'z', 'text'] + } + ), + textfont: fontAttrs({ + editType: 'plot', + autoSize: true, + autoColor: true, + colorEditType: 'style', + description: 'Sets the text font.' + }), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}), - zorder: scatterAttrs.zorder -}, - colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false}) + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }), + zorder: scatterAttrs.zorder + }, + colorScaleAttrs('', { cLetter: 'z', autoColorDflt: false }) ); diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js index 7387b005623..8cd3d5ba0ea 100644 --- a/src/traces/heatmap/defaults.js +++ b/src/traces/heatmap/defaults.js @@ -9,14 +9,13 @@ var handleStyleDefaults = require('./style_defaults'); var colorscaleDefaults = require('../../components/colorscale/defaults'); var attributes = require('./attributes'); - module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } var validData = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!validData) { + if (!validData) { traceOut.visible = false; return; } @@ -33,8 +32,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleStyleDefaults(traceIn, traceOut, coerce, layout); coerce('hoverongaps'); - coerce('connectgaps', Lib.isArray1D(traceOut.z) && (traceOut.zsmooth !== false)); + coerce('connectgaps', Lib.isArray1D(traceOut.z) && traceOut.zsmooth !== false); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); coerce('zorder'); }; diff --git a/src/traces/heatmap/plot.js b/src/traces/heatmap/plot.js index 2db0d0eb3b2..415f9183a75 100644 --- a/src/traces/heatmap/plot.js +++ b/src/traces/heatmap/plot.js @@ -28,11 +28,11 @@ function removeLabels(plotGroup) { selectLabels(plotGroup).remove(); } -module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { +module.exports = function (gd, plotinfo, cdheatmaps, heatmapLayer) { var xa = plotinfo.xaxis; var ya = plotinfo.yaxis; - Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function(cd) { + Lib.makeTraceGroups(heatmapLayer, cdheatmaps, 'hm').each(function (cd) { var plotGroup = d3.select(this); var cd0 = cd[0]; var trace = cd0.trace; @@ -67,17 +67,17 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { // these while loops find the first and last brick bounds that are defined // (in case of log of a negative) i = 0; - while(left === undefined && i < x.length - 1) { + while (left === undefined && i < x.length - 1) { left = xa.c2p(x[i]); i++; } i = x.length - 1; - while(right === undefined && i > 0) { + while (right === undefined && i > 0) { right = xa.c2p(x[i]); i--; } - if(right < left) { + if (right < left) { temp = right; right = left; left = temp; @@ -85,17 +85,17 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { } i = 0; - while(top === undefined && i < y.length - 1) { + while (top === undefined && i < y.length - 1) { top = ya.c2p(y[i]); i++; } i = y.length - 1; - while(bottom === undefined && i > 0) { + while (bottom === undefined && i > 0) { bottom = ya.c2p(y[i]); i--; } - if(bottom < top) { + if (bottom < top) { temp = top; top = bottom; bottom = temp; @@ -104,7 +104,7 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { // for contours with heatmap fill, we generate the boundaries based on // brick centers but then use the brick edges for drawing the bricks - if(isContour) { + if (isContour) { xc = x; yc = y; x = cd0.xfill; @@ -112,9 +112,9 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { } var drawingMethod = 'default'; - if(zsmooth) { + if (zsmooth) { drawingMethod = zsmooth === 'best' ? 'smooth' : 'fast'; - } else if(trace._islinear && xGap === 0 && yGap === 0 && supportsPixelatedImage()) { + } else if (trace._islinear && xGap === 0 && yGap === 0 && supportsPixelatedImage()) { drawingMethod = 'fast'; } @@ -122,7 +122,7 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { // time reasonable when you zoom in. if drawingMethod is fast, don't worry // about this, because zooming doesn't increase number of pixels // if zsmooth is best, don't include anything off screen because it takes too long - if(drawingMethod !== 'fast') { + if (drawingMethod !== 'fast') { var extra = zsmooth === 'best' ? 0 : 0.5; left = Math.max(-extra * xa._length, left); right = Math.min((1 + extra) * xa._length, right); @@ -136,11 +136,9 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { // setup image nodes // if image is entirely off-screen, don't even draw it - var isOffScreen = ( - left >= xa._length || right <= 0 || top >= ya._length || bottom <= 0 - ); + var isOffScreen = left >= xa._length || right <= 0 || top >= ya._length || bottom <= 0; - if(isOffScreen) { + if (isOffScreen) { var noImage = plotGroup.selectAll('image').data([]); noImage.exit().remove(); @@ -151,7 +149,7 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { // generate image data var canvasW, canvasH; - if(drawingMethod === 'fast') { + if (drawingMethod === 'fast') { canvasW = n; canvasH = m; } else { @@ -162,28 +160,29 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var canvas = document.createElement('canvas'); canvas.width = canvasW; canvas.height = canvasH; - var context = canvas.getContext('2d', {willReadFrequently: true}); + var context = canvas.getContext('2d', { willReadFrequently: true }); - var sclFunc = makeColorScaleFuncFromTrace(trace, {noNumericCheck: true, returnArray: true}); + var sclFunc = makeColorScaleFuncFromTrace(trace, { noNumericCheck: true, returnArray: true }); // map brick boundaries to image pixels - var xpx, - ypx; - if(drawingMethod === 'fast') { - xpx = xrev ? - function(index) { return n - 1 - index; } : - Lib.identity; - ypx = yrev ? - function(index) { return m - 1 - index; } : - Lib.identity; + var xpx, ypx; + if (drawingMethod === 'fast') { + xpx = xrev + ? function (index) { + return n - 1 - index; + } + : Lib.identity; + ypx = yrev + ? function (index) { + return m - 1 - index; + } + : Lib.identity; } else { - xpx = function(index) { - return Lib.constrain(Math.round(xa.c2p(x[index]) - left), - 0, imageWidth); + xpx = function (index) { + return Lib.constrain(Math.round(xa.c2p(x[index]) - left), 0, imageWidth); }; - ypx = function(index) { - return Lib.constrain(Math.round(ya.c2p(y[index]) - top), - 0, imageHeight); + ypx = function (index) { + return Lib.constrain(Math.round(ya.c2p(y[index]) - top), 0, imageHeight); }; } @@ -203,7 +202,7 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var xb, xi, v, row, c; function setColor(v, pixsize) { - if(v !== undefined) { + if (v !== undefined) { var c = sclFunc(v); c[0] = Math.round(c[0]); c[1] = Math.round(c[1]); @@ -220,43 +219,45 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { function interpColor(r0, r1, xinterp, yinterp) { var z00 = r0[xinterp.bin0]; - if(z00 === undefined) return setColor(undefined, 1); + if (z00 === undefined) return setColor(undefined, 1); var z01 = r0[xinterp.bin1]; var z10 = r1[xinterp.bin0]; var z11 = r1[xinterp.bin1]; - var dx = (z01 - z00) || 0; - var dy = (z10 - z00) || 0; + var dx = z01 - z00 || 0; + var dy = z10 - z00 || 0; var dxy; // the bilinear interpolation term needs different calculations // for all the different permutations of missing data // among the neighbors of the main point, to ensure // continuity across brick boundaries. - if(z01 === undefined) { - if(z11 === undefined) dxy = 0; - else if(z10 === undefined) dxy = 2 * (z11 - z00); - else dxy = (2 * z11 - z10 - z00) * 2 / 3; - } else if(z11 === undefined) { - if(z10 === undefined) dxy = 0; - else dxy = (2 * z00 - z01 - z10) * 2 / 3; - } else if(z10 === undefined) dxy = (2 * z11 - z01 - z00) * 2 / 3; - else dxy = (z11 + z00 - z01 - z10); + if (z01 === undefined) { + if (z11 === undefined) dxy = 0; + else if (z10 === undefined) dxy = 2 * (z11 - z00); + else dxy = ((2 * z11 - z10 - z00) * 2) / 3; + } else if (z11 === undefined) { + if (z10 === undefined) dxy = 0; + else dxy = ((2 * z00 - z01 - z10) * 2) / 3; + } else if (z10 === undefined) dxy = ((2 * z11 - z01 - z00) * 2) / 3; + else dxy = z11 + z00 - z01 - z10; return setColor(z00 + xinterp.frac * dx + yinterp.frac * (dy + xinterp.frac * dxy)); } - if(drawingMethod !== 'default') { // works fastest with imageData + if (drawingMethod !== 'default') { + // works fastest with imageData var pxIndex = 0; var pixels; try { pixels = new Uint8Array(canvasW * canvasH * 4); - } catch(e) { + } catch (e) { pixels = new Array(canvasW * canvasH * 4); } - if(drawingMethod === 'smooth') { // zsmooth="best" + if (drawingMethod === 'smooth') { + // zsmooth="best" var xForPx = xc || x; var yForPx = yc || y; var xPixArray = new Array(xForPx.length); @@ -267,28 +268,29 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var yinterp, r0, r1; // first make arrays of x and y pixel locations of brick boundaries - for(i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left); - for(i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top); + for (i = 0; i < xForPx.length; i++) xPixArray[i] = Math.round(xa.c2p(xForPx[i]) - left); + for (i = 0; i < yForPx.length; i++) yPixArray[i] = Math.round(ya.c2p(yForPx[i]) - top); // then make arrays of interpolations // (bin0=closest, bin1=next, frac=fractional dist.) - for(i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray); + for (i = 0; i < imageWidth; i++) xinterpArray[i] = findInterpX(i, xPixArray); // now do the interpolations and fill the png - for(j = 0; j < imageHeight; j++) { + for (j = 0; j < imageHeight; j++) { yinterp = findInterpY(j, yPixArray); r0 = z[yinterp.bin0]; r1 = z[yinterp.bin1]; - for(i = 0; i < imageWidth; i++, pxIndex += 4) { + for (i = 0; i < imageWidth; i++, pxIndex += 4) { c = interpColor(r0, r1, xinterpArray[i], yinterp); putColor(pixels, pxIndex, c); } } - } else { // drawingMethod = "fast" (zsmooth = "fast"|false) - for(j = 0; j < m; j++) { + } else { + // drawingMethod = "fast" (zsmooth = "fast"|false) + for (j = 0; j < m; j++) { row = z[j]; yb = ypx(j); - for(i = 0; i < n; i++) { + for (i = 0; i < n; i++) { c = setColor(row[i], 1); pxIndex = (yb * n + xpx(i)) * 4; putColor(pixels, pxIndex, c); @@ -299,44 +301,44 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var imageData = context.createImageData(canvasW, canvasH); try { imageData.data.set(pixels); - } catch(e) { + } catch (e) { var pxArray = imageData.data; var dlen = pxArray.length; - for(j = 0; j < dlen; j ++) { + for (j = 0; j < dlen; j++) { pxArray[j] = pixels[j]; } } context.putImageData(imageData, 0, 0); - } else { // rawingMethod = "default" (zsmooth = false) + } else { + // rawingMethod = "default" (zsmooth = false) // filling potentially large bricks works fastest with fillRect // gaps do not need to be exact integers, but if they *are* we will get // cleaner edges by rounding at least one edge var xGapLeft = Math.floor(xGap / 2); var yGapTop = Math.floor(yGap / 2); - for(j = 0; j < m; j++) { + for (j = 0; j < m; j++) { row = z[j]; yb.reverse(); yb[ybi] = ypx(j + 1); - if(yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) { + if (yb[0] === yb[1] || yb[0] === undefined || yb[1] === undefined) { continue; } xi = xpx(0); xb = [xi, xi]; - for(i = 0; i < n; i++) { + for (i = 0; i < n; i++) { // build one color brick! xb.reverse(); xb[xbi] = xpx(i + 1); - if(xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) { + if (xb[0] === xb[1] || xb[0] === undefined || xb[1] === undefined) { continue; } v = row[i]; c = setColor(v, (xb[1] - xb[0]) * (yb[1] - yb[0])); context.fillStyle = 'rgba(' + c.join(',') + ')'; - context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop, - xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap); + context.fillRect(xb[0] + xGapLeft, yb[0] + yGapTop, xb[1] - xb[0] - xGap, yb[1] - yb[0] - yGap); } } } @@ -346,11 +348,10 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { bcount = Math.round(bcount / pixcount); var avgColor = tinycolor('rgb(' + rcount + ',' + gcount + ',' + bcount + ')'); - gd._hmpixcount = (gd._hmpixcount||0) + pixcount; - gd._hmlumcount = (gd._hmlumcount||0) + pixcount * avgColor.getLuminance(); + gd._hmpixcount = (gd._hmpixcount || 0) + pixcount; + gd._hmlumcount = (gd._hmlumcount || 0) + pixcount * avgColor.getLuminance(); - var image3 = plotGroup.selectAll('image') - .data(cd); + var image3 = plotGroup.selectAll('image').data(cd); image3.enter().append('svg:image').attr({ xmlns: xmlnsNamespaces.svg, @@ -365,14 +366,14 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { 'xlink:href': canvas.toDataURL('image/png') }); - if(drawingMethod === 'fast' && !zsmooth) { + if (drawingMethod === 'fast' && !zsmooth) { image3.attr('style', PIXELATED_IMAGE_STYLE); } removeLabels(plotGroup); var texttemplate = trace.texttemplate; - if(texttemplate) { + if (texttemplate) { // dummy axis for formatting the z value var cOpts = extractOpts(trace); var dummyAx = { @@ -390,50 +391,54 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var jStop = aContour ? n - 1 : n; var textData = []; - for(i = iStart; i < iStop; i++) { + for (i = iStart; i < iStop; i++) { var yVal; - if(aContour) { + if (aContour) { yVal = cd0.y[i]; - } else if(aHistogram2dContour) { - if(i === 0 || i === m - 1) continue; + } else if (aHistogram2dContour) { + if (i === 0 || i === m - 1) continue; yVal = cd0.y[i]; - } else if(cd0.yCenter) { + } else if (cd0.yCenter) { yVal = cd0.yCenter[i]; } else { - if(i + 1 === m && cd0.y[i + 1] === undefined) continue; + if (i + 1 === m && cd0.y[i + 1] === undefined) continue; yVal = (cd0.y[i] + cd0.y[i + 1]) / 2; } var _y = Math.round(ya.c2p(yVal)); - if(0 > _y || _y > ya._length) continue; + if (0 > _y || _y > ya._length) continue; - for(j = jStart; j < jStop; j++) { + for (j = jStart; j < jStop; j++) { var xVal; - if(aContour) { + if (aContour) { xVal = cd0.x[j]; - } else if(aHistogram2dContour) { - if(j === 0 || j === n - 1) continue; + } else if (aHistogram2dContour) { + if (j === 0 || j === n - 1) continue; xVal = cd0.x[j]; - } else if(cd0.xCenter) { + } else if (cd0.xCenter) { xVal = cd0.xCenter[j]; } else { - if(j + 1 === n && cd0.x[j + 1] === undefined) continue; + if (j + 1 === n && cd0.x[j + 1] === undefined) continue; xVal = (cd0.x[j] + cd0.x[j + 1]) / 2; } var _x = Math.round(xa.c2p(xVal)); - if(0 > _x || _x > xa._length) continue; - - var obj = formatLabels({ - x: xVal, - y: yVal - }, trace, gd._fullLayout); + if (0 > _x || _x > xa._length) continue; + + var obj = formatLabels( + { + x: xVal, + y: yVal + }, + trace, + gd._fullLayout + ); obj.x = xVal; obj.y = yVal; var zVal = cd0.z[i][j]; - if(zVal === undefined) { + if (zVal === undefined) { obj.z = ''; obj.zLabel = ''; } else { @@ -442,16 +447,22 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { } var theText = cd0.text && cd0.text[i] && cd0.text[i][j]; - if(theText === undefined || theText === false) theText = ''; + if (theText === undefined || theText === false) theText = ''; obj.text = theText; - var _t = Lib.texttemplateString(texttemplate, obj, gd._fullLayout._d3locale, obj, trace._meta || {}); - if(!_t) continue; + var _t = Lib.texttemplateString( + texttemplate, + obj, + gd._fullLayout._d3locale, + obj, + trace._meta || {} + ); + if (!_t) continue; var lines = _t.split('
'); var nL = lines.length; var nC = 0; - for(k = 0; k < nL; k++) { + for (k = 0; k < nL; k++) { nC = Math.max(nC, lines[k].length); } @@ -470,31 +481,28 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { var fontSize = font.size; var globalFontSize = gd._fullLayout.font.size; - if(!fontSize || fontSize === 'auto') { + if (!fontSize || fontSize === 'auto') { var minW = Infinity; var minH = Infinity; var maxL = 0; var maxC = 0; - for(k = 0; k < textData.length; k++) { + for (k = 0; k < textData.length; k++) { var d = textData[k]; maxL = Math.max(maxL, d.l); maxC = Math.max(maxC, d.c); - if(k < textData.length - 1) { + if (k < textData.length - 1) { var nextD = textData[k + 1]; var dx = Math.abs(nextD.x - d.x); var dy = Math.abs(nextD.y - d.y); - if(dx) minW = Math.min(minW, dx); - if(dy) minH = Math.min(minH, dy); + if (dx) minW = Math.min(minW, dx); + if (dy) minH = Math.min(minH, dy); } } - if( - !isFinite(minW) || - !isFinite(minH) - ) { + if (!isFinite(minW) || !isFinite(minH)) { fontSize = globalFontSize; } else { minW -= xGap; @@ -506,17 +514,15 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { minW /= LINE_SPACING / 2; minH /= LINE_SPACING; - fontSize = Math.min( - Math.floor(minW), - Math.floor(minH), - globalFontSize - ); + fontSize = Math.min(Math.floor(minW), Math.floor(minH), globalFontSize); } } - if(fontSize <= 0 || !isFinite(fontSize)) return; + if (fontSize <= 0 || !isFinite(fontSize)) return; - var xFn = function(d) { return d.x; }; - var yFn = function(d) { + var xFn = function (d) { + return d.x; + }; + var yFn = function (d) { return d.y - fontSize * ((d.l * LINE_SPACING) / 2 - 1); }; @@ -528,16 +534,13 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { .classed(labelClass, 1) .append('text') .attr('text-anchor', 'middle') - .each(function(d) { + .each(function (d) { var thisLabel = d3.select(this); var fontColor = font.color; - if(!fontColor || fontColor === 'auto') { + if (!fontColor || fontColor === 'auto') { fontColor = Color.contrast( - d.z === undefined ? gd._fullLayout.plot_bgcolor : - 'rgba(' + - sclFunc(d.z).join() + - ')' + d.z === undefined ? gd._fullLayout.plot_bgcolor : 'rgba(' + sclFunc(d.z).join() + ')' ); } @@ -553,7 +556,7 @@ module.exports = function(gd, plotinfo, cdheatmaps, heatmapLayer) { variant: font.variant, textcase: font.textcase, lineposition: font.lineposition, - shadow: font.shadow, + shadow: font.shadow }) .text(d.t) .call(svgTextUtils.convertToTspans, gd); @@ -572,7 +575,7 @@ function findInterp(pixel, pixArray) { var bin0 = Math.round(interp); var frac = Math.abs(interp - bin0); - if(!interp || interp === maxBin || !frac) { + if (!interp || interp === maxBin || !frac) { return { bin0: bin0, bin1: bin0, @@ -591,15 +594,15 @@ function findInterpFromCenters(pixel, centerPixArray) { var bin = Lib.constrain(Lib.findBin(pixel, centerPixArray), 0, maxBin); var pix0 = centerPixArray[bin]; var pix1 = centerPixArray[bin + 1]; - var frac = ((pixel - pix0) / (pix1 - pix0)) || 0; - if(frac <= 0) { + var frac = (pixel - pix0) / (pix1 - pix0) || 0; + if (frac <= 0) { return { bin0: bin, bin1: bin, frac: 0 }; } - if(frac < 0.5) { + if (frac < 0.5) { return { bin0: bin, bin1: bin + 1, diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 7ba5776d70a..51a7cc61e1e 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -13,16 +13,12 @@ module.exports = { x: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the sample data to be binned on the x axis.' - ].join(' ') + description: ['Sets the sample data to be binned on the x axis.'].join(' ') }, y: { valType: 'data_array', editType: 'calc+clearAxisTypes', - description: [ - 'Sets the sample data to be binned on the y axis.' - ].join(' ') + description: ['Sets the sample data to be binned on the y axis.'].join(' ') }, xhoverformat: axisHoverFormat('x'), @@ -33,7 +29,7 @@ module.exports = { 'Sets hover text elements associated with each bar.', 'If a single string, the same string appears over all bars.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s coordinates.' + "this trace's coordinates." ].join(' ') }), hovertext: extendFlat({}, barAttrs.hovertext, { @@ -196,16 +192,22 @@ module.exports = { ].join(' ') }, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), - - texttemplate: texttemplateAttrs({ - arrayOk: false, - editType: 'plot' - }, { - keys: ['label', 'value'] - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), + + texttemplate: texttemplateAttrs( + { + arrayOk: false, + editType: 'plot' + }, + { + keys: ['label', 'value'] + } + ), textposition: extendFlat({}, barAttrs.textposition, { arrayOk: false diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index e381b0d7841..5ec5c62fbfc 100644 --- a/src/traces/histogram/defaults.js +++ b/src/traces/histogram/defaults.js @@ -17,7 +17,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var y = coerce('y'); var cumulative = coerce('cumulative.enabled'); - if(cumulative) { + if (cumulative) { coerce('cumulative.direction'); coerce('cumulative.currentbin'); } @@ -38,15 +38,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('xhoverformat'); coerce('yhoverformat'); - var orientation = coerce('orientation', (y && !x) ? 'h' : 'v'); + var orientation = coerce('orientation', y && !x ? 'h' : 'v'); var sampleLetter = orientation === 'v' ? 'x' : 'y'; var aggLetter = orientation === 'v' ? 'y' : 'x'; - var len = (x && y) ? - Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) : - Lib.minRowLength(traceOut[sampleLetter] || []); + var len = + x && y ? Math.min(Lib.minRowLength(x) && Lib.minRowLength(y)) : Lib.minRowLength(traceOut[sampleLetter] || []); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -57,7 +56,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); var hasAggregationData = traceOut[aggLetter]; - if(hasAggregationData) coerce('histfunc'); + if (hasAggregationData) coerce('histfunc'); coerce('histnorm'); // Note: bin defaults are now handled in Histogram.crossTraceDefaults @@ -72,8 +71,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // override defaultColor for error bars with defaultLine var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, {axis: 'x', inherit: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, { axis: 'y' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || Color.defaultLine, { axis: 'x', inherit: 'y' }); coerce('zorder'); }; diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 629a8eca23c..77d2cbe8441 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -69,15 +69,18 @@ module.exports = extendFlat( xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z', 1), - hovertemplate: hovertemplateAttrs({}, {keys: 'z'}), - texttemplate: texttemplateAttrs({ - arrayOk: false, - editType: 'plot' - }, { - keys: 'z' - }), + hovertemplate: hovertemplateAttrs({}, { keys: 'z' }), + texttemplate: texttemplateAttrs( + { + arrayOk: false, + editType: 'plot' + }, + { + keys: 'z' + } + ), textfont: heatmapAttrs.textfont, - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, - colorScaleAttrs('', {cLetter: 'z', autoColorDflt: false}) + colorScaleAttrs('', { cLetter: 'z', autoColorDflt: false }) ); diff --git a/src/traces/histogram2d/defaults.js b/src/traces/histogram2d/defaults.js index 9a794ef329b..e8adbc350e1 100644 --- a/src/traces/histogram2d/defaults.js +++ b/src/traces/histogram2d/defaults.js @@ -8,17 +8,16 @@ var colorscaleDefaults = require('../../components/colorscale/defaults'); var handleHeatmapLabelDefaults = require('../heatmap/label_defaults'); var attributes = require('./attributes'); - module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } handleSampleDefaults(traceIn, traceOut, coerce, layout); - if(traceOut.visible === false) return; + if (traceOut.visible === false) return; handleStyleDefaults(traceIn, traceOut, coerce, layout); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); coerce('hovertemplate'); handleHeatmapLabelDefaults(coerce, layout); diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index f1feef7e561..037392d8a59 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -7,45 +7,46 @@ var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').ax var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({ - x: histogram2dAttrs.x, - y: histogram2dAttrs.y, - z: histogram2dAttrs.z, - marker: histogram2dAttrs.marker, +module.exports = extendFlat( + { + x: histogram2dAttrs.x, + y: histogram2dAttrs.y, + z: histogram2dAttrs.z, + marker: histogram2dAttrs.marker, - histnorm: histogram2dAttrs.histnorm, - histfunc: histogram2dAttrs.histfunc, - nbinsx: histogram2dAttrs.nbinsx, - xbins: histogram2dAttrs.xbins, - nbinsy: histogram2dAttrs.nbinsy, - ybins: histogram2dAttrs.ybins, - autobinx: histogram2dAttrs.autobinx, - autobiny: histogram2dAttrs.autobiny, + histnorm: histogram2dAttrs.histnorm, + histfunc: histogram2dAttrs.histfunc, + nbinsx: histogram2dAttrs.nbinsx, + xbins: histogram2dAttrs.xbins, + nbinsy: histogram2dAttrs.nbinsy, + ybins: histogram2dAttrs.ybins, + autobinx: histogram2dAttrs.autobinx, + autobiny: histogram2dAttrs.autobiny, - bingroup: histogram2dAttrs.bingroup, - xbingroup: histogram2dAttrs.xbingroup, - ybingroup: histogram2dAttrs.ybingroup, + bingroup: histogram2dAttrs.bingroup, + xbingroup: histogram2dAttrs.xbingroup, + ybingroup: histogram2dAttrs.ybingroup, - autocontour: contourAttrs.autocontour, - ncontours: contourAttrs.ncontours, - contours: contourAttrs.contours, - line: { - color: contourAttrs.line.color, - width: extendFlat({}, contourAttrs.line.width, { - dflt: 0.5, - description: 'Sets the contour line width in (in px)' - }), - dash: contourAttrs.line.dash, - smoothing: contourAttrs.line.smoothing, - editType: 'plot' + autocontour: contourAttrs.autocontour, + ncontours: contourAttrs.ncontours, + contours: contourAttrs.contours, + line: { + color: contourAttrs.line.color, + width: extendFlat({}, contourAttrs.line.width, { + dflt: 0.5, + description: 'Sets the contour line width in (in px)' + }), + dash: contourAttrs.line.dash, + smoothing: contourAttrs.line.smoothing, + editType: 'plot' + }, + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z', 1), + hovertemplate: histogram2dAttrs.hovertemplate, + texttemplate: contourAttrs.texttemplate, + textfont: contourAttrs.textfont }, - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z', 1), - hovertemplate: histogram2dAttrs.hovertemplate, - texttemplate: contourAttrs.texttemplate, - textfont: contourAttrs.textfont -}, colorScaleAttrs('', { cLetter: 'z', editTypeOverride: 'calc' diff --git a/src/traces/histogram2dcontour/defaults.js b/src/traces/histogram2dcontour/defaults.js index 8f78147079f..4016ebd7162 100644 --- a/src/traces/histogram2dcontour/defaults.js +++ b/src/traces/histogram2dcontour/defaults.js @@ -8,7 +8,6 @@ var handleStyleDefaults = require('../contour/style_defaults'); var handleHeatmapLabelDefaults = require('../heatmap/label_defaults'); var attributes = require('./attributes'); - module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); @@ -19,17 +18,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } handleSampleDefaults(traceIn, traceOut, coerce, layout); - if(traceOut.visible === false) return; + if (traceOut.visible === false) return; handleContoursDefaults(traceIn, traceOut, coerce, coerce2); handleStyleDefaults(traceIn, traceOut, coerce, layout); coerce('xhoverformat'); coerce('yhoverformat'); coerce('hovertemplate'); - if( - traceOut.contours && - traceOut.contours.coloring === 'heatmap' - ) { + if (traceOut.contours && traceOut.contours.coloring === 'heatmap') { handleHeatmapLabelDefaults(coerce, layout); } }; diff --git a/src/traces/icicle/attributes.js b/src/traces/icicle/attributes.js index 9fc1f239cf0..60d2fa33851 100644 --- a/src/traces/icicle/attributes.js +++ b/src/traces/icicle/attributes.js @@ -37,7 +37,7 @@ module.exports = { '`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.', + 'and `tiling.flip` is *x*, the root nodes appear at the right.' ].join(' ') }, @@ -48,24 +48,22 @@ module.exports = { min: 0, dflt: 0, editType: 'plot', - description: [ - 'Sets the inner padding (in px).' - ].join(' ') + description: ['Sets the inner padding (in px).'].join(' ') }, - - editType: 'calc', + editType: 'calc' }, - marker: extendFlat({ - colors: sunburstAttrs.marker.colors, + marker: extendFlat( + { + colors: sunburstAttrs.marker.colors, - line: sunburstAttrs.marker.line, + line: sunburstAttrs.marker.line, - pattern: pattern, + pattern: pattern, - editType: 'calc' - }, + editType: 'calc' + }, colorScaleAttrs('marker', { colorAttr: 'colors', anim: false // TODO: set to anim: true? @@ -79,15 +77,21 @@ module.exports = { text: pieAttrs.text, textinfo: sunburstAttrs.textinfo, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys.concat(['label', 'value']) - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys.concat(['label', 'value']) + } + ), hovertext: pieAttrs.hovertext, hoverinfo: sunburstAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), textfont: pieAttrs.textfont, insidetextfont: pieAttrs.insidetextfont, @@ -97,5 +101,5 @@ module.exports = { sort: pieAttrs.sort, root: sunburstAttrs.root, - domain: domainAttrs({name: 'icicle', trace: true, editType: 'calc'}), + domain: domainAttrs({ name: 'icicle', trace: true, editType: 'calc' }) }; diff --git a/src/traces/icicle/defaults.js b/src/traces/icicle/defaults.js index 70595b50695..766035db64f 100644 --- a/src/traces/icicle/defaults.js +++ b/src/traces/icicle/defaults.js @@ -20,13 +20,13 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var labels = coerce('labels'); var parents = coerce('parents'); - if(!labels || !labels.length || !parents || !parents.length) { + if (!labels || !labels.length || !parents || !parents.length) { traceOut.visible = false; return; } var vals = coerce('values'); - if(vals && vals.length) { + if (vals && vals.length) { coerce('branchvalues'); } else { coerce('count'); @@ -41,7 +41,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var text = coerce('text'); coerce('texttemplate'); - if(!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); + if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); @@ -62,12 +62,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleMarkerDefaults(traceIn, traceOut, layout, coerce); - 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'}); + 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); @@ -81,7 +79,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } }; - if(hasPathbar) { + 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); diff --git a/src/traces/image/attributes.js b/src/traces/image/attributes.js index 4d12a10019a..77bf13d66c5 100644 --- a/src/traces/image/attributes.js +++ b/src/traces/image/attributes.js @@ -9,7 +9,7 @@ var colormodel = require('./constants').colormodel; var cm = ['rgb', 'rgba', 'rgba256', 'hsl', 'hsla']; var zminDesc = []; var zmaxDesc = []; -for(var i = 0; i < cm.length; i++) { +for (var i = 0; i < cm.length; i++) { var cr = colormodel[cm[i]]; zminDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zminDflt || cr.min).join(', ') + '].'); zmaxDesc.push('For the `' + cm[i] + '` colormodel, it is [' + (cr.zmaxDflt || cr.max).join(', ') + '].'); @@ -28,7 +28,7 @@ module.exports = extendFlat({ valType: 'data_array', editType: 'calc', description: [ - 'A 2-dimensional array in which each element is an array of 3 or 4 numbers representing a color.', + 'A 2-dimensional array in which each element is an array of 3 or 4 numbers representing a color.' ].join(' ') }, colormodel: { @@ -54,10 +54,10 @@ module.exports = extendFlat({ zmin: { valType: 'info_array', items: [ - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'} + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' } ], editType: 'calc', description: [ @@ -69,10 +69,10 @@ module.exports = extendFlat({ zmax: { valType: 'info_array', items: [ - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'}, - {valType: 'number', editType: 'calc'} + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' }, + { valType: 'number', editType: 'calc' } ], editType: 'calc', description: [ @@ -86,7 +86,7 @@ module.exports = extendFlat({ dflt: 0, editType: 'calc+clearAxisTypes', description: [ - 'Set the image\'s x position. The left edge of the image', + "Set the image's x position. The left edge of the image", '(or the right edge if the x axis is reversed or dx is negative)', 'will be found at xmin=x0-dx/2' ].join(' ') @@ -96,7 +96,7 @@ module.exports = extendFlat({ dflt: 0, editType: 'calc+clearAxisTypes', description: [ - 'Set the image\'s y position. The top edge of the image', + "Set the image's y position. The top edge of the image", '(or the bottom edge if the y axis is NOT reversed or if dy is negative)', 'will be found at ymin=y0-dy/2. By default when an image trace is', 'included, the y axis will be reversed so that the image is right-side-up,', @@ -108,13 +108,13 @@ module.exports = extendFlat({ valType: 'number', dflt: 1, editType: 'calc', - description: 'Set the pixel\'s horizontal size.' + description: "Set the pixel's horizontal size." }, dy: { valType: 'number', dflt: 1, editType: 'calc', - description: 'Set the pixel\'s vertical size' + description: "Set the pixel's vertical size" }, text: { valType: 'data_array', @@ -130,9 +130,12 @@ module.exports = extendFlat({ flags: ['x', 'y', 'z', 'color', 'name', 'text'], dflt: 'x+y+z+text+name' }), - hovertemplate: hovertemplateAttrs({}, { - keys: ['z', 'color', 'colormodel'] - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: ['z', 'color', 'colormodel'] + } + ), - zorder: zorder, + zorder: zorder }); diff --git a/src/traces/image/defaults.js b/src/traces/image/defaults.js index 24131a669be..54a648c8cac 100644 --- a/src/traces/image/defaults.js +++ b/src/traces/image/defaults.js @@ -11,12 +11,12 @@ module.exports = function supplyDefaults(traceIn, traceOut) { } coerce('source'); // sanitize source to only allow for data URI representing images - if(traceOut.source && !traceOut.source.match(dataUri)) delete traceOut.source; + if (traceOut.source && !traceOut.source.match(dataUri)) delete traceOut.source; traceOut._hasSource = !!traceOut.source; var z = coerce('z'); traceOut._hasZ = !(z === undefined || !z.length || !z[0] || !z[0].length); - if(!traceOut._hasZ && !traceOut._hasSource) { + if (!traceOut._hasZ && !traceOut._hasSource) { traceOut.visible = false; return; } @@ -27,12 +27,12 @@ module.exports = function supplyDefaults(traceIn, traceOut) { coerce('dy'); var cm; - if(traceOut._hasZ) { + if (traceOut._hasZ) { coerce('colormodel', 'rgb'); cm = constants.colormodel[traceOut.colormodel]; - coerce('zmin', (cm.zminDflt || cm.min)); - coerce('zmax', (cm.zmaxDflt || cm.max)); - } else if(traceOut._hasSource) { + coerce('zmin', cm.zminDflt || cm.min); + coerce('zmax', cm.zmaxDflt || cm.max); + } else if (traceOut._hasSource) { traceOut.colormodel = 'rgba256'; cm = constants.colormodel[traceOut.colormodel]; traceOut.zmin = cm.zminDflt; diff --git a/src/traces/isosurface/attributes.js b/src/traces/isosurface/attributes.js index cd1be7240e0..5a51a04d920 100644 --- a/src/traces/isosurface/attributes.js +++ b/src/traces/isosurface/attributes.js @@ -14,10 +14,9 @@ function makeSliceAttr(axLetter) { show: { valType: 'boolean', dflt: false, - description: [ - 'Determines whether or not slice planes about the', axLetter, - 'dimension are drawn.' - ].join(' ') + description: ['Determines whether or not slice planes about the', axLetter, 'dimension are drawn.'].join( + ' ' + ) }, locations: { valType: 'data_array', @@ -25,7 +24,9 @@ function makeSliceAttr(axLetter) { description: [ 'Specifies the location(s) of slices on the axis.', 'When not specified slices would be created for', - 'all points of the axis', axLetter, 'except start and end.' + 'all points of the axis', + axLetter, + 'except start and end.' ].join(' ') }, fill: { @@ -49,7 +50,8 @@ function makeCapAttr(axLetter) { valType: 'boolean', dflt: true, description: [ - 'Sets the fill ratio of the `slices`. The default fill value of the', axLetter, + 'Sets the fill ratio of the `slices`. The default fill value of the', + axLetter, '`slices` is 1 meaning that they are entirely shaded. On the other hand', 'Applying a `fill` ratio less than one would allow the creation of', 'openings parallel to the edges.' @@ -70,166 +72,160 @@ function makeCapAttr(axLetter) { }; } -var attrs = module.exports = overrideAll(extendFlat({ - x: { - valType: 'data_array', - description: [ - 'Sets the X coordinates of the vertices on X axis.' - ].join(' ') - }, - y: { - valType: 'data_array', - description: [ - 'Sets the Y coordinates of the vertices on Y axis.' - ].join(' ') - }, - z: { - valType: 'data_array', - description: [ - 'Sets the Z coordinates of the vertices on Z axis.' - ].join(' ') - }, - value: { - valType: 'data_array', - description: [ - 'Sets the 4th dimension (value) of the vertices.' - ].join(' ') - }, - isomin: { - valType: 'number', - description: [ - 'Sets the minimum boundary for iso-surface plot.' - ].join(' ') - }, - isomax: { - valType: 'number', - description: [ - 'Sets the maximum boundary for iso-surface plot.' - ].join(' ') - }, +var attrs = (module.exports = overrideAll( + extendFlat( + { + x: { + valType: 'data_array', + description: ['Sets the X coordinates of the vertices on X axis.'].join(' ') + }, + y: { + valType: 'data_array', + description: ['Sets the Y coordinates of the vertices on Y axis.'].join(' ') + }, + z: { + valType: 'data_array', + description: ['Sets the Z coordinates of the vertices on Z axis.'].join(' ') + }, + value: { + valType: 'data_array', + description: ['Sets the 4th dimension (value) of the vertices.'].join(' ') + }, + isomin: { + valType: 'number', + description: ['Sets the minimum boundary for iso-surface plot.'].join(' ') + }, + isomax: { + valType: 'number', + description: ['Sets the maximum boundary for iso-surface plot.'].join(' ') + }, - surface: { - show: { - valType: 'boolean', - dflt: true, - description: [ - 'Hides/displays surfaces between minimum and maximum iso-values.' - ].join(' ') - }, - count: { - valType: 'integer', - dflt: 2, - min: 1, - description: [ - 'Sets the number of iso-surfaces between minimum and maximum iso-values.', - 'By default this value is 2 meaning that only minimum and maximum surfaces', - 'would be drawn.' - ].join(' ') - }, - fill: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - description: [ - 'Sets the fill ratio of the iso-surface. The default fill value of the', - 'surface is 1 meaning that they are entirely shaded. On the other hand', - 'Applying a `fill` ratio less than one would allow the creation of', - 'openings parallel to the edges.' - ].join(' ') - }, - pattern: { - valType: 'flaglist', - flags: ['A', 'B', 'C', 'D', 'E'], - extras: ['all', 'odd', 'even'], - dflt: 'all', - description: [ - 'Sets the surface pattern of the iso-surface 3-D sections. The default pattern of', - 'the surface is `all` meaning that the rest of surface elements would be shaded.', - 'The check options (either 1 or 2) could be used to draw half of the squares', - 'on the surface. Using various combinations of capital `A`, `B`, `C`, `D` and `E`', - 'may also be used to reduce the number of triangles on the iso-surfaces and', - 'creating other patterns of interest.' - ].join(' ') - } - }, + surface: { + show: { + valType: 'boolean', + dflt: true, + description: ['Hides/displays surfaces between minimum and maximum iso-values.'].join(' ') + }, + count: { + valType: 'integer', + dflt: 2, + min: 1, + description: [ + 'Sets the number of iso-surfaces between minimum and maximum iso-values.', + 'By default this value is 2 meaning that only minimum and maximum surfaces', + 'would be drawn.' + ].join(' ') + }, + fill: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + description: [ + 'Sets the fill ratio of the iso-surface. The default fill value of the', + 'surface is 1 meaning that they are entirely shaded. On the other hand', + 'Applying a `fill` ratio less than one would allow the creation of', + 'openings parallel to the edges.' + ].join(' ') + }, + pattern: { + valType: 'flaglist', + flags: ['A', 'B', 'C', 'D', 'E'], + extras: ['all', 'odd', 'even'], + dflt: 'all', + description: [ + 'Sets the surface pattern of the iso-surface 3-D sections. The default pattern of', + 'the surface is `all` meaning that the rest of surface elements would be shaded.', + 'The check options (either 1 or 2) could be used to draw half of the squares', + 'on the surface. Using various combinations of capital `A`, `B`, `C`, `D` and `E`', + 'may also be used to reduce the number of triangles on the iso-surfaces and', + 'creating other patterns of interest.' + ].join(' ') + } + }, - spaceframe: { - show: { - valType: 'boolean', - dflt: false, - description: [ - 'Displays/hides tetrahedron shapes between minimum and', - 'maximum iso-values. Often useful when either caps or', - 'surfaces are disabled or filled with values less than 1.' - ].join(' ') - }, - fill: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.15, - description: [ - 'Sets the fill ratio of the `spaceframe` elements. The default fill value', - 'is 0.15 meaning that only 15% of the area of every faces of tetras would be', - 'shaded. Applying a greater `fill` ratio would allow the creation of stronger', - 'elements or could be sued to have entirely closed areas (in case of using 1).' - ].join(' ') - } - }, + spaceframe: { + show: { + valType: 'boolean', + dflt: false, + description: [ + 'Displays/hides tetrahedron shapes between minimum and', + 'maximum iso-values. Often useful when either caps or', + 'surfaces are disabled or filled with values less than 1.' + ].join(' ') + }, + fill: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.15, + description: [ + 'Sets the fill ratio of the `spaceframe` elements. The default fill value', + 'is 0.15 meaning that only 15% of the area of every faces of tetras would be', + 'shaded. Applying a greater `fill` ratio would allow the creation of stronger', + 'elements or could be sued to have entirely closed areas (in case of using 1).' + ].join(' ') + } + }, - slices: { - x: makeSliceAttr('x'), - y: makeSliceAttr('y'), - z: makeSliceAttr('z') - }, + slices: { + x: makeSliceAttr('x'), + y: makeSliceAttr('y'), + z: makeSliceAttr('z') + }, - caps: { - x: makeCapAttr('x'), - y: makeCapAttr('y'), - z: makeCapAttr('z') - }, + caps: { + x: makeCapAttr('x'), + y: makeCapAttr('y'), + z: makeCapAttr('z') + }, - text: { - valType: 'string', - dflt: '', - arrayOk: true, - description: [ - 'Sets the text elements associated with the vertices.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }, - hovertext: { - valType: 'string', - dflt: '', - arrayOk: true, - description: 'Same as `text`.' - }, - hovertemplate: hovertemplateAttrs(), - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z'), - valuehoverformat: axisHoverFormat('value', 1), + text: { + valType: 'string', + dflt: '', + arrayOk: true, + description: [ + 'Sets the text elements associated with the vertices.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }, + hovertext: { + valType: 'string', + dflt: '', + arrayOk: true, + description: 'Same as `text`.' + }, + hovertemplate: hovertemplateAttrs(), + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z'), + valuehoverformat: axisHoverFormat('value', 1), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}, + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + }, -colorScaleAttrs('', { - colorAttr: '`value`', - showScaleDflt: true, - editTypeOverride: 'calc' -}), { - opacity: meshAttrs.opacity, - lightposition: meshAttrs.lightposition, - lighting: meshAttrs.lighting, - flatshading: meshAttrs.flatshading, - contour: meshAttrs.contour, + colorScaleAttrs('', { + colorAttr: '`value`', + showScaleDflt: true, + editTypeOverride: 'calc' + }), + { + opacity: meshAttrs.opacity, + lightposition: meshAttrs.lightposition, + lighting: meshAttrs.lighting, + flatshading: meshAttrs.flatshading, + contour: meshAttrs.contour, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo) -}), 'calc', 'nested'); + hoverinfo: extendFlat({}, baseAttrs.hoverinfo) + } + ), + 'calc', + 'nested' +)); // required defaults to speed up surface normal calculations -attrs.flatshading.dflt = true; attrs.lighting.facenormalsepsilon.dflt = 0; +attrs.flatshading.dflt = true; +attrs.lighting.facenormalsepsilon.dflt = 0; attrs.x.editType = attrs.y.editType = attrs.z.editType = attrs.value.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index b9b134ccbd9..53df77ad9c3 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -8,232 +8,231 @@ var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; -module.exports = extendFlat({ - x: { - valType: 'data_array', - editType: 'calc+clearAxisTypes', - description: [ - 'Sets the X coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - y: { - valType: 'data_array', - editType: 'calc+clearAxisTypes', - description: [ - 'Sets the Y coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - z: { - valType: 'data_array', - editType: 'calc+clearAxisTypes', - description: [ - 'Sets the Z coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', - 'jointly represent the X, Y and Z coordinates of the nth vertex.' - ].join(' ') - }, - - i: { - valType: 'data_array', - editType: 'calc', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *first* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', - 'together represent face m (triangle m) in the mesh, where `i[m] = n` points to the triplet', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `i` represents a', - 'point in space, which is the first vertex of a triangle.' - ].join(' ') - }, - j: { - valType: 'data_array', - editType: 'calc', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *second* vertex of a triangle. For example, `{i[m], j[m], k[m]}` ', - 'together represent face m (triangle m) in the mesh, where `j[m] = n` points to the triplet', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `j` represents a', - 'point in space, which is the second vertex of a triangle.' - ].join(' ') - - }, - k: { - valType: 'data_array', - editType: 'calc', - description: [ - 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', - 'vectors, representing the *third* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', - 'together represent face m (triangle m) in the mesh, where `k[m] = n` points to the triplet ', - '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `k` represents a', - 'point in space, which is the third vertex of a triangle.' - ].join(' ') +module.exports = extendFlat( + { + x: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + description: [ + 'Sets the X coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, + y: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + description: [ + 'Sets the Y coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, + z: { + valType: 'data_array', + editType: 'calc+clearAxisTypes', + description: [ + 'Sets the Z coordinates of the vertices. The nth element of vectors `x`, `y` and `z`', + 'jointly represent the X, Y and Z coordinates of the nth vertex.' + ].join(' ') + }, - }, + i: { + valType: 'data_array', + editType: 'calc', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *first* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', + 'together represent face m (triangle m) in the mesh, where `i[m] = n` points to the triplet', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `i` represents a', + 'point in space, which is the first vertex of a triangle.' + ].join(' ') + }, + j: { + valType: 'data_array', + editType: 'calc', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *second* vertex of a triangle. For example, `{i[m], j[m], k[m]}` ', + 'together represent face m (triangle m) in the mesh, where `j[m] = n` points to the triplet', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `j` represents a', + 'point in space, which is the second vertex of a triangle.' + ].join(' ') + }, + k: { + valType: 'data_array', + editType: 'calc', + description: [ + 'A vector of vertex indices, i.e. integer values between 0 and the length of the vertex', + 'vectors, representing the *third* vertex of a triangle. For example, `{i[m], j[m], k[m]}`', + 'together represent face m (triangle m) in the mesh, where `k[m] = n` points to the triplet ', + '`{x[n], y[n], z[n]}` in the vertex arrays. Therefore, each element in `k` represents a', + 'point in space, which is the third vertex of a triangle.' + ].join(' ') + }, - text: { - valType: 'string', - dflt: '', - arrayOk: true, - editType: 'calc', - description: [ - 'Sets the text elements associated with the vertices.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }, - hovertext: { - valType: 'string', - dflt: '', - arrayOk: true, - editType: 'calc', - description: 'Same as `text`.' - }, - hovertemplate: hovertemplateAttrs({editType: 'calc'}), - - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z'), - - delaunayaxis: { - valType: 'enumerated', - values: [ 'x', 'y', 'z' ], - dflt: 'z', - editType: 'calc', - description: [ - 'Sets the Delaunay axis, which is the axis that is perpendicular to the surface of the', - 'Delaunay triangulation.', - 'It has an effect if `i`, `j`, `k` are not provided and `alphahull` is set to indicate', - 'Delaunay triangulation.' - ].join(' ') - }, + text: { + valType: 'string', + dflt: '', + arrayOk: true, + editType: 'calc', + description: [ + 'Sets the text elements associated with the vertices.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }, + hovertext: { + valType: 'string', + dflt: '', + arrayOk: true, + editType: 'calc', + description: 'Same as `text`.' + }, + hovertemplate: hovertemplateAttrs({ editType: 'calc' }), - alphahull: { - valType: 'number', - dflt: -1, - editType: 'calc', - description: [ - 'Determines how the mesh surface triangles are derived from the set of', - 'vertices (points) represented by the `x`, `y` and `z` arrays, if', - 'the `i`, `j`, `k` arrays are not supplied.', - 'For general use of `mesh3d` it is preferred that `i`, `j`, `k` are', - 'supplied.', - - 'If *-1*, Delaunay triangulation is used, which is mainly suitable if the', - 'mesh is a single, more or less layer surface that is perpendicular to `delaunayaxis`.', - 'In case the `delaunayaxis` intersects the mesh surface at more than one point', - 'it will result triangles that are very long in the dimension of `delaunayaxis`.', - - 'If *>0*, the alpha-shape algorithm is used. In this case, the positive `alphahull` value', - 'signals the use of the alpha-shape algorithm, _and_ its value', - 'acts as the parameter for the mesh fitting.', - - 'If *0*, the convex-hull algorithm is used. It is suitable for convex bodies', - 'or if the intention is to enclose the `x`, `y` and `z` point set into a convex', - 'hull.' - ].join(' ') - }, + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z'), - intensity: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the intensity values for vertices or cells', - 'as defined by `intensitymode`.', - 'It can be used for plotting fields on meshes.' - ].join(' ') - }, - intensitymode: { - valType: 'enumerated', - values: ['vertex', 'cell'], - dflt: 'vertex', - editType: 'calc', - description: [ - 'Determines the source of `intensity` values.' - ].join(' ') - }, + delaunayaxis: { + valType: 'enumerated', + values: ['x', 'y', 'z'], + dflt: 'z', + editType: 'calc', + description: [ + 'Sets the Delaunay axis, which is the axis that is perpendicular to the surface of the', + 'Delaunay triangulation.', + 'It has an effect if `i`, `j`, `k` are not provided and `alphahull` is set to indicate', + 'Delaunay triangulation.' + ].join(' ') + }, - // Color field - color: { - valType: 'color', - editType: 'calc', - description: 'Sets the color of the whole mesh' - }, - vertexcolor: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the color of each vertex', - 'Overrides *color*. While Red, green and blue colors', - 'are in the range of 0 and 255; in the case of having', - 'vertex color data in RGBA format, the alpha color', - 'should be normalized to be between 0 and 1.' - ].join(' ') - }, - facecolor: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the color of each face', - 'Overrides *color* and *vertexcolor*.' - ].join(' ') - }, -}, - -colorScaleAttrs('', { - colorAttr: '`intensity`', - showScaleDflt: true, - editTypeOverride: 'calc' -}), { - opacity: surfaceAttrs.opacity, - - // Flat shaded mode - flatshading: { - valType: 'boolean', - dflt: false, - editType: 'calc', - description: [ - 'Determines whether or not normal smoothing is applied to the meshes,', - 'creating meshes with an angular, low-poly look via flat reflections.' - ].join(' ') - }, + alphahull: { + valType: 'number', + dflt: -1, + editType: 'calc', + description: [ + 'Determines how the mesh surface triangles are derived from the set of', + 'vertices (points) represented by the `x`, `y` and `z` arrays, if', + 'the `i`, `j`, `k` arrays are not supplied.', + 'For general use of `mesh3d` it is preferred that `i`, `j`, `k` are', + 'supplied.', + + 'If *-1*, Delaunay triangulation is used, which is mainly suitable if the', + 'mesh is a single, more or less layer surface that is perpendicular to `delaunayaxis`.', + 'In case the `delaunayaxis` intersects the mesh surface at more than one point', + 'it will result triangles that are very long in the dimension of `delaunayaxis`.', + + 'If *>0*, the alpha-shape algorithm is used. In this case, the positive `alphahull` value', + 'signals the use of the alpha-shape algorithm, _and_ its value', + 'acts as the parameter for the mesh fitting.', + + 'If *0*, the convex-hull algorithm is used. It is suitable for convex bodies', + 'or if the intention is to enclose the `x`, `y` and `z` point set into a convex', + 'hull.' + ].join(' ') + }, - contour: { - show: extendFlat({}, surfaceAttrs.contours.x.show, { + intensity: { + valType: 'data_array', + editType: 'calc', description: [ - 'Sets whether or not dynamic contours are shown on hover' + 'Sets the intensity values for vertices or cells', + 'as defined by `intensitymode`.', + 'It can be used for plotting fields on meshes.' ].join(' ') - }), - color: surfaceAttrs.contours.x.color, - width: surfaceAttrs.contours.x.width, - editType: 'calc' - }, + }, + intensitymode: { + valType: 'enumerated', + values: ['vertex', 'cell'], + dflt: 'vertex', + editType: 'calc', + description: ['Determines the source of `intensity` values.'].join(' ') + }, - lightposition: { - x: extendFlat({}, surfaceAttrs.lightposition.x, {dflt: 1e5}), - y: extendFlat({}, surfaceAttrs.lightposition.y, {dflt: 1e5}), - z: extendFlat({}, surfaceAttrs.lightposition.z, {dflt: 0}), - editType: 'calc' - }, - lighting: extendFlat({ - vertexnormalsepsilon: { - valType: 'number', - min: 0.00, - max: 1, - dflt: 1e-12, // otherwise finely tessellated things eg. the brain will have no specular light reflection + // Color field + color: { + valType: 'color', editType: 'calc', - description: 'Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.' + description: 'Sets the color of the whole mesh' }, - facenormalsepsilon: { - valType: 'number', - min: 0.00, - max: 1, - dflt: 1e-6, // even the brain model doesn't appear to need finer than this + vertexcolor: { + valType: 'data_array', + editType: 'calc', + description: [ + 'Sets the color of each vertex', + 'Overrides *color*. While Red, green and blue colors', + 'are in the range of 0 and 255; in the case of having', + 'vertex color data in RGBA format, the alpha color', + 'should be normalized to be between 0 and 1.' + ].join(' ') + }, + facecolor: { + valType: 'data_array', editType: 'calc', - description: 'Epsilon for face normals calculation avoids math issues arising from degenerate geometry.' + description: ['Sets the color of each face', 'Overrides *color* and *vertexcolor*.'].join(' ') + } + }, + + colorScaleAttrs('', { + colorAttr: '`intensity`', + showScaleDflt: true, + editTypeOverride: 'calc' + }), + { + opacity: surfaceAttrs.opacity, + + // Flat shaded mode + flatshading: { + valType: 'boolean', + dflt: false, + editType: 'calc', + description: [ + 'Determines whether or not normal smoothing is applied to the meshes,', + 'creating meshes with an angular, low-poly look via flat reflections.' + ].join(' ') + }, + + contour: { + show: extendFlat({}, surfaceAttrs.contours.x.show, { + description: ['Sets whether or not dynamic contours are shown on hover'].join(' ') + }), + color: surfaceAttrs.contours.x.color, + width: surfaceAttrs.contours.x.width, + editType: 'calc' }, - editType: 'calc' - }, surfaceAttrs.lighting), - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, {editType: 'calc'}), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}); + lightposition: { + x: extendFlat({}, surfaceAttrs.lightposition.x, { dflt: 1e5 }), + y: extendFlat({}, surfaceAttrs.lightposition.y, { dflt: 1e5 }), + z: extendFlat({}, surfaceAttrs.lightposition.z, { dflt: 0 }), + editType: 'calc' + }, + lighting: extendFlat( + { + vertexnormalsepsilon: { + valType: 'number', + min: 0.0, + max: 1, + dflt: 1e-12, // otherwise finely tessellated things eg. the brain will have no specular light reflection + editType: 'calc', + description: + 'Epsilon for vertex normals calculation avoids math issues arising from degenerate geometry.' + }, + facenormalsepsilon: { + valType: 'number', + min: 0.0, + max: 1, + dflt: 1e-6, // even the brain model doesn't appear to need finer than this + editType: 'calc', + description: + 'Epsilon for face normals calculation avoids math issues arising from degenerate geometry.' + }, + editType: 'calc' + }, + surfaceAttrs.lighting + ), + + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { editType: 'calc' }), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + } +); diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index c3e94d6501f..1653488afa4 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -12,27 +12,29 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // read in face/vertex properties function readComponents(array) { - var ret = array.map(function(attr) { + var ret = array.map(function (attr) { var result = coerce(attr); - if(result && Lib.isArrayOrTypedArray(result)) return result; + if (result && Lib.isArrayOrTypedArray(result)) return result; return null; }); - return ret.every(function(x) { - return x && x.length === ret[0].length; - }) && ret; + return ( + ret.every(function (x) { + return x && x.length === ret[0].length; + }) && ret + ); } var coords = readComponents(['x', 'y', 'z']); - if(!coords) { + if (!coords) { traceOut.visible = false; return; } readComponents(['i', 'j', 'k']); // three indices should be all provided or not - if( + if ( (traceOut.i && (!traceOut.j || !traceOut.k)) || (traceOut.j && (!traceOut.k || !traceOut.i)) || (traceOut.k && (!traceOut.i || !traceOut.j)) @@ -60,23 +62,25 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout 'alphahull', 'delaunayaxis', 'opacity' - ].forEach(function(x) { coerce(x); }); + ].forEach(function (x) { + coerce(x); + }); var showContour = coerce('contour.show'); - if(showContour) { + if (showContour) { coerce('contour.color'); coerce('contour.width'); } - if('intensity' in traceIn) { + if ('intensity' in traceIn) { coerce('intensity'); coerce('intensitymode'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'c' }); } else { traceOut.showscale = false; - if('facecolor' in traceIn) coerce('facecolor'); - else if('vertexcolor' in traceIn) coerce('vertexcolor'); + if ('facecolor' in traceIn) coerce('facecolor'); + else if ('vertexcolor' in traceIn) coerce('vertexcolor'); else coerce('color', defaultColor); } diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js index a8a3663fc21..dd7013d840e 100644 --- a/src/traces/parcats/attributes.js +++ b/src/traces/parcats/attributes.js @@ -7,36 +7,33 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; var domainAttrs = require('../../plots/domain').attributes; -var line = extendFlat( - {editType: 'calc'}, - colorScaleAttrs('line', {editTypeOverride: 'calc'}), - { - shape: { - valType: 'enumerated', - values: ['linear', 'hspline'], - dflt: 'linear', - editType: 'plot', - description: [ - 'Sets the shape of the paths.', - 'If `linear`, paths are composed of straight lines.', - 'If `hspline`, paths are composed of horizontal curved splines' - ].join(' ') - }, +var line = extendFlat({ editType: 'calc' }, colorScaleAttrs('line', { editTypeOverride: 'calc' }), { + shape: { + valType: 'enumerated', + values: ['linear', 'hspline'], + dflt: 'linear', + editType: 'plot', + description: [ + 'Sets the shape of the paths.', + 'If `linear`, paths are composed of straight lines.', + 'If `hspline`, paths are composed of horizontal curved splines' + ].join(' ') + }, - hovertemplate: hovertemplateAttrs({ + hovertemplate: hovertemplateAttrs( + { editType: 'plot', arrayOk: false - }, { + }, + { keys: ['count', 'probability'], - description: [ - 'This value here applies when hovering over lines.' - ].join(' ') - }) - } -); + description: ['This value here applies when hovering over lines.'].join(' ') + } + ) +}); module.exports = { - domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}), + domain: domainAttrs({ name: 'parcats', trace: true, editType: 'calc' }), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['count', 'probability'], @@ -55,20 +52,20 @@ module.exports = { 'If `dimension`, hover interactions take place across all categories per dimension.' ].join(' ') }, - hovertemplate: hovertemplateAttrs({ - editType: 'plot', - arrayOk: false - }, { - keys: [ - 'count', 'probability', 'category', - 'categorycount', 'colorcount', 'bandcolorcount' - ], - description: [ - 'This value here applies when hovering over dimensions.', - 'Note that *categorycount*, *colorcount* and *bandcolorcount*', - 'are only available when `hoveron` contains the *color* flag. ' - ].join(' ') - }), + hovertemplate: hovertemplateAttrs( + { + editType: 'plot', + arrayOk: false + }, + { + keys: ['count', 'probability', 'category', 'categorycount', 'colorcount', 'bandcolorcount'], + description: [ + 'This value here applies when hovering over dimensions.', + 'Note that *categorycount*, *colorcount* and *bandcolorcount*', + 'are only available when `hoveron` contains the *color* flag. ' + ].join(' ') + } + ), arrangement: { valType: 'enumerated', @@ -119,9 +116,7 @@ module.exports = { }, categoryorder: { valType: 'enumerated', - values: [ - 'trace', 'category ascending', 'category descending', 'array' - ], + values: ['trace', 'category ascending', 'category descending', 'array'], dflt: 'trace', editType: 'calc', description: [ diff --git a/src/traces/parcats/defaults.js b/src/traces/parcats/defaults.js index cd724d0a9ac..eca598c0259 100644 --- a/src/traces/parcats/defaults.js +++ b/src/traces/parcats/defaults.js @@ -15,10 +15,10 @@ function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { coerce('line.hovertemplate'); var lineColor = coerce('line.color', layout.colorway[0]); - if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) { - if(lineColor.length) { + if (hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) { + if (lineColor.length) { coerce('line.colorscale'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: 'line.', cLetter: 'c'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: 'line.', cLetter: 'c' }); return lineColor.length; } else { traceOut.line.color = defaultColor; @@ -34,26 +34,25 @@ function dimensionDefaults(dimensionIn, dimensionOut) { var values = coerce('values'); var visible = coerce('visible'); - if(!(values && values.length)) { + if (!(values && values.length)) { visible = dimensionOut.visible = false; } - if(visible) { + if (visible) { // Dimension level coerce('label'); coerce('displayindex', dimensionOut._index); // Category level var arrayIn = dimensionIn.categoryarray; - var isValidArray = (Lib.isArrayOrTypedArray(arrayIn) && arrayIn.length > 0) || - isTypedArraySpec(arrayIn); + var isValidArray = (Lib.isArrayOrTypedArray(arrayIn) && arrayIn.length > 0) || isTypedArraySpec(arrayIn); var orderDefault; - if(isValidArray) orderDefault = 'array'; + if (isValidArray) orderDefault = 'array'; var order = coerce('categoryorder', orderDefault); // coerce 'categoryarray' only in array order case - if(order === 'array') { + if (order === 'array') { coerce('categoryarray'); coerce('ticktext'); } else { @@ -62,7 +61,7 @@ function dimensionDefaults(dimensionIn, dimensionOut) { } // cannot set 'categoryorder' to 'array' with an invalid 'categoryarray' - if(!isValidArray && order === 'array') { + if (!isValidArray && order === 'array') { dimensionOut.categoryorder = 'trace'; } } @@ -82,7 +81,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleDomainDefaults(traceOut, layout, coerce); - if(!Array.isArray(dimensions) || !dimensions.length) { + if (!Array.isArray(dimensions) || !dimensions.length) { traceOut.visible = false; } diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index cd1c657346f..dc9f39a6b4f 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -51,15 +51,12 @@ module.exports = { values: { valType: 'data_array', editType: 'calc', - description: [ - 'Sets the values of the sectors.', - 'If omitted, we count occurrences of each label.' - ].join(' ') + description: ['Sets the values of the sectors.', 'If omitted, we count occurrences of each label.'].join(' ') }, marker: { colors: { - valType: 'data_array', // TODO 'color_array' ? + valType: 'data_array', // TODO 'color_array' ? editType: 'calc', description: [ 'Sets the color of each sector.', @@ -74,9 +71,7 @@ module.exports = { dflt: colorAttrs.defaultLine, arrayOk: true, editType: 'style', - description: [ - 'Sets the color of the line enclosing each sector.' - ].join(' ') + description: ['Sets the color of the line enclosing each sector.'].join(' ') }, width: { valType: 'number', @@ -84,9 +79,7 @@ module.exports = { dflt: 0, arrayOk: true, editType: 'style', - description: [ - 'Sets the width (in px) of the line enclosing each sector.' - ].join(' ') + description: ['Sets the width (in px) of the line enclosing each sector.'].join(' ') }, editType: 'calc' }, @@ -115,15 +108,15 @@ module.exports = { 'If a single string, the same string appears for', 'all data points.', 'If an array of string, the items are mapped in order of', - 'this trace\'s sectors.', + "this trace's sectors.", 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }, -// 'see eg:' -// 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif', -// '(this example involves a map too - may someday be a whole trace type', -// 'of its own. but the point is the size of the whole pie is important.)' + // 'see eg:' + // 'https://www.e-education.psu.edu/natureofgeoinfo/sites/www.e-education.psu.edu.natureofgeoinfo/files/image/hisp_pies.gif', + // '(this example involves a map too - may someday be a whole trace type', + // 'of its own. but the point is the size of the whole pie is important.)' scalegroup: { valType: 'string', dflt: '', @@ -141,28 +134,30 @@ module.exports = { flags: ['label', 'text', 'value', 'percent'], extras: ['none'], editType: 'calc', - description: [ - 'Determines which trace information appear on the graph.' - ].join(' ') + description: ['Determines which trace information appear on the graph.'].join(' ') }, hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: hovertemplateAttrs({}, { - keys: ['label', 'color', 'value', 'percent', 'text'] - }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['label', 'color', 'value', 'percent', 'text'] - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: ['label', 'color', 'value', 'percent', 'text'] + } + ), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['label', 'color', 'value', 'percent', 'text'] + } + ), textposition: { valType: 'enumerated', values: ['inside', 'outside', 'auto', 'none'], dflt: 'auto', arrayOk: true, editType: 'plot', - description: [ - 'Specifies the location of the `textinfo`.' - ].join(' ') + description: ['Specifies the location of the `textinfo`.'].join(' ') }, textfont: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `textinfo`.' @@ -192,9 +187,7 @@ module.exports = { valType: 'boolean', dflt: false, editType: 'plot', - description: [ - 'Determines whether outside text labels can push the margins.' - ].join(' ') + description: ['Determines whether outside text labels can push the margins.'].join(' ') }, title: { @@ -202,10 +195,7 @@ module.exports = { valType: 'string', dflt: '', editType: 'plot', - description: [ - 'Sets the title of the chart.', - 'If it is empty, no title is displayed.', - ].join(' ') + description: ['Sets the title of the chart.', 'If it is empty, no title is displayed.'].join(' ') }, font: extendFlat({}, textFontAttrs, { description: 'Sets the font used for `title`.' @@ -213,21 +203,23 @@ module.exports = { position: { valType: 'enumerated', values: [ - 'top left', 'top center', 'top right', + 'top left', + 'top center', + 'top right', 'middle center', - 'bottom left', 'bottom center', 'bottom right' + 'bottom left', + 'bottom center', + 'bottom right' ], editType: 'plot', - description: [ - 'Specifies the location of the `title`.', - ].join(' ') + description: ['Specifies the location of the `title`.'].join(' ') }, editType: 'plot' }, // position and shape - domain: domainAttrs({name: 'pie', trace: true, editType: 'calc'}), + domain: domainAttrs({ name: 'pie', trace: true, editType: 'calc' }), hole: { valType: 'number', @@ -235,10 +227,9 @@ module.exports = { max: 1, dflt: 0, editType: 'calc', - description: [ - 'Sets the fraction of the radius to cut out of the pie.', - 'Use this to make a donut chart.' - ].join(' ') + description: ['Sets the fraction of the radius to cut out of the pie.', 'Use this to make a donut chart.'].join( + ' ' + ) }, // ordering and direction @@ -246,10 +237,7 @@ module.exports = { valType: 'boolean', dflt: true, editType: 'calc', - description: [ - 'Determines whether or not the sectors are reordered', - 'from largest to smallest.' - ].join(' ') + description: ['Determines whether or not the sectors are reordered', 'from largest to smallest.'].join(' ') }, direction: { /** @@ -263,19 +251,13 @@ module.exports = { values: ['clockwise', 'counterclockwise'], dflt: 'counterclockwise', editType: 'calc', - description: [ - 'Specifies the direction at which succeeding sectors follow', - 'one another.' - ].join(' ') + description: ['Specifies the direction at which succeeding sectors follow', 'one another.'].join(' ') }, rotation: { valType: 'angle', dflt: 0, editType: 'calc', - description: [ - 'Instead of the first slice starting at 12 o\'clock,', - 'rotate to some other angle.' - ].join(' ') + description: ["Instead of the first slice starting at 12 o'clock,", 'rotate to some other angle.'].join(' ') }, pull: { @@ -291,5 +273,5 @@ module.exports = { 'to pull all slices apart from each other equally', 'or an array to highlight one or more slices.' ].join(' ') - }, + } }; diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index f2aef62d0e2..1291d04cd79 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -10,23 +10,20 @@ var coercePattern = require('../../lib').coercePattern; function handleLabelsAndValues(labels, values) { var hasLabels = Lib.isArrayOrTypedArray(labels); var hasValues = Lib.isArrayOrTypedArray(values); - var len = Math.min( - hasLabels ? labels.length : Infinity, - hasValues ? values.length : Infinity - ); + var len = Math.min(hasLabels ? labels.length : Infinity, hasValues ? values.length : Infinity); - if(!isFinite(len)) len = 0; + if (!isFinite(len)) len = 0; - if(len && hasValues) { + if (len && hasValues) { var hasPositive; - for(var i = 0; i < len; i++) { + for (var i = 0; i < len; i++) { var v = values[i]; - if(isNumeric(v) && v > 0) { + if (isNumeric(v) && v > 0) { hasPositive = true; break; } } - if(!hasPositive) len = 0; + if (!hasPositive) len = 0; } return { @@ -38,18 +35,18 @@ function handleLabelsAndValues(labels, values) { function handleMarkerDefaults(traceIn, traceOut, layout, coerce, isPie) { var lineWidth = coerce('marker.line.width'); - if(lineWidth) { - coerce('marker.line.color', - isPie ? undefined : - layout.paper_bgcolor // case of funnelarea, sunburst, icicle, treemap + if (lineWidth) { + coerce( + 'marker.line.color', + isPie ? undefined : layout.paper_bgcolor // case of funnelarea, sunburst, icicle, treemap ); } var markerColors = coerce('marker.colors'); coercePattern(coerce, 'marker.pattern', markerColors); // push the marker colors (with s) to the foreground colors, to work around logic in the drawing pattern code on marker.color (without s, which is okay for a bar trace) - if(traceIn.marker && !traceOut.marker.pattern.fgcolor) traceOut.marker.pattern.fgcolor = traceIn.marker.colors; - if(!traceOut.marker.pattern.bgcolor) traceOut.marker.pattern.bgcolor = layout.paper_bgcolor; + if (traceIn.marker && !traceOut.marker.pattern.fgcolor) traceOut.marker.pattern.fgcolor = traceIn.marker.colors; + if (!traceOut.marker.pattern.bgcolor) traceOut.marker.pattern.bgcolor = layout.paper_bgcolor; } function supplyDefaults(traceIn, traceOut, defaultColor, layout) { @@ -65,14 +62,12 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { traceOut._hasLabels = res.hasLabels; traceOut._hasValues = res.hasValues; - if(!traceOut._hasLabels && - traceOut._hasValues - ) { + if (!traceOut._hasLabels && traceOut._hasValues) { coerce('label0'); coerce('dlabel'); } - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -86,12 +81,12 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var textData = coerce('text'); var textTemplate = coerce('texttemplate'); var textInfo; - if(!textTemplate) textInfo = coerce('textinfo', Lib.isArrayOrTypedArray(textData) ? 'text+percent' : 'percent'); + if (!textTemplate) textInfo = coerce('textinfo', Lib.isArrayOrTypedArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); - if(textTemplate || (textInfo && textInfo !== 'none')) { + if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { moduleHasSelected: false, @@ -104,14 +99,14 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var hasBoth = Array.isArray(textposition) || textposition === 'auto'; var hasOutside = hasBoth || textposition === 'outside'; - if(hasOutside) { + if (hasOutside) { coerce('automargin'); } - if(textposition === 'inside' || textposition === 'auto' || Array.isArray(textposition)) { + if (textposition === 'inside' || textposition === 'auto' || Array.isArray(textposition)) { coerce('insidetextorientation'); } - } else if(textInfo === 'none') { + } else if (textInfo === 'none') { coerce('textposition', 'none'); } @@ -119,9 +114,9 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var hole = coerce('hole'); var title = coerce('title.text'); - if(title) { + if (title) { var titlePosition = coerce('title.position', hole ? 'middle center' : 'top center'); - if(!hole && titlePosition === 'middle center') traceOut.title.position = 'top center'; + if (!hole && titlePosition === 'middle center') traceOut.title.position = 'top center'; Lib.coerceFont(coerce, 'title.font', layout.font); } diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index fbb8fd4de29..ea6a5359f97 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -30,7 +30,7 @@ function plot(gd, cdModule) { prerenderTitles(cdModule, gd); layoutAreas(cdModule, gs); - var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function(cd) { + var plotGroups = Lib.makeTraceGroups(fullLayout._pielayer, cdModule, 'trace').each(function (cd) { var plotGroup = d3.select(this); var cd0 = cd[0]; var trace = cd0.trace; @@ -41,11 +41,10 @@ function plot(gd, cdModule) { // maybe miter with a small-ish stroke-miterlimit? plotGroup.attr('stroke-linejoin', 'round'); - plotGroup.each(function() { + plotGroup.each(function () { var slices = d3.select(this).selectAll('g.slice').data(cd); - slices.enter().append('g') - .classed('slice', true); + slices.enter().append('g').classed('slice', true); slices.exit().remove(); var quadrants = [ @@ -54,8 +53,8 @@ function plot(gd, cdModule) { ]; var hasOutsideText = false; - slices.each(function(pt, i) { - if(pt.hidden) { + slices.each(function (pt, i) { + if (pt.hidden) { d3.select(this).selectAll('path,g').remove(); return; } @@ -71,15 +70,17 @@ function plot(gd, cdModule) { var sliceTop = d3.select(this); var slicePath = sliceTop.selectAll('path.surface').data([pt]); - slicePath.enter().append('path') + slicePath + .enter() + .append('path') .classed('surface', true) - .style({'pointer-events': isStatic ? 'none' : 'all'}); + .style({ 'pointer-events': isStatic ? 'none' : 'all' }); sliceTop.call(attachFxHandlers, gd, cd); - if(trace.pull) { + if (trace.pull) { var pull = +helpers.castOption(trace.pull, pt.pts) || 0; - if(pull > 0) { + if (pull > 0) { cx += pull * pt.pxmid[0]; cy += pull * pt.pxmid[1]; } @@ -92,66 +93,94 @@ function plot(gd, cdModule) { var dx = scale * (finish[0] - start[0]); var dy = scale * (finish[1] - start[1]); - return 'a' + - (scale * cd0.r) + ',' + (scale * cd0.r) + ' 0 ' + - pt.largeArc + (cw ? ' 1 ' : ' 0 ') + dx + ',' + dy; + return ( + 'a' + + scale * cd0.r + + ',' + + scale * cd0.r + + ' 0 ' + + pt.largeArc + + (cw ? ' 1 ' : ' 0 ') + + dx + + ',' + + dy + ); } var hole = trace.hole; - if(pt.v === cd0.vTotal) { // 100% fails bcs arc start and end are identical - var outerCircle = 'M' + (cx + pt.px0[0]) + ',' + (cy + pt.px0[1]) + + if (pt.v === cd0.vTotal) { + // 100% fails bcs arc start and end are identical + var outerCircle = + 'M' + + (cx + pt.px0[0]) + + ',' + + (cy + pt.px0[1]) + arc(pt.px0, pt.pxmid, true, 1) + - arc(pt.pxmid, pt.px0, true, 1) + 'Z'; - if(hole) { - slicePath.attr('d', - 'M' + (cx + hole * pt.px0[0]) + ',' + (cy + hole * pt.px0[1]) + - arc(pt.px0, pt.pxmid, false, hole) + - arc(pt.pxmid, pt.px0, false, hole) + - 'Z' + outerCircle); + arc(pt.pxmid, pt.px0, true, 1) + + 'Z'; + if (hole) { + slicePath.attr( + 'd', + 'M' + + (cx + hole * pt.px0[0]) + + ',' + + (cy + hole * pt.px0[1]) + + arc(pt.px0, pt.pxmid, false, hole) + + arc(pt.pxmid, pt.px0, false, hole) + + 'Z' + + outerCircle + ); } else slicePath.attr('d', outerCircle); } else { var outerArc = arc(pt.px0, pt.px1, true, 1); - if(hole) { + if (hole) { var rim = 1 - hole; - slicePath.attr('d', - 'M' + (cx + hole * pt.px1[0]) + ',' + (cy + hole * pt.px1[1]) + - arc(pt.px1, pt.px0, false, hole) + - 'l' + (rim * pt.px0[0]) + ',' + (rim * pt.px0[1]) + - outerArc + - 'Z'); + slicePath.attr( + 'd', + 'M' + + (cx + hole * pt.px1[0]) + + ',' + + (cy + hole * pt.px1[1]) + + arc(pt.px1, pt.px0, false, hole) + + 'l' + + rim * pt.px0[0] + + ',' + + rim * pt.px0[1] + + outerArc + + 'Z' + ); } else { - slicePath.attr('d', - 'M' + cx + ',' + cy + - 'l' + pt.px0[0] + ',' + pt.px0[1] + - outerArc + - 'Z'); + slicePath.attr('d', 'M' + cx + ',' + cy + 'l' + pt.px0[0] + ',' + pt.px0[1] + outerArc + 'Z'); } } // add text formatSliceLabel(gd, pt, cd0); var textPosition = helpers.castOption(trace.textposition, pt.pts); - var sliceTextGroup = sliceTop.selectAll('g.slicetext') - .data(pt.text && (textPosition !== 'none') ? [0] : []); + var sliceTextGroup = sliceTop + .selectAll('g.slicetext') + .data(pt.text && textPosition !== 'none' ? [0] : []); - sliceTextGroup.enter().append('g') - .classed('slicetext', true); + sliceTextGroup.enter().append('g').classed('slicetext', true); sliceTextGroup.exit().remove(); - sliceTextGroup.each(function() { - var sliceText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) { + sliceTextGroup.each(function () { + var sliceText = Lib.ensureSingle(d3.select(this), '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, textPosition === 'outside' ? - determineOutsideTextFont(trace, pt, fullLayout.font) : - determineInsideTextFont(trace, pt, fullLayout.font) + var font = Lib.ensureUniformFontSize( + gd, + textPosition === 'outside' + ? determineOutsideTextFont(trace, pt, fullLayout.font) + : determineInsideTextFont(trace, pt, fullLayout.font) ); - sliceText.text(pt.text) + sliceText + .text(pt.text) .attr({ class: 'slicetext', transform: '', @@ -164,11 +193,11 @@ function plot(gd, cdModule) { var textBB = Drawing.bBox(sliceText.node()); var transform; - if(textPosition === 'outside') { + if (textPosition === 'outside') { transform = transformOutsideText(textBB, pt); } else { transform = transformInsideText(textBB, pt, cd0); - if(textPosition === 'auto' && transform.scale < 1) { + if (textPosition === 'auto' && transform.scale < 1) { var newFont = Lib.ensureUniformFontSize(gd, trace.outsidetextfont); sliceText.call(Drawing.font, newFont); @@ -185,7 +214,7 @@ function plot(gd, cdModule) { computeTransform(transform, textBB); // save some stuff to use later ensure no labels overlap - if(transform.outside) { + if (transform.outside) { var targetY = transform.targetY; pt.yLabelMin = targetY - textBB.height / 2; pt.yLabelMid = targetY; @@ -204,53 +233,57 @@ function plot(gd, cdModule) { }); // add the title - var titleTextGroup = d3.select(this).selectAll('g.titletext') + var titleTextGroup = d3 + .select(this) + .selectAll('g.titletext') .data(trace.title.text ? [0] : []); - titleTextGroup.enter().append('g') - .classed('titletext', true); + titleTextGroup.enter().append('g').classed('titletext', true); titleTextGroup.exit().remove(); - titleTextGroup.each(function() { - var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function(s) { + titleTextGroup.each(function () { + var titleText = Lib.ensureSingle(d3.select(this), 'text', '', function (s) { // prohibit tex interpretation as above s.attr('data-notex', 1); }); var txt = trace.title.text; - if(trace._meta) { + if (trace._meta) { txt = Lib.templateString(txt, trace._meta); } - titleText.text(txt) + titleText + .text(txt) .attr({ class: 'titletext', transform: '', - 'text-anchor': 'middle', + 'text-anchor': 'middle' }) - .call(Drawing.font, trace.title.font) - .call(svgTextUtils.convertToTspans, gd); + .call(Drawing.font, trace.title.font) + .call(svgTextUtils.convertToTspans, gd); var transform; - if(trace.title.position === 'middle center') { + if (trace.title.position === 'middle center') { transform = positionTitleInside(cd0); } else { transform = positionTitleOutside(cd0, gs); } - titleText.attr('transform', + titleText.attr( + 'transform', strTranslate(transform.x, transform.y) + - strScale(Math.min(1, transform.scale)) + - strTranslate(transform.tx, transform.ty)); + strScale(Math.min(1, transform.scale)) + + strTranslate(transform.tx, transform.ty) + ); }); // now make sure no labels overlap (at least within one pie) - if(hasOutsideText) scootLabels(quadrants, trace); + if (hasOutsideText) scootLabels(quadrants, trace); plotTextLines(slices, trace); - if(hasOutsideText && trace.automargin) { + if (hasOutsideText && trace.automargin) { // TODO if we ever want to improve perf, // we could reuse the textBB computed above together // with the sliceText transform info @@ -283,20 +316,20 @@ function plot(gd, cdModule) { // spaced wrong. You just have to tell it to try again later and it gets fixed. // I have no idea why we haven't seen this in other contexts. Also, sometimes // it gets the initial draw correct but on redraw it gets confused. - setTimeout(function() { - plotGroups.selectAll('tspan').each(function() { + setTimeout(function () { + plotGroups.selectAll('tspan').each(function () { var s = d3.select(this); - if(s.attr('dy')) s.attr('dy', s.attr('dy')); + if (s.attr('dy')) s.attr('dy', s.attr('dy')); }); }, 0); } // TODO add support for transition function plotTextLines(slices, trace) { - slices.each(function(pt) { + slices.each(function (pt) { var sliceTop = d3.select(this); - if(!pt.labelExtraX && !pt.labelExtraY) { + if (!pt.labelExtraX && !pt.labelExtraY) { sliceTop.select('path.textline').remove(); return; } @@ -313,25 +346,25 @@ function plotTextLines(slices, trace) { var lineStartX = pt.cxFinal + pt.pxmid[0]; var lineStartY = pt.cyFinal + pt.pxmid[1]; var textLinePath = 'M' + lineStartX + ',' + lineStartY; - var finalX = (pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1) / 4; + var finalX = ((pt.yLabelMax - pt.yLabelMin) * (pt.pxmid[0] < 0 ? -1 : 1)) / 4; - if(pt.labelExtraX) { - var yFromX = pt.labelExtraX * pt.pxmid[1] / pt.pxmid[0]; + if (pt.labelExtraX) { + var yFromX = (pt.labelExtraX * pt.pxmid[1]) / pt.pxmid[0]; var yNet = pt.yLabelMid + pt.labelExtraY - (pt.cyFinal + pt.pxmid[1]); - if(Math.abs(yFromX) > Math.abs(yNet)) { + if (Math.abs(yFromX) > Math.abs(yNet)) { textLinePath += - 'l' + (yNet * pt.pxmid[0] / pt.pxmid[1]) + ',' + yNet + - 'H' + (lineStartX + pt.labelExtraX + finalX); + 'l' + + (yNet * pt.pxmid[0]) / pt.pxmid[1] + + ',' + + yNet + + 'H' + + (lineStartX + pt.labelExtraX + finalX); } else { - textLinePath += 'l' + pt.labelExtraX + ',' + yFromX + - 'v' + (yNet - yFromX) + - 'h' + finalX; + textLinePath += 'l' + pt.labelExtraX + ',' + yFromX + 'v' + (yNet - yFromX) + 'h' + finalX; } } else { - textLinePath += - 'V' + (pt.yLabelMid + pt.labelExtraY) + - 'h' + finalX; + textLinePath += 'V' + (pt.yLabelMid + pt.labelExtraY) + 'h' + finalX; } Lib.ensureSingle(sliceTop, 'path', 'textline') @@ -353,89 +386,96 @@ function attachFxHandlers(sliceTop, gd, cd) { // hover state vars // have we drawn a hover label, so it should be cleared later - if(!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false; + if (!('_hasHoverLabel' in trace)) trace._hasHoverLabel = false; // have we emitted a hover event, so later an unhover event should be emitted // note that click events do not depend on this - you can still get them // with hovermode: false or if you were earlier dragging, then clicked // in the same slice that you moused up in - if(!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false; + if (!('_hasHoverEvent' in trace)) trace._hasHoverEvent = false; - sliceTop.on('mouseover', function(pt) { + sliceTop.on('mouseover', function (pt) { // in case fullLayout or fullData has changed without a replot var fullLayout2 = gd._fullLayout; var trace2 = gd._fullData[trace.index]; - if(gd._dragging || fullLayout2.hovermode === false) return; + if (gd._dragging || fullLayout2.hovermode === false) return; var hoverinfo = trace2.hoverinfo; - if(Array.isArray(hoverinfo)) { + if (Array.isArray(hoverinfo)) { // super hacky: we need to pull out the *first* hoverinfo from // pt.pts, then put it back into an array in a dummy trace // and call castHoverinfo on that. // TODO: do we want to have Fx.castHoverinfo somehow handle this? // it already takes an array for index, for 2D, so this seems tricky. - hoverinfo = Fx.castHoverinfo({ - hoverinfo: [helpers.castOption(hoverinfo, pt.pts)], - _module: trace._module - }, fullLayout2, 0); + hoverinfo = Fx.castHoverinfo( + { + hoverinfo: [helpers.castOption(hoverinfo, pt.pts)], + _module: trace._module + }, + fullLayout2, + 0 + ); } - if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; + if (hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; // in case we dragged over the pie from another subplot, // or if hover is turned off - if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) { + if (trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) { var rInscribed = pt.rInscribed || 0; var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed); var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed); var separators = fullLayout2.separators; var text = []; - if(hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label); + if (hoverinfo && hoverinfo.indexOf('label') !== -1) text.push(pt.label); pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); - if(hoverinfo && hoverinfo.indexOf('text') !== -1) { + if (hoverinfo && hoverinfo.indexOf('text') !== -1) { var tx = pt.text; - if(Lib.isValidTextValue(tx)) text.push(tx); + if (Lib.isValidTextValue(tx)) text.push(tx); } pt.value = pt.v; pt.valueLabel = helpers.formatPieValue(pt.v, separators); - if(hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel); + if (hoverinfo && hoverinfo.indexOf('value') !== -1) text.push(pt.valueLabel); pt.percent = pt.v / cd0.vTotal; pt.percentLabel = helpers.formatPiePercent(pt.percent, separators); - if(hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel); + if (hoverinfo && hoverinfo.indexOf('percent') !== -1) text.push(pt.percentLabel); var hoverLabel = trace2.hoverlabel; var hoverFont = hoverLabel.font; var bbox = []; - Fx.loneHover({ - trace: trace, - x0: hoverCenterX - rInscribed * cd0.r, - x1: hoverCenterX + rInscribed * cd0.r, - y: hoverCenterY, - _x0: isFunnelArea ? cx + pt.TL[0] : hoverCenterX - rInscribed * cd0.r, - _x1: isFunnelArea ? cx + pt.TR[0] : hoverCenterX + rInscribed * cd0.r, - _y0: isFunnelArea ? cy + pt.TL[1] : hoverCenterY - rInscribed * cd0.r, - _y1: isFunnelArea ? cy + pt.BL[1] : hoverCenterY + rInscribed * cd0.r, - text: text.join('
'), - name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined, - idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right', - color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color, - borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts), - fontFamily: helpers.castOption(hoverFont.family, pt.pts), - fontSize: helpers.castOption(hoverFont.size, pt.pts), - fontColor: helpers.castOption(hoverFont.color, pt.pts), - nameLength: helpers.castOption(hoverLabel.namelength, pt.pts), - textAlign: helpers.castOption(hoverLabel.align, pt.pts), - hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts), - hovertemplateLabels: pt, - eventData: [eventData(pt, trace2)] - }, { - container: fullLayout2._hoverlayer.node(), - outerContainer: fullLayout2._paper.node(), - gd: gd, - inOut_bbox: bbox - }); + Fx.loneHover( + { + trace: trace, + x0: hoverCenterX - rInscribed * cd0.r, + x1: hoverCenterX + rInscribed * cd0.r, + y: hoverCenterY, + _x0: isFunnelArea ? cx + pt.TL[0] : hoverCenterX - rInscribed * cd0.r, + _x1: isFunnelArea ? cx + pt.TR[0] : hoverCenterX + rInscribed * cd0.r, + _y0: isFunnelArea ? cy + pt.TL[1] : hoverCenterY - rInscribed * cd0.r, + _y1: isFunnelArea ? cy + pt.BL[1] : hoverCenterY + rInscribed * cd0.r, + text: text.join('
'), + name: trace2.hovertemplate || hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined, + idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right', + color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color, + borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts), + fontFamily: helpers.castOption(hoverFont.family, pt.pts), + fontSize: helpers.castOption(hoverFont.size, pt.pts), + fontColor: helpers.castOption(hoverFont.color, pt.pts), + nameLength: helpers.castOption(hoverLabel.namelength, pt.pts), + textAlign: helpers.castOption(hoverLabel.align, pt.pts), + hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts), + hovertemplateLabels: pt, + eventData: [eventData(pt, trace2)] + }, + { + container: fullLayout2._hoverlayer.node(), + outerContainer: fullLayout2._paper.node(), + gd: gd, + inOut_bbox: bbox + } + ); pt.bbox = bbox[0]; trace._hasHoverLabel = true; @@ -448,12 +488,12 @@ function attachFxHandlers(sliceTop, gd, cd) { }); }); - sliceTop.on('mouseout', function(evt) { + sliceTop.on('mouseout', function (evt) { var fullLayout2 = gd._fullLayout; var trace2 = gd._fullData[trace.index]; var pt = d3.select(this).datum(); - if(trace._hasHoverEvent) { + if (trace._hasHoverEvent) { evt.originalEvent = d3.event; gd.emit('plotly_unhover', { points: [eventData(pt, trace2)], @@ -462,13 +502,13 @@ function attachFxHandlers(sliceTop, gd, cd) { trace._hasHoverEvent = false; } - if(trace._hasHoverLabel) { + if (trace._hasHoverLabel) { Fx.loneUnhover(fullLayout2._hoverlayer.node()); trace._hasHoverLabel = false; } }); - sliceTop.on('click', function(pt) { + sliceTop.on('click', function (pt) { // TODO: this does not support right-click. If we want to support it, we // would likely need to change pie to use dragElement instead of straight // map subplot event binding. Or perhaps better, make a simple wrapper with the @@ -477,7 +517,7 @@ function attachFxHandlers(sliceTop, gd, cd) { var fullLayout2 = gd._fullLayout; var trace2 = gd._fullData[trace.index]; - if(gd._dragging || fullLayout2.hovermode === false) return; + if (gd._dragging || fullLayout2.hovermode === false) return; gd._hoverdata = [eventData(pt, trace2)]; Fx.click(gd, d3.event); @@ -539,13 +579,13 @@ function determineOutsideTextFont(trace, pt, layoutFont) { variant: variant, textcase: textcase, lineposition: lineposition, - shadow: shadow, + shadow: shadow }; } function determineInsideTextFont(trace, pt, layoutFont) { var customColor = helpers.castOption(trace.insidetextfont.color, pt.pts); - if(!customColor && trace._input.textfont) { + if (!customColor && trace._input.textfont) { // Why not simply using trace.textfont? Because if not set, it // defaults to layout.font which has a default color. But if // textfont.color and insidetextfont.color don't supply a value, @@ -602,7 +642,7 @@ function determineInsideTextFont(trace, pt, layoutFont) { variant: variant, textcase: textcase, lineposition: lineposition, - shadow: shadow, + shadow: shadow }; } @@ -610,25 +650,26 @@ function prerenderTitles(cdModule, gd) { var cd0, trace; // Determine the width and height of the title for each pie. - for(var i = 0; i < cdModule.length; i++) { + for (var i = 0; i < cdModule.length; i++) { cd0 = cdModule[i][0]; trace = cd0.trace; - if(trace.title.text) { + if (trace.title.text) { var txt = trace.title.text; - if(trace._meta) { + if (trace._meta) { txt = Lib.templateString(txt, trace._meta); } - var dummyTitle = Drawing.tester.append('text') - .attr('data-notex', 1) - .text(txt) - .call(Drawing.font, trace.title.font) - .call(svgTextUtils.convertToTspans, gd); + var dummyTitle = Drawing.tester + .append('text') + .attr('data-notex', 1) + .text(txt) + .call(Drawing.font, trace.title.font) + .call(svgTextUtils.convertToTspans, gd); var bBox = Drawing.bBox(dummyTitle.node(), true); cd0.titleBox = { width: bBox.width, - height: bBox.height, + height: bBox.height }; dummyTitle.remove(); } @@ -640,7 +681,7 @@ function transformInsideText(textBB, pt, cd0) { var rInscribed = pt.rInscribed; var isEmpty = pt.startangle === pt.stopangle; - if(isEmpty) { + if (isEmpty) { return { rCenter: 1 - rInscribed, scale: 0, @@ -650,7 +691,7 @@ function transformInsideText(textBB, pt, cd0) { } var ring = pt.ring; - var isCircle = (ring === 1) && (Math.abs(pt.startangle - pt.stopangle) === Math.PI * 2); + var isCircle = ring === 1 && Math.abs(pt.startangle - pt.stopangle) === Math.PI * 2; var halfAngle = pt.halfangle; var midAngle = pt.midangle; @@ -664,19 +705,20 @@ function transformInsideText(textBB, pt, cd0) { var allTransforms = []; var newT; - if(!isAuto) { + if (!isAuto) { // max size if text is placed (horizontally) at the top or bottom of the arc - var considerCrossing = function(angle, key) { - if(isCrossing(pt, angle)) { + var considerCrossing = function (angle, key) { + if (isCrossing(pt, angle)) { var dStart = Math.abs(angle - pt.startangle); var dStop = Math.abs(angle - pt.stopangle); var closestEdge = dStart < dStop ? dStart : dStop; - if(key === 'tan') { + if (key === 'tan') { newT = calcTanTransform(textBB, r, ring, closestEdge, 0); - } else { // case of 'rad' + } else { + // case of 'rad' newT = calcRadTransform(textBB, r, ring, closestEdge, Math.PI / 2); } newT.textPosAngle = angle; @@ -687,21 +729,21 @@ function transformInsideText(textBB, pt, cd0) { // to cover all cases with trace.rotation added var i; - if(isHorizontal || isTangential) { + if (isHorizontal || isTangential) { // top - for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * i, 'tan'); + for (i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * i, 'tan'); // bottom - for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1), 'tan'); + for (i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1), 'tan'); } - if(isHorizontal || isRadial) { + if (isHorizontal || isRadial) { // left - for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1.5), 'rad'); + for (i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 1.5), 'rad'); // right - for(i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 0.5), 'rad'); + for (i = 4; i >= -4; i -= 2) considerCrossing(Math.PI * (i + 0.5), 'rad'); } } - if(isCircle || isAuto || isHorizontal) { + if (isCircle || isAuto || isHorizontal) { // max size text can be inserted inside without rotating it // this inscribes the text rectangle in a circle, which is then inscribed // in the slice, so it will be an underestimate, which some day we may want @@ -709,7 +751,7 @@ function transformInsideText(textBB, pt, cd0) { var textDiameter = Math.sqrt(textBB.width * textBB.width + textBB.height * textBB.height); newT = { - scale: rInscribed * r * 2 / textDiameter, + scale: (rInscribed * r * 2) / textDiameter, // and the center position and rotation in this case rCenter: 1 - rInscribed, @@ -717,18 +759,18 @@ function transformInsideText(textBB, pt, cd0) { }; newT.textPosAngle = (pt.startangle + pt.stopangle) / 2; - if(newT.scale >= 1) return newT; + if (newT.scale >= 1) return newT; allTransforms.push(newT); } - if(isAuto || isRadial) { + if (isAuto || isRadial) { newT = calcRadTransform(textBB, r, ring, halfAngle, midAngle); newT.textPosAngle = (pt.startangle + pt.stopangle) / 2; allTransforms.push(newT); } - if(isAuto || isTangential) { + if (isAuto || isTangential) { newT = calcTanTransform(textBB, r, ring, halfAngle, midAngle); newT.textPosAngle = (pt.startangle + pt.stopangle) / 2; allTransforms.push(newT); @@ -736,14 +778,14 @@ function transformInsideText(textBB, pt, cd0) { var id = 0; var maxScale = 0; - for(var k = 0; k < allTransforms.length; k++) { + for (var k = 0; k < allTransforms.length; k++) { var s = allTransforms[k].scale; - if(maxScale < s) { + if (maxScale < s) { maxScale = s; id = k; } - if(!isAuto && maxScale >= 1) { + if (!isAuto && maxScale >= 1) { // respect test order for non-auto options break; } @@ -754,10 +796,7 @@ function transformInsideText(textBB, pt, cd0) { function isCrossing(pt, angle) { var start = pt.startangle; var stop = pt.stopangle; - return ( - (start > angle && angle > stop) || - (start < angle && angle < stop) - ); + return (start > angle && angle > stop) || (start < angle && angle < stop); } function calcRadTransform(textBB, r, ring, halfAngle, midAngle) { @@ -767,7 +806,7 @@ function calcRadTransform(textBB, r, ring, halfAngle, midAngle) { var a = textBB.width / textBB.height; var s = calcMaxHalfSize(a, halfAngle, r, ring); return { - scale: s * 2 / textBB.height, + scale: (s * 2) / textBB.height, rCenter: calcRCenter(a, s / r), rotate: calcRotate(midAngle) }; @@ -780,7 +819,7 @@ function calcTanTransform(textBB, r, ring, halfAngle, midAngle) { var a = textBB.height / textBB.width; var s = calcMaxHalfSize(a, halfAngle, r, ring); return { - scale: s * 2 / textBB.width, + scale: (s * 2) / textBB.width, rCenter: calcRCenter(a, s / r), rotate: calcRotate(midAngle + Math.PI / 2) }; @@ -791,19 +830,16 @@ function calcRCenter(a, b) { } function calcRotate(t) { - return (180 / Math.PI * t + 720) % 180 - 90; + return (((180 / Math.PI) * t + 720) % 180) - 90; } function calcMaxHalfSize(a, halfAngle, r, ring) { var q = a + 1 / (2 * Math.tan(halfAngle)); - return r * Math.min( - 1 / (Math.sqrt(q * q + 0.5) + q), - ring / (Math.sqrt(a * a + ring / 2) + a) - ); + return r * Math.min(1 / (Math.sqrt(q * q + 0.5) + q), ring / (Math.sqrt(a * a + ring / 2) + a)); } function getInscribedRadiusFraction(pt, cd0) { - if(pt.v === cd0.vTotal && !cd0.trace.hole) return 1;// special case of 100% with no hole + if (pt.v === cd0.vTotal && !cd0.trace.hole) return 1; // special case of 100% with no hole return Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2); } @@ -814,28 +850,27 @@ function transformOutsideText(textBB, pt) { var dx = textBB.width / 2; var dy = textBB.height / 2; - if(x < 0) dx *= -1; - if(y < 0) dy *= -1; + if (x < 0) dx *= -1; + if (y < 0) dy *= -1; return { scale: 1, rCenter: 1, rotate: 0, - x: dx + Math.abs(dy) * (dx > 0 ? 1 : -1) / 2, - y: dy / (1 + x * x / (y * y)), + x: dx + (Math.abs(dy) * (dx > 0 ? 1 : -1)) / 2, + y: dy / (1 + (x * x) / (y * y)), outside: true }; } function positionTitleInside(cd0) { - var textDiameter = - Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height); + var textDiameter = Math.sqrt(cd0.titleBox.width * cd0.titleBox.width + cd0.titleBox.height * cd0.titleBox.height); return { x: cd0.cx, y: cd0.cy, - scale: cd0.trace.hole * cd0.r * 2 / textDiameter, + scale: (cd0.trace.hole * cd0.r * 2) / textDiameter, tx: 0, - ty: - cd0.titleBox.height / 2 + cd0.trace.title.font.size + ty: -cd0.titleBox.height / 2 + cd0.trace.title.font.size }; } @@ -864,24 +899,24 @@ function positionTitleOutside(cd0, plotSize) { translate.ty += trace.title.font.size; maxPull = getMaxPull(trace); - if(trace.title.position.indexOf('top') !== -1) { + if (trace.title.position.indexOf('top') !== -1) { topMiddle.y -= (1 + maxPull) * cd0.r; translate.ty -= cd0.titleBox.height; - } else if(trace.title.position.indexOf('bottom') !== -1) { + } else if (trace.title.position.indexOf('bottom') !== -1) { topMiddle.y += (1 + maxPull) * cd0.r; } var rx = applyAspectRatio(cd0.r, cd0.trace.aspectratio); - var maxWidth = plotSize.w * (trace.domain.x[1] - trace.domain.x[0]) / 2; - if(trace.title.position.indexOf('left') !== -1) { + var maxWidth = (plotSize.w * (trace.domain.x[1] - trace.domain.x[0])) / 2; + if (trace.title.position.indexOf('left') !== -1) { // we start the text at the left edge of the pie maxWidth = maxWidth + rx; topMiddle.x -= (1 + maxPull) * rx; translate.tx += cd0.titleBox.width / 2; - } else if(trace.title.position.indexOf('center') !== -1) { + } else if (trace.title.position.indexOf('center') !== -1) { maxWidth *= 2; - } else if(trace.title.position.indexOf('right') !== -1) { + } else if (trace.title.position.indexOf('right') !== -1) { maxWidth = maxWidth + rx; topMiddle.x += (1 + maxPull) * rx; translate.tx -= cd0.titleBox.width / 2; @@ -898,7 +933,7 @@ function positionTitleOutside(cd0, plotSize) { } function applyAspectRatio(x, aspectratio) { - return x / ((aspectratio === undefined) ? 1 : aspectratio); + return x / (aspectratio === undefined ? 1 : aspectratio); } function getTitleSpace(cd0, plotSize) { @@ -910,28 +945,42 @@ function getTitleSpace(cd0, plotSize) { function getMaxPull(trace) { var maxPull = trace.pull; - if(!maxPull) return 0; + if (!maxPull) return 0; var j; - if(Lib.isArrayOrTypedArray(maxPull)) { + if (Lib.isArrayOrTypedArray(maxPull)) { maxPull = 0; - for(j = 0; j < trace.pull.length; j++) { - if(trace.pull[j] > maxPull) maxPull = trace.pull[j]; + for (j = 0; j < trace.pull.length; j++) { + if (trace.pull[j] > maxPull) maxPull = trace.pull[j]; } } return maxPull; } function scootLabels(quadrants, trace) { - var xHalf, yHalf, equatorFirst, farthestX, farthestY, - xDiffSign, yDiffSign, thisQuad, oppositeQuad, - wholeSide, i, thisQuadOutside, firstOppositeOutsidePt; - - function topFirst(a, b) { return a.pxmid[1] - b.pxmid[1]; } - function bottomFirst(a, b) { return b.pxmid[1] - a.pxmid[1]; } + var xHalf, + yHalf, + equatorFirst, + farthestX, + farthestY, + xDiffSign, + yDiffSign, + thisQuad, + oppositeQuad, + wholeSide, + i, + thisQuadOutside, + firstOppositeOutsidePt; + + function topFirst(a, b) { + return a.pxmid[1] - b.pxmid[1]; + } + function bottomFirst(a, b) { + return b.pxmid[1] - a.pxmid[1]; + } function scootOneLabel(thisPt, prevPt) { - if(!prevPt) prevPt = {}; + if (!prevPt) prevPt = {}; var prevOuterY = prevPt.labelExtraY + (yHalf ? prevPt.yLabelMax : prevPt.yLabelMin); var thisInnerY = yHalf ? thisPt.yLabelMin : thisPt.yLabelMax; @@ -943,30 +992,30 @@ function scootLabels(quadrants, trace) { // make sure this label doesn't overlap other labels // this *only* has us move these labels vertically - if(newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY; + if (newExtraY * yDiffSign > 0) thisPt.labelExtraY = newExtraY; // make sure this label doesn't overlap any slices - if(!Lib.isArrayOrTypedArray(trace.pull)) return; // this can only happen with array pulls + if (!Lib.isArrayOrTypedArray(trace.pull)) return; // this can only happen with array pulls - for(i = 0; i < wholeSide.length; i++) { + for (i = 0; i < wholeSide.length; i++) { otherPt = wholeSide[i]; // overlap can only happen if the other point is pulled more than this one - if(otherPt === thisPt || ( - (helpers.castOption(trace.pull, thisPt.pts) || 0) >= - (helpers.castOption(trace.pull, otherPt.pts) || 0)) + if ( + otherPt === thisPt || + (helpers.castOption(trace.pull, thisPt.pts) || 0) >= (helpers.castOption(trace.pull, otherPt.pts) || 0) ) { continue; } - if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) { + if ((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) { // closer to the equator - by construction all of these happen first // move the text vertically to get away from these slices otherOuterY = otherPt.cyFinal + farthestY(otherPt.px0[1], otherPt.px1[1]); newExtraY = otherOuterY - thisInnerY - thisPt.labelExtraY; - if(newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY; - } else if((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) { + if (newExtraY * yDiffSign > 0) thisPt.labelExtraY += newExtraY; + } else if ((thisOuterY + thisPt.labelExtraY - thisSliceOuterY) * yDiffSign > 0) { // farther from the equator - happens after we've done all the // vertical moving we're going to do // move horizontally to get away from these more polar slices @@ -978,17 +1027,17 @@ function scootLabels(quadrants, trace) { otherOuterX = otherPt.cxFinal + farthestX(otherPt.px0[0], otherPt.px1[0]); newExtraX = otherOuterX + xBuffer - (thisPt.cxFinal + thisPt.pxmid[0]) - thisPt.labelExtraX; - if(newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX; + if (newExtraX * xDiffSign > 0) thisPt.labelExtraX += newExtraX; } } } - for(yHalf = 0; yHalf < 2; yHalf++) { + for (yHalf = 0; yHalf < 2; yHalf++) { equatorFirst = yHalf ? topFirst : bottomFirst; farthestY = yHalf ? Math.max : Math.min; yDiffSign = yHalf ? 1 : -1; - for(xHalf = 0; xHalf < 2; xHalf++) { + for (xHalf = 0; xHalf < 2; xHalf++) { farthestX = xHalf ? Math.max : Math.min; xDiffSign = xHalf ? 1 : -1; @@ -1002,25 +1051,25 @@ function scootLabels(quadrants, trace) { wholeSide = oppositeQuad.concat(thisQuad); thisQuadOutside = []; - for(i = 0; i < thisQuad.length; i++) { - if(thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]); + for (i = 0; i < thisQuad.length; i++) { + if (thisQuad[i].yLabelMid !== undefined) thisQuadOutside.push(thisQuad[i]); } firstOppositeOutsidePt = false; - for(i = 0; yHalf && i < oppositeQuad.length; i++) { - if(oppositeQuad[i].yLabelMid !== undefined) { + for (i = 0; yHalf && i < oppositeQuad.length; i++) { + if (oppositeQuad[i].yLabelMid !== undefined) { firstOppositeOutsidePt = oppositeQuad[i]; break; } } // each needs to avoid the previous - for(i = 0; i < thisQuadOutside.length; i++) { + for (i = 0; i < thisQuadOutside.length; i++) { var prevPt = i && thisQuadOutside[i - 1]; // bottom half needs to avoid the first label of the top half // top half we still need to call scootOneLabel on the first slice // so we can avoid other slices, but we don't pass a prevPt - if(firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt; + if (firstOppositeOutsidePt && !i) prevPt = firstOppositeOutsidePt; scootOneLabel(thisQuadOutside[i], prevPt); } } @@ -1031,7 +1080,7 @@ function layoutAreas(cdModule, plotSize) { var scaleGroups = []; // figure out the center and maximum radius - for(var i = 0; i < cdModule.length; i++) { + for (var i = 0; i < cdModule.length; i++) { var cd0 = cdModule[i][0]; var trace = cd0.trace; @@ -1039,25 +1088,25 @@ function layoutAreas(cdModule, plotSize) { var width = plotSize.w * (domain.x[1] - domain.x[0]); var height = plotSize.h * (domain.y[1] - domain.y[0]); // leave some space for the title, if it will be displayed outside - if(trace.title.text && trace.title.position !== 'middle center') { + if (trace.title.text && trace.title.position !== 'middle center') { height -= getTitleSpace(cd0, plotSize); } var rx = width / 2; var ry = height / 2; - if(trace.type === 'funnelarea' && !trace.scalegroup) { + if (trace.type === 'funnelarea' && !trace.scalegroup) { ry /= trace.aspectratio; } cd0.r = Math.min(rx, ry) / (1 + getMaxPull(trace)); - cd0.cx = plotSize.l + plotSize.w * (trace.domain.x[1] + trace.domain.x[0]) / 2; + cd0.cx = plotSize.l + (plotSize.w * (trace.domain.x[1] + trace.domain.x[0])) / 2; cd0.cy = plotSize.t + plotSize.h * (1 - trace.domain.y[0]) - height / 2; - if(trace.title.text && trace.title.position.indexOf('bottom') !== -1) { + if (trace.title.text && trace.title.position.indexOf('bottom') !== -1) { cd0.cy -= getTitleSpace(cd0, plotSize); } - if(trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) { + if (trace.scalegroup && scaleGroups.indexOf(trace.scalegroup) === -1) { scaleGroups.push(trace.scalegroup); } } @@ -1069,22 +1118,22 @@ function groupScale(cdModule, scaleGroups) { var cd0, i, trace; // scale those that are grouped - for(var k = 0; k < scaleGroups.length; k++) { + for (var k = 0; k < scaleGroups.length; k++) { var min = Infinity; var g = scaleGroups[k]; - for(i = 0; i < cdModule.length; i++) { + for (i = 0; i < cdModule.length; i++) { cd0 = cdModule[i][0]; trace = cd0.trace; - if(trace.scalegroup === g) { + if (trace.scalegroup === g) { var area; - if(trace.type === 'pie') { + if (trace.type === 'pie') { area = cd0.r * cd0.r; - } else if(trace.type === 'funnelarea') { + } else if (trace.type === 'funnelarea') { var rx, ry; - if(trace.aspectratio > 1) { + if (trace.aspectratio > 1) { rx = cd0.r; ry = rx / trace.aspectratio; } else { @@ -1101,12 +1150,12 @@ function groupScale(cdModule, scaleGroups) { } } - for(i = 0; i < cdModule.length; i++) { + for (i = 0; i < cdModule.length; i++) { cd0 = cdModule[i][0]; trace = cd0.trace; - if(trace.scalegroup === g) { + if (trace.scalegroup === g) { var v = min * cd0.vTotal; - if(trace.type === 'funnelarea') { + if (trace.type === 'funnelarea') { v /= (1 + trace.baseratio) / 2; v /= trace.aspectratio; } @@ -1122,17 +1171,17 @@ function setCoords(cd) { var r = cd0.r; var trace = cd0.trace; var currentAngle = helpers.getRotationAngle(trace.rotation); - var angleFactor = 2 * Math.PI / cd0.vTotal; + var angleFactor = (2 * Math.PI) / cd0.vTotal; var firstPt = 'px0'; var lastPt = 'px1'; var i, cdi, currentCoords; - if(trace.direction === 'counterclockwise') { - for(i = 0; i < cd.length; i++) { - if(!cd[i].hidden) break; // find the first non-hidden slice + if (trace.direction === 'counterclockwise') { + for (i = 0; i < cd.length; i++) { + if (!cd[i].hidden) break; // find the first non-hidden slice } - if(i === cd.length) return; // all slices hidden + if (i === cd.length) return; // all slices hidden currentAngle += angleFactor * cd[i].v; angleFactor *= -1; @@ -1142,23 +1191,23 @@ function setCoords(cd) { currentCoords = getCoords(r, currentAngle); - for(i = 0; i < cd.length; i++) { + for (i = 0; i < cd.length; i++) { cdi = cd[i]; - if(cdi.hidden) continue; + if (cdi.hidden) continue; cdi[firstPt] = currentCoords; cdi.startangle = currentAngle; - currentAngle += angleFactor * cdi.v / 2; + currentAngle += (angleFactor * cdi.v) / 2; cdi.pxmid = getCoords(r, currentAngle); cdi.midangle = currentAngle; - currentAngle += angleFactor * cdi.v / 2; + currentAngle += (angleFactor * cdi.v) / 2; currentCoords = getCoords(r, currentAngle); cdi.stopangle = currentAngle; cdi[lastPt] = currentCoords; - cdi.largeArc = (cdi.v > cd0.vTotal / 2) ? 1 : 0; + cdi.largeArc = cdi.v > cd0.vTotal / 2 ? 1 : 0; cdi.halfangle = Math.PI * Math.min(cdi.v / cd0.vTotal, 0.5); cdi.ring = 1 - trace.hole; @@ -1178,9 +1227,11 @@ function formatSliceLabel(gd, pt, cd0) { // now insert text var textinfo = trace.textinfo; - if(!texttemplate && textinfo && textinfo !== 'none') { + if (!texttemplate && textinfo && textinfo !== 'none') { var parts = textinfo.split('+'); - var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; }; + var hasFlag = function (flag) { + return parts.indexOf(flag) !== -1; + }; var hasLabel = hasFlag('label'); var hasText = hasFlag('text'); var hasValue = hasFlag('value'); @@ -1190,12 +1241,12 @@ function formatSliceLabel(gd, pt, cd0) { var text; text = hasLabel ? [pt.label] : []; - if(hasText) { + if (hasText) { var tx = helpers.getFirstFilled(trace.text, pt.pts); - if(isValidTextValue(tx)) text.push(tx); + if (isValidTextValue(tx)) text.push(tx); } - if(hasValue) text.push(helpers.formatPieValue(pt.v, separators)); - if(hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); + if (hasValue) text.push(helpers.formatPieValue(pt.v, separators)); + if (hasPercent) text.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); pt.text = text.join('
'); } @@ -1212,24 +1263,24 @@ function formatSliceLabel(gd, pt, cd0) { }; } - if(texttemplate) { + if (texttemplate) { var txt = Lib.castOption(trace, pt.i, 'texttemplate'); - if(!txt) { + if (!txt) { pt.text = ''; } else { var obj = makeTemplateVariables(pt); var ptTx = helpers.getFirstFilled(trace.text, pt.pts); - if(isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; + if (isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {}); } } } function computeTransform( - transform, // inout - textBB // in + transform, // inout + textBB // in ) { - var a = transform.rotate * Math.PI / 180; + var a = (transform.rotate * Math.PI) / 180; var cosA = Math.cos(a); var sinA = Math.sin(a); var midX = (textBB.left + textBB.right) / 2; diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index 98a04b99db2..e533a76a279 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -13,276 +13,277 @@ var descriptionOnlyNumbers = require('../../plots/cartesian/axis_format_attribut var extendFlat = require('../../lib/extend').extendFlat; var overrideAll = require('../../plot_api/edit_types').overrideAll; -var attrs = module.exports = overrideAll({ - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: [], - arrayOk: false, - description: [ - 'Determines which trace information appear on hover.', - 'If `none` or `skip` are set, no information is displayed upon hovering.', - 'But, if `none` is set, click and hover events are still fired.', - 'Note that this attribute is superseded by `node.hoverinfo` and `node.hoverinfo`', - 'for nodes and links respectively.' - ].join(' ') - }), - hoverlabel: fxAttrs.hoverlabel, - domain: domainAttrs({name: 'sankey', trace: true}), - - orientation: { - valType: 'enumerated', - values: ['v', 'h'], - dflt: 'h', - description: 'Sets the orientation of the Sankey diagram.' - }, - - valueformat: { - valType: 'string', - dflt: '.3s', - description: descriptionOnlyNumbers('value') - }, - - valuesuffix: { - valType: 'string', - dflt: '', - description: [ - 'Adds a unit to follow the value in the hover tooltip. Add a space if a separation', - 'is necessary from the value.' - ].join(' ') - }, - - arrangement: { - valType: 'enumerated', - values: ['snap', 'perpendicular', 'freeform', 'fixed'], - dflt: 'snap', - description: [ - 'If value is `snap` (the default), the node arrangement is assisted by automatic snapping of elements to', - 'preserve space between nodes specified via `nodepad`.', - 'If value is `perpendicular`, the nodes can only move along a line perpendicular to the flow.', - 'If value is `freeform`, the nodes can freely move on the plane.', - 'If value is `fixed`, the nodes are stationary.' - ].join(' ') - }, - - textfont: fontAttrs({ - autoShadowDflt: true, - description: 'Sets the font for node labels' - }), - - // Remove top-level customdata - customdata: undefined, - - node: { - label: { - valType: 'data_array', - dflt: [], - description: 'The shown name of the node.' - }, - groups: { - valType: 'info_array', - impliedEdits: {x: [], y: []}, - dimensions: 2, - freeLength: true, - dflt: [], - items: {valType: 'number', editType: 'calc'}, +var attrs = (module.exports = overrideAll( + { + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: [], + arrayOk: false, description: [ - 'Groups of nodes.', - 'Each group is defined by an array with the indices of the nodes it contains.', - 'Multiple groups can be specified.' + 'Determines which trace information appear on hover.', + 'If `none` or `skip` are set, no information is displayed upon hovering.', + 'But, if `none` is set, click and hover events are still fired.', + 'Note that this attribute is superseded by `node.hoverinfo` and `node.hoverinfo`', + 'for nodes and links respectively.' ].join(' ') + }), + hoverlabel: fxAttrs.hoverlabel, + domain: domainAttrs({ name: 'sankey', trace: true }), + + orientation: { + valType: 'enumerated', + values: ['v', 'h'], + dflt: 'h', + description: 'Sets the orientation of the Sankey diagram.' }, - x: { - valType: 'data_array', - dflt: [], - description: 'The normalized horizontal position of the node.' - }, - y: { - valType: 'data_array', - dflt: [], - description: 'The normalized vertical position of the node.' + + valueformat: { + valType: 'string', + dflt: '.3s', + description: descriptionOnlyNumbers('value') }, - color: { - valType: 'color', - arrayOk: true, + + valuesuffix: { + valType: 'string', + dflt: '', description: [ - 'Sets the `node` color. It can be a single value, or an array for specifying color for each `node`.', - 'If `node.color` is omitted, then the default `Plotly` color palette will be cycled through', - 'to have a variety of colors. These defaults are not fully opaque, to allow some visibility of', - 'what is beneath the node.' + 'Adds a unit to follow the value in the hover tooltip. Add a space if a separation', + 'is necessary from the value.' ].join(' ') }, - customdata: { - valType: 'data_array', - editType: 'calc', + + arrangement: { + valType: 'enumerated', + values: ['snap', 'perpendicular', 'freeform', 'fixed'], + dflt: 'snap', description: [ - 'Assigns extra data to each node.' + 'If value is `snap` (the default), the node arrangement is assisted by automatic snapping of elements to', + 'preserve space between nodes specified via `nodepad`.', + 'If value is `perpendicular`, the nodes can only move along a line perpendicular to the flow.', + 'If value is `freeform`, the nodes can freely move on the plane.', + 'If value is `fixed`, the nodes are stationary.' ].join(' ') }, - line: { + + textfont: fontAttrs({ + autoShadowDflt: true, + description: 'Sets the font for node labels' + }), + + // Remove top-level customdata + customdata: undefined, + + node: { + label: { + valType: 'data_array', + dflt: [], + description: 'The shown name of the node.' + }, + groups: { + valType: 'info_array', + impliedEdits: { x: [], y: [] }, + dimensions: 2, + freeLength: true, + dflt: [], + items: { valType: 'number', editType: 'calc' }, + description: [ + 'Groups of nodes.', + 'Each group is defined by an array with the indices of the nodes it contains.', + 'Multiple groups can be specified.' + ].join(' ') + }, + x: { + valType: 'data_array', + dflt: [], + description: 'The normalized horizontal position of the node.' + }, + y: { + valType: 'data_array', + dflt: [], + description: 'The normalized vertical position of the node.' + }, color: { valType: 'color', - dflt: colorAttrs.defaultLine, arrayOk: true, description: [ - 'Sets the color of the `line` around each `node`.' + 'Sets the `node` color. It can be a single value, or an array for specifying color for each `node`.', + 'If `node.color` is omitted, then the default `Plotly` color palette will be cycled through', + 'to have a variety of colors. These defaults are not fully opaque, to allow some visibility of', + 'what is beneath the node.' ].join(' ') }, - width: { + customdata: { + valType: 'data_array', + editType: 'calc', + description: ['Assigns extra data to each node.'].join(' ') + }, + line: { + color: { + valType: 'color', + dflt: colorAttrs.defaultLine, + arrayOk: true, + description: ['Sets the color of the `line` around each `node`.'].join(' ') + }, + width: { + valType: 'number', + min: 0, + dflt: 0.5, + arrayOk: true, + description: ['Sets the width (in px) of the `line` around each `node`.'].join(' ') + } + }, + pad: { valType: 'number', + arrayOk: false, min: 0, - dflt: 0.5, - arrayOk: true, + dflt: 20, + description: 'Sets the padding (in px) between the `nodes`.' + }, + thickness: { + valType: 'number', + arrayOk: false, + min: 1, + dflt: 20, + description: 'Sets the thickness (in px) of the `nodes`.' + }, + hoverinfo: { + valType: 'enumerated', + values: ['all', 'none', 'skip'], + dflt: 'all', description: [ - 'Sets the width (in px) of the `line` around each `node`.' + 'Determines which trace information appear when hovering nodes.', + 'If `none` or `skip` are set, no information is displayed upon hovering.', + 'But, if `none` is set, click and hover events are still fired.' ].join(' ') - } - }, - pad: { - valType: 'number', - arrayOk: false, - min: 0, - dflt: 20, - description: 'Sets the padding (in px) between the `nodes`.' - }, - thickness: { - valType: 'number', - arrayOk: false, - min: 1, - dflt: 20, - description: 'Sets the thickness (in px) of the `nodes`.' - }, - hoverinfo: { - valType: 'enumerated', - values: ['all', 'none', 'skip'], - dflt: 'all', - description: [ - 'Determines which trace information appear when hovering nodes.', - 'If `none` or `skip` are set, no information is displayed upon hovering.', - 'But, if `none` is set, click and hover events are still fired.' - ].join(' ') - }, - hoverlabel: fxAttrs.hoverlabel, // needs editType override, - hovertemplate: hovertemplateAttrs({}, { - description: 'Variables `sourceLinks` and `targetLinks` are arrays of link objects.', - keys: ['value', 'label'] - }), - align: { - valType: 'enumerated', - values: ['justify', 'left', 'right', 'center'], - dflt: 'justify', - description: 'Sets the alignment method used to position the nodes along the horizontal axis.' + }, + hoverlabel: fxAttrs.hoverlabel, // needs editType override, + hovertemplate: hovertemplateAttrs( + {}, + { + description: 'Variables `sourceLinks` and `targetLinks` are arrays of link objects.', + keys: ['value', 'label'] + } + ), + align: { + valType: 'enumerated', + values: ['justify', 'left', 'right', 'center'], + dflt: 'justify', + description: 'Sets the alignment method used to position the nodes along the horizontal axis.' + }, + description: 'The nodes of the Sankey plot.' }, - description: 'The nodes of the Sankey plot.' - }, - link: { - arrowlen: { - valType: 'number', - min: 0, - dflt: 0, - description: [ - 'Sets the length (in px) of the links arrow, if 0 no arrow will be drawn.' - ].join(' ') - }, - label: { - valType: 'data_array', - dflt: [], - description: 'The shown name of the link.' - }, - color: { - valType: 'color', - arrayOk: true, - description: [ - 'Sets the `link` color. It can be a single value, or an array for specifying color for each `link`.', - 'If `link.color` is omitted, then by default, a translucent grey link will be used.' - ].join(' ') - }, - hovercolor: { - valType: 'color', - arrayOk: true, - description: [ - 'Sets the `link` hover color. It can be a single value, or an array for specifying hover colors for', - 'each `link`. If `link.hovercolor` is omitted, then by default, links will become slightly more', - 'opaque when hovered over.' - ].join(' ') - }, - customdata: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Assigns extra data to each link.' - ].join(' ') - }, - line: { + link: { + arrowlen: { + valType: 'number', + min: 0, + dflt: 0, + description: ['Sets the length (in px) of the links arrow, if 0 no arrow will be drawn.'].join(' ') + }, + label: { + valType: 'data_array', + dflt: [], + description: 'The shown name of the link.' + }, color: { valType: 'color', - dflt: colorAttrs.defaultLine, arrayOk: true, description: [ - 'Sets the color of the `line` around each `link`.' + 'Sets the `link` color. It can be a single value, or an array for specifying color for each `link`.', + 'If `link.color` is omitted, then by default, a translucent grey link will be used.' ].join(' ') }, - width: { - valType: 'number', - min: 0, - dflt: 0, + hovercolor: { + valType: 'color', arrayOk: true, description: [ - 'Sets the width (in px) of the `line` around each `link`.' + 'Sets the `link` hover color. It can be a single value, or an array for specifying hover colors for', + 'each `link`. If `link.hovercolor` is omitted, then by default, links will become slightly more', + 'opaque when hovered over.' ].join(' ') - } - }, - source: { - valType: 'data_array', - dflt: [], - description: 'An integer number `[0..nodes.length - 1]` that represents the source node.' - }, - target: { - valType: 'data_array', - dflt: [], - description: 'An integer number `[0..nodes.length - 1]` that represents the target node.' - }, - value: { - valType: 'data_array', - dflt: [], - description: 'A numeric value representing the flow volume value.' - }, - hoverinfo: { - valType: 'enumerated', - values: ['all', 'none', 'skip'], - dflt: 'all', - description: [ - 'Determines which trace information appear when hovering links.', - 'If `none` or `skip` are set, no information is displayed upon hovering.', - 'But, if `none` is set, click and hover events are still fired.' - ].join(' ') - }, - hoverlabel: fxAttrs.hoverlabel, // needs editType override, - hovertemplate: hovertemplateAttrs({}, { - description: 'Variables `source` and `target` are node objects.', - keys: ['value', 'label'] - }), - colorscales: templatedArray('concentrationscales', { - editType: 'calc', - label: { - valType: 'string', - editType: 'calc', - description: 'The label of the links to color based on their concentration within a flow.', - dflt: '' }, - cmax: { - valType: 'number', + customdata: { + valType: 'data_array', editType: 'calc', - dflt: 1, - description: 'Sets the upper bound of the color domain.' + description: ['Assigns extra data to each link.'].join(' ') }, - cmin: { - valType: 'number', - editType: 'calc', - dflt: 0, - description: 'Sets the lower bound of the color domain.' + line: { + color: { + valType: 'color', + dflt: colorAttrs.defaultLine, + arrayOk: true, + description: ['Sets the color of the `line` around each `link`.'].join(' ') + }, + width: { + valType: 'number', + min: 0, + dflt: 0, + arrayOk: true, + description: ['Sets the width (in px) of the `line` around each `link`.'].join(' ') + } }, - colorscale: extendFlat(colorAttributes().colorscale, {dflt: [[0, 'white'], [1, 'black']]}) - }), - description: 'The links of the Sankey plot.', - } -}, 'calc', 'nested'); + source: { + valType: 'data_array', + dflt: [], + description: 'An integer number `[0..nodes.length - 1]` that represents the source node.' + }, + target: { + valType: 'data_array', + dflt: [], + description: 'An integer number `[0..nodes.length - 1]` that represents the target node.' + }, + value: { + valType: 'data_array', + dflt: [], + description: 'A numeric value representing the flow volume value.' + }, + hoverinfo: { + valType: 'enumerated', + values: ['all', 'none', 'skip'], + dflt: 'all', + description: [ + 'Determines which trace information appear when hovering links.', + 'If `none` or `skip` are set, no information is displayed upon hovering.', + 'But, if `none` is set, click and hover events are still fired.' + ].join(' ') + }, + hoverlabel: fxAttrs.hoverlabel, // needs editType override, + hovertemplate: hovertemplateAttrs( + {}, + { + description: 'Variables `source` and `target` are node objects.', + keys: ['value', 'label'] + } + ), + colorscales: templatedArray('concentrationscales', { + editType: 'calc', + label: { + valType: 'string', + editType: 'calc', + description: 'The label of the links to color based on their concentration within a flow.', + dflt: '' + }, + cmax: { + valType: 'number', + editType: 'calc', + dflt: 1, + description: 'Sets the upper bound of the color domain.' + }, + cmin: { + valType: 'number', + editType: 'calc', + dflt: 0, + description: 'Sets the lower bound of the color domain.' + }, + colorscale: extendFlat(colorAttributes().colorscale, { + dflt: [ + [0, 'white'], + [1, 'black'] + ] + }) + }), + description: 'The links of the Sankey plot.' + } + }, + 'calc', + 'nested' +)); diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 84844d8fc60..3a29e1b075a 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -46,9 +46,7 @@ function axisPeriod0(axis) { function axisPeriodAlignment(axis) { return { valType: 'enumerated', - values: [ - 'start', 'middle', 'end' - ], + values: ['start', 'middle', 'end'], dflt: 'middle', editType: 'calc', description: [ @@ -82,10 +80,7 @@ module.exports = { dflt: 1, editType: 'calc', anim: true, - description: [ - 'Sets the x coordinate step.', - 'See `x0` for more info.' - ].join(' ') + description: ['Sets the x coordinate step.', 'See `x0` for more info.'].join(' ') }, y: { valType: 'data_array', @@ -110,10 +105,7 @@ module.exports = { dflt: 1, editType: 'calc', anim: true, - description: [ - 'Sets the y coordinate step.', - 'See `y0` for more info.' - ].join(' ') + description: ['Sets the y coordinate step.', 'See `y0` for more info.'].join(' ') }, xperiod: axisPeriod('x'), @@ -227,15 +219,13 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.', + "this trace's (x,y) coordinates.", 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', 'these elements will be seen in the hover labels.' ].join(' ') }, - texttemplate: texttemplateAttrs({}, { - - }), + texttemplate: texttemplateAttrs({}, {}), hovertext: { valType: 'string', dflt: '', @@ -246,7 +236,7 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.', + "this trace's (x,y) coordinates.", 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }, @@ -276,9 +266,12 @@ module.exports = { 'or text, then the default is *fills*, otherwise it is *points*.' ].join(' ') }, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), line: { color: { @@ -318,8 +311,9 @@ module.exports = { '*0* corresponds to no smoothing (equivalent to a *linear* shape).' ].join(' ') }, - dash: extendFlat({}, dash, {editType: 'style'}), - backoff: { // we want to have a similar option for the start of the line + dash: extendFlat({}, dash, { editType: 'style' }), + backoff: { + // we want to have a similar option for the start of the line valType: 'number', min: 0, dflt: 'auto', @@ -400,10 +394,9 @@ module.exports = { values: ['radial', 'horizontal', 'vertical', 'none'], dflt: 'none', editType: 'calc', - description: [ - 'Sets the type/orientation of the color gradient for the fill.', - 'Defaults to *none*.' - ].join(' ') + description: ['Sets the type/orientation of the color gradient for the fill.', 'Defaults to *none*.'].join( + ' ' + ) }, start: { valType: 'number', @@ -444,161 +437,156 @@ module.exports = { ].join(' ') }, editType: 'calc', - description: [ - 'Sets a fill gradient.', - 'If not specified, the fillcolor is used instead.' - ].join(' ') + description: ['Sets a fill gradient.', 'If not specified, the fillcolor is used instead.'].join(' ') }), fillpattern: pattern, - marker: extendFlat({ - symbol: { - valType: 'enumerated', - values: Drawing.symbolList, - dflt: 'circle', - arrayOk: true, - editType: 'style', - description: [ - 'Sets the marker symbol type.', - 'Adding 100 is equivalent to appending *-open* to a symbol name.', - 'Adding 200 is equivalent to appending *-dot* to a symbol name.', - 'Adding 300 is equivalent to appending *-open-dot*', - 'or *dot-open* to a symbol name.' - ].join(' ') - }, - opacity: { - valType: 'number', - min: 0, - max: 1, - arrayOk: true, - editType: 'style', - anim: true, - description: 'Sets the marker opacity.' - }, - angle: { - valType: 'angle', - dflt: 0, - arrayOk: true, - editType: 'plot', - anim: false, // TODO: possibly set to true in future - description: [ - 'Sets the marker angle in respect to `angleref`.' - ].join(' ') - }, - angleref: { - valType: 'enumerated', - values: ['previous', 'up'], - dflt: 'up', - editType: 'plot', - anim: false, - description: [ - 'Sets the reference for marker angle.', - 'With *previous*, angle 0 points along the line from the previous point to this one.', - 'With *up*, angle 0 points toward the top of the screen.' - ].join(' ') - }, - standoff: { - valType: 'number', - min: 0, - dflt: 0, - arrayOk: true, - editType: 'plot', - anim: true, - description: [ - 'Moves the marker away from the data point in the direction of `angle` (in px).', - 'This can be useful for example if you have another marker at this', - 'location and you want to point an arrowhead marker at it.' - ].join(' ') - }, - size: { - valType: 'number', - min: 0, - dflt: 6, - arrayOk: true, - editType: 'calc', - anim: true, - description: 'Sets the marker size (in px).' - }, - maxdisplayed: { - valType: 'number', - min: 0, - dflt: 0, - editType: 'plot', - description: [ - 'Sets a maximum number of points to be drawn on the graph.', - '*0* corresponds to no limit.' - ].join(' ') - }, - sizeref: { - valType: 'number', - dflt: 1, - editType: 'calc', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the scale factor used to determine the rendered size of', - 'marker points. Use with `sizemin` and `sizemode`.' - ].join(' ') - }, - sizemin: { - valType: 'number', - min: 0, - dflt: 0, - editType: 'calc', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the minimum size (in px) of the rendered marker points.' - ].join(' ') - }, - sizemode: { - valType: 'enumerated', - values: ['diameter', 'area'], - dflt: 'diameter', - editType: 'calc', - description: [ - 'Has an effect only if `marker.size` is set to a numerical array.', - 'Sets the rule for which the data in `size` is converted', - 'to pixels.' - ].join(' ') - }, - - line: extendFlat({ - width: { + marker: extendFlat( + { + symbol: { + valType: 'enumerated', + values: Drawing.symbolList, + dflt: 'circle', + arrayOk: true, + editType: 'style', + description: [ + 'Sets the marker symbol type.', + 'Adding 100 is equivalent to appending *-open* to a symbol name.', + 'Adding 200 is equivalent to appending *-dot* to a symbol name.', + 'Adding 300 is equivalent to appending *-open-dot*', + 'or *dot-open* to a symbol name.' + ].join(' ') + }, + opacity: { valType: 'number', min: 0, + max: 1, arrayOk: true, editType: 'style', anim: true, - description: 'Sets the width (in px) of the lines bounding the marker points.' + description: 'Sets the marker opacity.' }, - editType: 'calc' - }, - colorScaleAttrs('marker.line', {anim: true}) - ), - gradient: { - type: { + angle: { + valType: 'angle', + dflt: 0, + arrayOk: true, + editType: 'plot', + anim: false, // TODO: possibly set to true in future + description: ['Sets the marker angle in respect to `angleref`.'].join(' ') + }, + angleref: { valType: 'enumerated', - values: ['radial', 'horizontal', 'vertical', 'none'], + values: ['previous', 'up'], + dflt: 'up', + editType: 'plot', + anim: false, + description: [ + 'Sets the reference for marker angle.', + 'With *previous*, angle 0 points along the line from the previous point to this one.', + 'With *up*, angle 0 points toward the top of the screen.' + ].join(' ') + }, + standoff: { + valType: 'number', + min: 0, + dflt: 0, arrayOk: true, - dflt: 'none', - editType: 'calc', + editType: 'plot', + anim: true, description: [ - 'Sets the type of gradient used to fill the markers' + 'Moves the marker away from the data point in the direction of `angle` (in px).', + 'This can be useful for example if you have another marker at this', + 'location and you want to point an arrowhead marker at it.' ].join(' ') }, - color: { - valType: 'color', + size: { + valType: 'number', + min: 0, + dflt: 6, arrayOk: true, editType: 'calc', + anim: true, + description: 'Sets the marker size (in px).' + }, + maxdisplayed: { + valType: 'number', + min: 0, + dflt: 0, + editType: 'plot', + description: [ + 'Sets a maximum number of points to be drawn on the graph.', + '*0* corresponds to no limit.' + ].join(' ') + }, + sizeref: { + valType: 'number', + dflt: 1, + editType: 'calc', + description: [ + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the scale factor used to determine the rendered size of', + 'marker points. Use with `sizemin` and `sizemode`.' + ].join(' ') + }, + sizemin: { + valType: 'number', + min: 0, + dflt: 0, + editType: 'calc', description: [ - 'Sets the final color of the gradient fill:', - 'the center color for radial, the right for horizontal,', - 'or the bottom for vertical.', + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the minimum size (in px) of the rendered marker points.' ].join(' ') }, + sizemode: { + valType: 'enumerated', + values: ['diameter', 'area'], + dflt: 'diameter', + editType: 'calc', + description: [ + 'Has an effect only if `marker.size` is set to a numerical array.', + 'Sets the rule for which the data in `size` is converted', + 'to pixels.' + ].join(' ') + }, + + line: extendFlat( + { + width: { + valType: 'number', + min: 0, + arrayOk: true, + editType: 'style', + anim: true, + description: 'Sets the width (in px) of the lines bounding the marker points.' + }, + editType: 'calc' + }, + colorScaleAttrs('marker.line', { anim: true }) + ), + gradient: { + type: { + valType: 'enumerated', + values: ['radial', 'horizontal', 'vertical', 'none'], + arrayOk: true, + dflt: 'none', + editType: 'calc', + description: ['Sets the type of gradient used to fill the markers'].join(' ') + }, + color: { + valType: 'color', + arrayOk: true, + editType: 'calc', + description: [ + 'Sets the final color of the gradient fill:', + 'the center color for radial, the right for horizontal,', + 'or the bottom for vertical.' + ].join(' ') + }, + editType: 'calc' + }, editType: 'calc' }, - editType: 'calc' - }, - colorScaleAttrs('marker', {anim: true}) + colorScaleAttrs('marker', { anim: true }) ), selected: { marker: { @@ -668,17 +656,20 @@ module.exports = { textposition: { valType: 'enumerated', values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right' + 'top left', + 'top center', + 'top right', + 'middle left', + 'middle center', + 'middle right', + 'bottom left', + 'bottom center', + 'bottom right' ], dflt: 'middle center', arrayOk: true, editType: 'calc', - description: [ - 'Sets the positions of the `text` elements', - 'with respects to the (x,y) coordinates.' - ].join(' ') + description: ['Sets the positions of the `text` elements', 'with respects to the (x,y) coordinates.'].join(' ') }, textfont: fontAttrs({ editType: 'calc', diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index de63b81e49e..670059b44ac 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -22,9 +22,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) traceOut.visible = false; + if (!len) traceOut.visible = false; - if(!traceOut.visible) return; + if (!traceOut.visible) return; handlePeriodDefaults(traceIn, traceOut, layout, coerce); coerce('xhoverformat'); @@ -33,38 +33,34 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('zorder'); var stackGroupOpts = handleStackDefaults(traceIn, traceOut, layout, coerce); - if( - layout.scattermode === 'group' && - traceOut.orientation === undefined - ) { + if (layout.scattermode === 'group' && traceOut.orientation === undefined) { coerce('orientation', 'v'); } - var defaultMode = !stackGroupOpts && (len < constants.PTS_LINESONLY) ? - 'lines+markers' : 'lines'; + var defaultMode = !stackGroupOpts && len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('text'); coerce('hovertext'); coerce('mode', defaultMode); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {backoff: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { backoff: true }); handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); coerce('line.simplify'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce); } var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('cliponaxis'); coerce('marker.maxdisplayed'); dfltHoverOn.push('points'); @@ -73,25 +69,25 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // It's possible for this default to be changed by a later trace. // We handle that case in some hacky code inside handleStackDefaults. coerce('fill', stackGroupOpts ? stackGroupOpts.fillDflt : 'none'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce, { moduleHasFillgradient: true }); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); coercePattern(coerce, 'fillpattern', traceOut.fillcolor, false); } var lineColor = (traceOut.line || {}).color; var markerColor = (traceOut.marker || {}).color; - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); - if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'y' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'x', inherit: 'y' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index ccb56b8d348..90df4ddac84 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -18,25 +18,25 @@ var scatterLineAttrs = scatterAttrs.line; var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var lineAttrs = extendFlat({ - width: scatterLineAttrs.width, - dash: { - valType: 'enumerated', - values: sortObjectKeys(DASHES), - dflt: 'solid', - description: 'Sets the dash style of the lines.' - } -}, colorAttributes('line')); +var lineAttrs = extendFlat( + { + width: scatterLineAttrs.width, + dash: { + valType: 'enumerated', + values: sortObjectKeys(DASHES), + dflt: 'solid', + description: 'Sets the dash style of the lines.' + } + }, + colorAttributes('line') +); function makeProjectionAttr(axLetter) { return { show: { valType: 'boolean', dflt: false, - description: [ - 'Sets whether or not projections are shown along the', - axLetter, 'axis.' - ].join(' ') + description: ['Sets whether or not projections are shown along the', axLetter, 'axis.'].join(' ') }, opacity: { valType: 'number', @@ -50,126 +50,131 @@ function makeProjectionAttr(axLetter) { min: 0, max: 10, dflt: 2 / 3, - description: [ - 'Sets the scale factor determining the size of the', - 'projection marker points.' - ].join(' ') + description: ['Sets the scale factor determining the size of the', 'projection marker points.'].join(' ') } }; } -var attrs = module.exports = overrideAll({ - x: scatterAttrs.x, - y: scatterAttrs.y, - z: { - valType: 'data_array', - description: 'Sets the z coordinates.' - }, - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (x,y,z) triplet.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y,z) coordinates.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }), - texttemplate: texttemplateAttrs({}, { - - }), - hovertext: extendFlat({}, scatterAttrs.hovertext, { - description: [ - 'Sets text elements associated with each (x,y,z) triplet.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y,z) coordinates.', - 'To be seen, trace `hoverinfo` must contain a *text* flag.' - ].join(' ') - }), - hovertemplate: hovertemplateAttrs(), - - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z'), - - mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? - {dflt: 'lines+markers'}), - surfaceaxis: { - valType: 'enumerated', - values: [-1, 0, 1, 2], - dflt: -1, - description: [ - 'If *-1*, the scatter points are not fill with a surface', - 'If *0*, *1*, *2*, the scatter points are filled with', - 'a Delaunay surface about the x, y, z respectively.' - ].join(' ') - }, - surfacecolor: { - valType: 'color', - description: 'Sets the surface fill color.' - }, - projection: { - x: makeProjectionAttr('x'), - y: makeProjectionAttr('y'), - z: makeProjectionAttr('z') - }, - - connectgaps: scatterAttrs.connectgaps, - line: lineAttrs, +var attrs = (module.exports = overrideAll( + { + x: scatterAttrs.x, + y: scatterAttrs.y, + z: { + valType: 'data_array', + description: 'Sets the z coordinates.' + }, - marker: extendFlat({ // Parity with scatter.js? - symbol: { + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y,z) triplet.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (x,y,z) coordinates.", + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }), + texttemplate: texttemplateAttrs({}, {}), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets text elements associated with each (x,y,z) triplet.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (x,y,z) coordinates.", + 'To be seen, trace `hoverinfo` must contain a *text* flag.' + ].join(' ') + }), + hovertemplate: hovertemplateAttrs(), + + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z'), + + mode: extendFlat( + {}, + scatterAttrs.mode, // shouldn't this be on-par with 2D? + { dflt: 'lines+markers' } + ), + surfaceaxis: { valType: 'enumerated', - values: sortObjectKeys(MARKER_SYMBOLS), - dflt: 'circle', - arrayOk: true, - description: 'Sets the marker symbol type.' - }, - size: extendFlat({}, scatterMarkerAttrs.size, {dflt: 8}), - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: extendFlat({}, scatterMarkerAttrs.opacity, { - arrayOk: false, + values: [-1, 0, 1, 2], + dflt: -1, description: [ - 'Sets the marker opacity.', - 'Note that the marker opacity for scatter3d traces', - 'must be a scalar value for performance reasons.', - 'To set a blending opacity value', - '(i.e. which is not transparent), set *marker.color*', - 'to an rgba color and use its alpha channel.' + 'If *-1*, the scatter points are not fill with a surface', + 'If *0*, *1*, *2*, the scatter points are filled with', + 'a Delaunay surface about the x, y, z respectively.' ].join(' ') + }, + surfacecolor: { + valType: 'color', + description: 'Sets the surface fill color.' + }, + projection: { + x: makeProjectionAttr('x'), + y: makeProjectionAttr('y'), + z: makeProjectionAttr('z') + }, + + connectgaps: scatterAttrs.connectgaps, + line: lineAttrs, + + marker: extendFlat( + { + // Parity with scatter.js? + symbol: { + valType: 'enumerated', + values: sortObjectKeys(MARKER_SYMBOLS), + dflt: 'circle', + arrayOk: true, + description: 'Sets the marker symbol type.' + }, + size: extendFlat({}, scatterMarkerAttrs.size, { dflt: 8 }), + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: extendFlat({}, scatterMarkerAttrs.opacity, { + arrayOk: false, + description: [ + 'Sets the marker opacity.', + 'Note that the marker opacity for scatter3d traces', + 'must be a scalar value for performance reasons.', + 'To set a blending opacity value', + '(i.e. which is not transparent), set *marker.color*', + 'to an rgba color and use its alpha channel.' + ].join(' ') + }), + colorbar: scatterMarkerAttrs.colorbar, + + line: extendFlat( + { + width: extendFlat({}, scatterMarkerLineAttrs.width, { arrayOk: false }) + }, + colorAttributes('marker.line') + ) + }, + colorAttributes('marker') + ), + + textposition: extendFlat({}, scatterAttrs.textposition, { dflt: 'top center' }), + textfont: fontAttrs({ + noFontShadow: true, + noFontLineposition: true, + noFontTextcase: true, + editType: 'calc', + colorEditType: 'style', + arrayOk: true, + variantValues: ['normal', 'small-caps'], + description: 'Sets the text font.' }), - colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({ - width: extendFlat({}, scatterMarkerLineAttrs.width, {arrayOk: false}) - }, - colorAttributes('marker.line') - ) + opacity: baseAttrs.opacity, + + hoverinfo: extendFlat({}, baseAttrs.hoverinfo) }, - colorAttributes('marker') - ), - - textposition: extendFlat({}, scatterAttrs.textposition, {dflt: 'top center'}), - textfont: fontAttrs({ - noFontShadow: true, - noFontLineposition: true, - noFontTextcase: true, - editType: 'calc', - colorEditType: 'style', - arrayOk: true, - variantValues: ['normal', 'small-caps'], - description: 'Sets the text font.' - }), - - opacity: baseAttrs.opacity, - - hoverinfo: extendFlat({}, baseAttrs.hoverinfo) -}, 'calc', 'nested'); + 'calc', + 'nested' +)); attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 8b9b75559cc..e6c65b4b5fd 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -39,27 +39,28 @@ function LineWithMarkers(scene, uid) { var proto = LineWithMarkers.prototype; -proto.handlePick = function(selection) { - if(selection.object && +proto.handlePick = function (selection) { + if ( + selection.object && (selection.object === this.linePlot || - selection.object === this.delaunayMesh || - selection.object === this.textMarkers || - selection.object === this.scatterPlot) + selection.object === this.delaunayMesh || + selection.object === this.textMarkers || + selection.object === this.scatterPlot) ) { - var ind = selection.index = selection.data.index; + var ind = (selection.index = selection.data.index); - if(selection.object.highlight) { + if (selection.object.highlight) { selection.object.highlight(null); } - if(this.scatterPlot) { + if (this.scatterPlot) { selection.object = this.scatterPlot; this.scatterPlot.highlight(selection.data); } selection.textLabel = ''; - if(this.textLabels) { - if(Lib.isArrayOrTypedArray(this.textLabels)) { - if(this.textLabels[ind] || this.textLabels[ind] === 0) { + if (this.textLabels) { + if (Lib.isArrayOrTypedArray(this.textLabels)) { + if (this.textLabels[ind] || this.textLabels[ind] === 0) { selection.textLabel = this.textLabels[ind]; } } else { @@ -67,11 +68,7 @@ proto.handlePick = function(selection) { } } - selection.traceCoordinate = [ - this.data.x[ind], - this.data.y[ind], - this.data.z[ind] - ]; + selection.traceCoordinate = [this.data.x[ind], this.data.y[ind], this.data.z[ind]]; return true; } @@ -84,19 +81,18 @@ function constructDelaunay(points, color, axis) { var filteredIds = []; var i; - for(i = 0; i < points.length; ++i) { + for (i = 0; i < points.length; ++i) { var p = points[i]; - if(isNaN(p[u]) || !isFinite(p[u]) || - isNaN(p[v]) || !isFinite(p[v])) { + if (isNaN(p[u]) || !isFinite(p[u]) || isNaN(p[v]) || !isFinite(p[v])) { continue; } filteredPoints.push([p[u], p[v]]); filteredIds.push(i); } var cells = triangulate(filteredPoints); - for(i = 0; i < cells.length; ++i) { + for (i = 0; i < cells.length; ++i) { var c = cells[i]; - for(var j = 0; j < c.length; ++j) { + for (var j = 0; j < c.length; ++j) { c[j] = filteredIds[c[j]]; } } @@ -109,35 +105,37 @@ function constructDelaunay(points, color, axis) { function calculateErrorParams(errors) { var capSize = [0.0, 0.0, 0.0]; - var color = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; + var color = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ]; var lineWidth = [1.0, 1.0, 1.0]; - for(var i = 0; i < 3; i++) { + for (var i = 0; i < 3; i++) { var e = errors[i]; - if(e && e.copy_zstyle !== false && errors[2].visible !== false) e = errors[2]; - if(!e || !e.visible) continue; + if (e && e.copy_zstyle !== false && errors[2].visible !== false) e = errors[2]; + if (!e || !e.visible) continue; - capSize[i] = e.width / 2; // ballpark rescaling + capSize[i] = e.width / 2; // ballpark rescaling color[i] = str2RgbaArray(e.color); lineWidth[i] = e.thickness; } - return {capSize: capSize, color: color, lineWidth: lineWidth}; + return { capSize: capSize, color: color, lineWidth: lineWidth }; } function parseAlignmentX(a) { - if(a === null || a === undefined) return 0; + if (a === null || a === undefined) return 0; - return (a.indexOf('left') > -1) ? -1 : - (a.indexOf('right') > -1) ? 1 : 0; + return a.indexOf('left') > -1 ? -1 : a.indexOf('right') > -1 ? 1 : 0; } function parseAlignmentY(a) { - if(a === null || a === undefined) return 0; + if (a === null || a === undefined) return 0; - return (a.indexOf('top') > -1) ? -1 : - (a.indexOf('bottom') > -1) ? 1 : 0; + return a.indexOf('top') > -1 ? -1 : a.indexOf('bottom') > -1 ? 1 : 0; } function calculateTextOffset(tp) { @@ -146,18 +144,12 @@ function calculateTextOffset(tp) { var defaultAlignmentX = 0; var defaultAlignmentY = 0; - var textOffset = [ - defaultAlignmentX, - defaultAlignmentY - ]; + var textOffset = [defaultAlignmentX, defaultAlignmentY]; - if(Array.isArray(tp)) { - for(var i = 0; i < tp.length; i++) { - textOffset[i] = [ - defaultAlignmentX, - defaultAlignmentY - ]; - if(tp[i]) { + if (Array.isArray(tp)) { + for (var i = 0; i < tp.length; i++) { + textOffset[i] = [defaultAlignmentX, defaultAlignmentY]; + if (tp[i]) { textOffset[i][0] = parseAlignmentX(tp[i]); textOffset[i][1] = parseAlignmentY(tp[i]); } @@ -170,7 +162,6 @@ function calculateTextOffset(tp) { return textOffset; } - function calculateSize(sizeIn, sizeFn) { // rough parity with Plotly 2D markers return sizeFn(sizeIn * 4); @@ -183,11 +174,11 @@ function calculateSymbol(symbolIn) { function formatParam(paramIn, len, calculate, dflt, extraFn) { var paramOut = null; - if(Lib.isArrayOrTypedArray(paramIn)) { + if (Lib.isArrayOrTypedArray(paramIn)) { paramOut = []; - for(var i = 0; i < len; i++) { - if(paramIn[i] === undefined) paramOut[i] = dflt; + for (var i = 0; i < len; i++) { + if (paramIn[i] === undefined) paramOut[i] = dflt; else paramOut[i] = calculate(paramIn[i], extraFn); } } else paramOut = calculate(paramIn, Lib.identity); @@ -195,7 +186,6 @@ function formatParam(paramIn, len, calculate, dflt, extraFn) { return paramOut; } - function convertPlotlyOptions(scene, data) { var points = []; var sceneLayout = scene.fullSceneLayout; @@ -217,7 +207,7 @@ function convertPlotlyOptions(scene, data) { var text; // Convert points - for(i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // sanitize numbers and apply transforms based on axes.type xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0]; yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1]; @@ -227,13 +217,13 @@ function convertPlotlyOptions(scene, data) { } // convert text - if(Array.isArray(data.text)) { + if (Array.isArray(data.text)) { text = data.text; - } else if(Lib.isTypedArray(data.text)) { + } else if (Lib.isTypedArray(data.text)) { text = Array.from(data.text); - } else if(data.text !== undefined) { + } else if (data.text !== undefined) { text = new Array(len); - for(i = 0; i < len; i++) text[i] = data.text; + for (i = 0; i < len; i++) text[i] = data.text; } function formatter(axName, val) { @@ -243,19 +233,23 @@ function convertPlotlyOptions(scene, data) { // check texttemplate var texttemplate = data.texttemplate; - if(texttemplate) { + if (texttemplate) { var fullLayout = scene.fullLayout; var d3locale = fullLayout._d3locale; var isArray = Array.isArray(texttemplate); var N = isArray ? Math.min(texttemplate.length, len) : len; - var txt = isArray ? - function(i) { return texttemplate[i]; } : - function() { return texttemplate; }; + var txt = isArray + ? function (i) { + return texttemplate[i]; + } + : function () { + return texttemplate; + }; text = new Array(N); - for(i = 0; i < N; i++) { - var d = {x: x[i], y: y[i], z: z[i]}; + for (i = 0; i < N; i++) { + var d = { x: x[i], y: y[i], z: z[i] }; var labels = { xLabel: formatter('xaxis', x[i]), yLabel: formatter('yaxis', y[i]), @@ -275,24 +269,24 @@ function convertPlotlyOptions(scene, data) { text: text }; - if('line' in data) { + if ('line' in data) { params.lineColor = formatColor(line, 1, len); params.lineWidth = line.width; params.lineDashes = line.dash; } - if('marker' in data) { + if ('marker' in data) { var sizeFn = makeBubbleSizeFn(data); params.scatterColor = formatColor(marker, 1, len); params.scatterSize = formatParam(marker.size, len, calculateSize, 20, sizeFn); params.scatterMarker = formatParam(marker.symbol, len, calculateSymbol, '●'); - params.scatterLineWidth = marker.line.width; // arrayOk === false + params.scatterLineWidth = marker.line.width; // arrayOk === false params.scatterLineColor = formatColor(marker.line, 1, len); params.scatterAngle = 0; } - if('textposition' in data) { + if ('textposition' in data) { params.textOffset = calculateTextOffset(data.textposition); params.textColor = formatColor(data.textfont, 1, len); params.textSize = formatParam(data.textfont.size, len, Lib.identity, 12); @@ -307,9 +301,9 @@ function convertPlotlyOptions(scene, data) { params.project = [false, false, false]; params.projectScale = [1, 1, 1]; params.projectOpacity = [1, 1, 1]; - for(i = 0; i < 3; ++i) { + for (i = 0; i < 3; ++i) { var projection = data.projection[dims[i]]; - if((params.project[i] = projection.show)) { + if ((params.project[i] = projection.show)) { params.projectOpacity[i] = projection.opacity; params.projectScale[i] = projection.scale; } @@ -329,32 +323,36 @@ function convertPlotlyOptions(scene, data) { } function _arrayToColor(color) { - if(Lib.isArrayOrTypedArray(color)) { + if (Lib.isArrayOrTypedArray(color)) { var c = color[0]; - if(Lib.isArrayOrTypedArray(c)) color = c; + if (Lib.isArrayOrTypedArray(c)) color = c; - return 'rgb(' + color.slice(0, 3).map(function(x) { - return Math.round(x * 255); - }) + ')'; + return ( + 'rgb(' + + color.slice(0, 3).map(function (x) { + return Math.round(x * 255); + }) + + ')' + ); } return null; } function arrayToColor(colors) { - if(!Lib.isArrayOrTypedArray(colors)) { + if (!Lib.isArrayOrTypedArray(colors)) { return null; } - if((colors.length === 4) && (typeof colors[0] === 'number')) { + if (colors.length === 4 && typeof colors[0] === 'number') { return _arrayToColor(colors); } return colors.map(_arrayToColor); } -proto.update = function(data) { +proto.update = function (data) { var gl = this.scene.glplot.gl; var lineOptions; var scatterOptions; @@ -368,17 +366,16 @@ proto.update = function(data) { // Run data conversion var options = convertPlotlyOptions(this.scene, data); - if('mode' in options) { + if ('mode' in options) { this.mode = options.mode; } - if('lineDashes' in options) { - if(options.lineDashes in DASH_PATTERNS) { + if ('lineDashes' in options) { + if (options.lineDashes in DASH_PATTERNS) { dashPattern = DASH_PATTERNS[options.lineDashes]; } } - this.color = arrayToColor(options.scatterColor) || - arrayToColor(options.lineColor); + this.color = arrayToColor(options.scatterColor) || arrayToColor(options.lineColor); // Save data points this.dataPoints = options.position; @@ -394,14 +391,14 @@ proto.update = function(data) { connectGaps: data.connectgaps }; - if(this.mode.indexOf('lines') !== -1) { - if(this.linePlot) this.linePlot.update(lineOptions); + if (this.mode.indexOf('lines') !== -1) { + if (this.linePlot) this.linePlot.update(lineOptions); else { this.linePlot = createLinePlot(lineOptions); this.linePlot._trace = this; this.scene.glplot.add(this.linePlot); } - } else if(this.linePlot) { + } else if (this.linePlot) { this.scene.glplot.remove(this.linePlot); this.linePlot.dispose(); this.linePlot = null; @@ -409,7 +406,7 @@ proto.update = function(data) { // N.B. marker.opacity must be a scalar for performance var scatterOpacity = data.opacity; - if(data.marker && data.marker.opacity !== undefined) scatterOpacity *= data.marker.opacity; + if (data.marker && data.marker.opacity !== undefined) scatterOpacity *= data.marker.opacity; scatterOptions = { gl: this.scene.glplot.gl, @@ -426,15 +423,15 @@ proto.update = function(data) { projectOpacity: options.projectOpacity }; - if(this.mode.indexOf('markers') !== -1) { - if(this.scatterPlot) this.scatterPlot.update(scatterOptions); + if (this.mode.indexOf('markers') !== -1) { + if (this.scatterPlot) this.scatterPlot.update(scatterOptions); else { this.scatterPlot = createScatterPlot(scatterOptions); this.scatterPlot._trace = this; this.scatterPlot.highlightScale = 1; this.scene.glplot.add(this.scatterPlot); } - } else if(this.scatterPlot) { + } else if (this.scatterPlot) { this.scene.glplot.remove(this.scatterPlot); this.scatterPlot.dispose(); this.scatterPlot = null; @@ -460,15 +457,15 @@ proto.update = function(data) { this.textLabels = data.hovertext || data.text; - if(this.mode.indexOf('text') !== -1) { - if(this.textMarkers) this.textMarkers.update(textOptions); + if (this.mode.indexOf('text') !== -1) { + if (this.textMarkers) this.textMarkers.update(textOptions); else { this.textMarkers = createScatterPlot(textOptions); this.textMarkers._trace = this; this.textMarkers.highlightScale = 1; this.scene.glplot.add(this.textMarkers); } - } else if(this.textMarkers) { + } else if (this.textMarkers) { this.scene.glplot.remove(this.textMarkers); this.textMarkers.dispose(); this.textMarkers = null; @@ -483,29 +480,25 @@ proto.update = function(data) { capSize: options.errorCapSize, opacity: data.opacity }; - if(this.errorBars) { - if(options.errorBounds) { + if (this.errorBars) { + if (options.errorBounds) { this.errorBars.update(errorOptions); } else { this.scene.glplot.remove(this.errorBars); this.errorBars.dispose(); this.errorBars = null; } - } else if(options.errorBounds) { + } else if (options.errorBounds) { this.errorBars = createErrorBars(errorOptions); this.errorBars._trace = this; this.scene.glplot.add(this.errorBars); } - if(options.delaunayAxis >= 0) { - var delaunayOptions = constructDelaunay( - options.position, - options.delaunayColor, - options.delaunayAxis - ); + if (options.delaunayAxis >= 0) { + var delaunayOptions = constructDelaunay(options.position, options.delaunayColor, options.delaunayAxis); delaunayOptions.opacity = data.opacity; - if(this.delaunayMesh) { + if (this.delaunayMesh) { this.delaunayMesh.update(delaunayOptions); } else { delaunayOptions.gl = gl; @@ -513,31 +506,31 @@ proto.update = function(data) { this.delaunayMesh._trace = this; this.scene.glplot.add(this.delaunayMesh); } - } else if(this.delaunayMesh) { + } else if (this.delaunayMesh) { this.scene.glplot.remove(this.delaunayMesh); this.delaunayMesh.dispose(); this.delaunayMesh = null; } }; -proto.dispose = function() { - if(this.linePlot) { +proto.dispose = function () { + if (this.linePlot) { this.scene.glplot.remove(this.linePlot); this.linePlot.dispose(); } - if(this.scatterPlot) { + if (this.scatterPlot) { this.scene.glplot.remove(this.scatterPlot); this.scatterPlot.dispose(); } - if(this.errorBars) { + if (this.errorBars) { this.scene.glplot.remove(this.errorBars); this.errorBars.dispose(); } - if(this.textMarkers) { + if (this.textMarkers) { this.scene.glplot.remove(this.textMarkers); this.textMarkers.dispose(); } - if(this.delaunayMesh) { + if (this.delaunayMesh) { this.scene.glplot.remove(this.delaunayMesh); this.delaunayMesh.dispose(); } diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index bcacbdabbc2..65a60ef988d 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -16,7 +16,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleXYZDefaults(traceIn, traceOut, coerce, layout); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -30,42 +30,42 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noSelect: true, noAngle: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noSelect: true, noAngle: true }); } - if(subTypes.hasLines(traceOut)) { + if (subTypes.hasLines(traceOut)) { coerce('connectgaps'); handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noSelect: true, noFontShadow: true, noFontLineposition: true, - noFontTextcase: true, + noFontTextcase: true }); } var lineColor = (traceOut.line || {}).color; var markerColor = (traceOut.marker || {}).color; - if(coerce('surfaceaxis') >= 0) coerce('surfacecolor', lineColor || markerColor); + if (coerce('surfaceaxis') >= 0) coerce('surfacecolor', lineColor || markerColor); var dims = ['x', 'y', 'z']; - for(var i = 0; i < 3; ++i) { + for (var i = 0; i < 3; ++i) { var projection = 'projection.' + dims[i]; - if(coerce(projection + '.show')) { + if (coerce(projection + '.show')) { coerce(projection + '.opacity'); coerce(projection + '.scale'); } } var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'z'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y', inherit: 'z'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'z'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'z' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'y', inherit: 'z' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'x', inherit: 'z' }); }; function handleXYZDefaults(traceIn, traceOut, coerce, layout) { @@ -77,7 +77,7 @@ function handleXYZDefaults(traceIn, traceOut, coerce, layout) { var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); - if(x && y && z) { + if (x && y && z) { // TODO: what happens if one is missing? len = Math.min(x.length, y.length, z.length); traceOut._length = traceOut._xlength = traceOut._ylength = traceOut._zlength = len; diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index 33cad08035b..066cc88f515 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -33,7 +33,7 @@ module.exports = { editType: 'calc', description: 'Sets the b-axis coordinates.' }, - mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + mode: extendFlat({}, scatterAttrs.mode, { dflt: 'markers' }), text: extendFlat({}, scatterAttrs.text, { description: [ 'Sets text elements associated with each (a,b) point.', @@ -45,9 +45,12 @@ module.exports = { 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['a', 'b', 'text'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['a', 'b', 'text'] + } + ), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (a,b) point.', @@ -63,8 +66,7 @@ module.exports = { width: scatterLineAttrs.width, dash: scatterLineAttrs.dash, backoff: scatterLineAttrs.backoff, - shape: extendFlat({}, scatterLineAttrs.shape, - {values: ['linear', 'spline']}), + shape: extendFlat({}, scatterLineAttrs.shape, { values: ['linear', 'spline'] }), smoothing: scatterLineAttrs.smoothing, editType: 'calc' }, @@ -85,26 +87,28 @@ module.exports = { ].join(' ') }), fillcolor: makeFillcolorAttr(), - marker: extendFlat({ - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity, - maxdisplayed: scatterMarkerAttrs.maxdisplayed, - angle: scatterMarkerAttrs.angle, - angleref: scatterMarkerAttrs.angleref, - standoff: scatterMarkerAttrs.standoff, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - line: extendFlat({ - width: scatterMarkerLineAttrs.width, + marker: extendFlat( + { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + maxdisplayed: scatterMarkerAttrs.maxdisplayed, + angle: scatterMarkerAttrs.angle, + angleref: scatterMarkerAttrs.angleref, + standoff: scatterMarkerAttrs.standoff, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + line: extendFlat( + { + width: scatterMarkerLineAttrs.width, + editType: 'calc' + }, + colorScaleAttrs('marker.line') + ), + gradient: scatterMarkerAttrs.gradient, editType: 'calc' }, - colorScaleAttrs('marker.line') - ), - gradient: scatterMarkerAttrs.gradient, - editType: 'calc' - }, colorScaleAttrs('marker') ), diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index f1234ea192f..b2a253b3f0d 100644 --- a/src/traces/scattercarpet/defaults.js +++ b/src/traces/scattercarpet/defaults.js @@ -27,7 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var b = coerce('b'); var len = Math.min(a.length, b.length); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -41,39 +41,39 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('mode', defaultMode); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {backoff: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { backoff: true }); handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { handleTextDefaults(traceIn, traceOut, layout, coerce); } var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('marker.maxdisplayed'); dfltHoverOn.push('points'); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } var hoverOn = coerce('hoveron', dfltHoverOn.join('+') || 'points'); - if(hoverOn !== 'fills') coerce('hovertemplate'); + if (hoverOn !== 'fills') coerce('hovertemplate'); coerce('zorder'); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 4b14da5e813..f547883d4f1 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -20,151 +20,160 @@ const breakingChangeWarning = [ 'Country names in existing plots may not work in the new version.' ].join(' '); -module.exports = overrideAll({ - lon: { - valType: 'data_array', - description: 'Sets the longitude coordinates (in degrees East).' - }, - lat: { - valType: 'data_array', - description: 'Sets the latitude coordinates (in degrees North).' - }, +module.exports = overrideAll( + { + lon: { + valType: 'data_array', + description: 'Sets the longitude coordinates (in degrees East).' + }, + lat: { + valType: 'data_array', + description: 'Sets the latitude coordinates (in degrees North).' + }, - locations: { - valType: 'data_array', - description: [ - 'Sets the coordinates via location IDs or names.', - 'Coordinates correspond to the centroid of each location given.', - 'See `locationmode` for more info.' - ].join(' ') - }, - locationmode: { - valType: 'enumerated', - values: ['ISO-3', 'USA-states', 'country names', 'geojson-id'], - dflt: 'ISO-3', - description: [ - breakingChangeWarning, - 'Determines the set of locations used to match entries in `locations`', - 'to regions on the map.', - 'Values *ISO-3*, *USA-states*, *country names* correspond to features on', - 'the base map and value *geojson-id* corresponds to features from a custom', - 'GeoJSON linked to the `geojson` attribute.' - ].join(' ') - }, + locations: { + valType: 'data_array', + description: [ + 'Sets the coordinates via location IDs or names.', + 'Coordinates correspond to the centroid of each location given.', + 'See `locationmode` for more info.' + ].join(' ') + }, + locationmode: { + valType: 'enumerated', + values: ['ISO-3', 'USA-states', 'country names', 'geojson-id'], + dflt: 'ISO-3', + description: [ + breakingChangeWarning, + 'Determines the set of locations used to match entries in `locations`', + 'to regions on the map.', + 'Values *ISO-3*, *USA-states*, *country names* correspond to features on', + 'the base map and value *geojson-id* corresponds to features from a custom', + 'GeoJSON linked to the `geojson` attribute.' + ].join(' ') + }, - geojson: { - valType: 'any', - editType: 'calc', - description: [ - 'Sets optional GeoJSON data associated with this trace.', - 'If not given, the features on the base map are used when `locations` is set.', + geojson: { + valType: 'any', + editType: 'calc', + description: [ + 'Sets optional GeoJSON data associated with this trace.', + 'If not given, the features on the base map are used when `locations` is set.', - 'It can be set as a valid GeoJSON object or as a URL string.', - 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', - 'with geometries of type *Polygon* or *MultiPolygon*.' + 'It can be set as a valid GeoJSON object or as a URL string.', + 'Note that we only accept GeoJSONs of type *FeatureCollection* or *Feature*', + 'with geometries of type *Polygon* or *MultiPolygon*.' - // TODO add topojson support with additional 'topojsonobject' attr? - // https://github.com/topojson/topojson-specification/blob/master/README.md - ].join(' ') - }, - featureidkey: { - valType: 'string', - editType: 'calc', - dflt: 'id', - description: [ - 'Sets the key in GeoJSON features which is used as id to match the items', - 'included in the `locations` array.', - 'Only has an effect when `geojson` is set.', - 'Support nested property, for example *properties.name*.' - ].join(' ') - }, + // TODO add topojson support with additional 'topojsonobject' attr? + // https://github.com/topojson/topojson-specification/blob/master/README.md + ].join(' ') + }, + featureidkey: { + valType: 'string', + editType: 'calc', + dflt: 'id', + description: [ + 'Sets the key in GeoJSON features which is used as id to match the items', + 'included in the `locations` array.', + 'Only has an effect when `geojson` is set.', + 'Support nested property, for example *properties.name*.' + ].join(' ') + }, - mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (lon,lat) pair', - 'or item in `locations`.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) or `locations` coordinates.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['lat', 'lon', 'location', 'text'] - }), - hovertext: extendFlat({}, scatterAttrs.hovertext, { - description: [ - 'Sets hover text elements associated with each (lon,lat) pair', - 'or item in `locations`.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) or `locations` coordinates.', - 'To be seen, trace `hoverinfo` must contain a *text* flag.' - ].join(' ') - }), - - textfont: scatterAttrs.textfont, - textposition: scatterAttrs.textposition, - - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: dash - }, - connectgaps: scatterAttrs.connectgaps, - - marker: extendFlat({ - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity, - angle: scatterMarkerAttrs.angle, - angleref: extendFlat({}, scatterMarkerAttrs.angleref, { - values: ['previous', 'up', 'north'], + mode: extendFlat({}, scatterAttrs.mode, { dflt: 'markers' }), + + text: extendFlat({}, scatterAttrs.text, { description: [ - 'Sets the reference for marker angle.', - 'With *previous*, angle 0 points along the line from the previous point to this one.', - 'With *up*, angle 0 points toward the top of the screen.', - 'With *north*, angle 0 points north based on the current map projection.', + 'Sets text elements associated with each (lon,lat) pair', + 'or item in `locations`.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) or `locations` coordinates.", + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' ].join(' ') }), - standoff: scatterMarkerAttrs.standoff, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({ - width: scatterMarkerLineAttrs.width + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['lat', 'lon', 'location', 'text'] + } + ), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets hover text elements associated with each (lon,lat) pair', + 'or item in `locations`.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) or `locations` coordinates.", + 'To be seen, trace `hoverinfo` must contain a *text* flag.' + ].join(' ') + }), + + textfont: scatterAttrs.textfont, + textposition: scatterAttrs.textposition, + + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: dash }, - colorAttributes('marker.line') + connectgaps: scatterAttrs.connectgaps, + + marker: extendFlat( + { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + angle: scatterMarkerAttrs.angle, + angleref: extendFlat({}, scatterMarkerAttrs.angleref, { + values: ['previous', 'up', 'north'], + description: [ + 'Sets the reference for marker angle.', + 'With *previous*, angle 0 points along the line from the previous point to this one.', + 'With *up*, angle 0 points toward the top of the screen.', + 'With *north*, angle 0 points north based on the current map projection.' + ].join(' ') + }), + standoff: scatterMarkerAttrs.standoff, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + colorbar: scatterMarkerAttrs.colorbar, + line: extendFlat( + { + width: scatterMarkerLineAttrs.width + }, + colorAttributes('marker.line') + ), + gradient: scatterMarkerAttrs.gradient + }, + colorAttributes('marker') ), - gradient: scatterMarkerAttrs.gradient - }, - colorAttributes('marker') - ), - - fill: { - valType: 'enumerated', - values: ['none', 'toself'], - dflt: 'none', - description: [ - 'Sets the area to fill with a solid color.', - 'Use with `fillcolor` if not *none*.', - '*toself* connects the endpoints of the trace (or each segment', - 'of the trace if it has gaps) into a closed shape.' - ].join(' ') - }, - fillcolor: makeFillcolorAttr(), - selected: scatterAttrs.selected, - unselected: scatterAttrs.unselected, + fill: { + valType: 'enumerated', + values: ['none', 'toself'], + dflt: 'none', + description: [ + 'Sets the area to fill with a solid color.', + 'Use with `fillcolor` if not *none*.', + '*toself* connects the endpoints of the trace (or each segment', + 'of the trace if it has gaps) into a closed shape.' + ].join(' ') + }, + fillcolor: makeFillcolorAttr(), + + selected: scatterAttrs.selected, + unselected: scatterAttrs.unselected, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: ['lon', 'lat', 'location', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), -}, 'calc', 'nested'); + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: ['lon', 'lat', 'location', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs() + }, + 'calc', + 'nested' +); diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index f2a1ad9b9d8..015bd849e5e 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -13,7 +13,7 @@ var attributes = require('./attributes'); const locationmodeBreakingChangeWarning = [ 'The library used by the *country names* `locationmode` option is changing in the next major version.', 'Some country names in existing plots may not work in the new version.', - 'To ensure consistent behavior, consider setting `locationmode` to *ISO-3*.', + 'To ensure consistent behavior, consider setting `locationmode` to *ISO-3*.' ].join(' '); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { @@ -24,20 +24,20 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var locations = coerce('locations'); var len; - if(locations && locations.length) { + if (locations && locations.length) { var geojson = coerce('geojson'); var locationmodeDflt; - if((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) { + if ((typeof geojson === 'string' && geojson !== '') || Lib.isPlainObject(geojson)) { locationmodeDflt = 'geojson-id'; } var locationMode = coerce('locationmode', locationmodeDflt); - if(locationMode === 'country names') { + if (locationMode === 'country names') { Lib.warn(locationmodeBreakingChangeWarning); } - if(locationMode === 'geojson-id') { + if (locationMode === 'geojson-id') { coerce('featureidkey'); } @@ -48,7 +48,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout len = Math.min(lon.length, lat.length); } - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -60,22 +60,22 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertemplate'); coerce('mode'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { + if (subTypes.hasLines(traceOut)) { handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 39312dfbd40..2d62563098a 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -16,98 +16,96 @@ var scatterLineAttrs = scatterAttrs.line; var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var attrs = module.exports = overrideAll({ - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, +var attrs = (module.exports = overrideAll( + { + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, - xperiod: scatterAttrs.xperiod, - yperiod: scatterAttrs.yperiod, - xperiod0: scatterAttrs.xperiod0, - yperiod0: scatterAttrs.yperiod0, - xperiodalignment: scatterAttrs.xperiodalignment, - yperiodalignment: scatterAttrs.yperiodalignment, - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), + xperiod: scatterAttrs.xperiod, + yperiod: scatterAttrs.yperiod, + xperiod0: scatterAttrs.xperiod0, + yperiod0: scatterAttrs.yperiod0, + xperiodalignment: scatterAttrs.xperiodalignment, + yperiodalignment: scatterAttrs.yperiodalignment, + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), - text: scatterAttrs.text, - hovertext: scatterAttrs.hovertext, + text: scatterAttrs.text, + hovertext: scatterAttrs.hovertext, - textposition: scatterAttrs.textposition, - textfont: fontAttrs({ - noFontShadow: true, - noFontLineposition: true, - noFontTextcase: true, - editType: 'calc', - colorEditType: 'style', - arrayOk: true, - noNumericWeightValues: true, - variantValues: ['normal', 'small-caps'], - description: 'Sets the text font.' - }), + textposition: scatterAttrs.textposition, + textfont: fontAttrs({ + noFontShadow: true, + noFontLineposition: true, + noFontTextcase: true, + editType: 'calc', + colorEditType: 'style', + arrayOk: true, + noNumericWeightValues: true, + variantValues: ['normal', 'small-caps'], + description: 'Sets the text font.' + }), - mode: { - valType: 'flaglist', - flags: ['lines', 'markers', 'text'], - extras: ['none'], - description: [ - 'Determines the drawing mode for this scatter trace.' - ].join(' ') - }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - shape: { - valType: 'enumerated', - values: ['linear', 'hv', 'vh', 'hvh', 'vhv'], - dflt: 'linear', - editType: 'plot', - description: [ - 'Determines the line shape.', - 'The values correspond to step-wise line shapes.' - ].join(' ') + mode: { + valType: 'flaglist', + flags: ['lines', 'markers', 'text'], + extras: ['none'], + description: ['Determines the drawing mode for this scatter trace.'].join(' ') }, - dash: { - valType: 'enumerated', - values: sortObjectKeys(DASHES), - dflt: 'solid', - description: 'Sets the style of the lines.' - } - }, - marker: extendFlat({}, colorScaleAttrs('marker'), { - symbol: scatterMarkerAttrs.symbol, - angle: scatterMarkerAttrs.angle, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: scatterMarkerAttrs.opacity, - colorbar: scatterMarkerAttrs.colorbar, - line: extendFlat({}, colorScaleAttrs('marker.line'), { - width: scatterMarkerLineAttrs.width - }) - }), - connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, {dflt: 'none'}), - fillcolor: makeFillcolorAttr(), + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + shape: { + valType: 'enumerated', + values: ['linear', 'hv', 'vh', 'hvh', 'vhv'], + dflt: 'linear', + editType: 'plot', + description: ['Determines the line shape.', 'The values correspond to step-wise line shapes.'].join(' ') + }, + dash: { + valType: 'enumerated', + values: sortObjectKeys(DASHES), + dflt: 'solid', + description: 'Sets the style of the lines.' + } + }, + marker: extendFlat({}, colorScaleAttrs('marker'), { + symbol: scatterMarkerAttrs.symbol, + angle: scatterMarkerAttrs.angle, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: scatterMarkerAttrs.opacity, + colorbar: scatterMarkerAttrs.colorbar, + line: extendFlat({}, colorScaleAttrs('marker.line'), { + width: scatterMarkerLineAttrs.width + }) + }), + connectgaps: scatterAttrs.connectgaps, + fill: extendFlat({}, scatterAttrs.fill, { dflt: 'none' }), + fillcolor: makeFillcolorAttr(), - // no hoveron + // no hoveron - selected: { - marker: scatterAttrs.selected.marker, - textfont: scatterAttrs.selected.textfont - }, - unselected: { - marker: scatterAttrs.unselected.marker, - textfont: scatterAttrs.unselected.textfont - }, - - opacity: baseAttrs.opacity + selected: { + marker: scatterAttrs.selected.marker, + textfont: scatterAttrs.selected.textfont + }, + unselected: { + marker: scatterAttrs.unselected.marker, + textfont: scatterAttrs.unselected.textfont + }, -}, 'calc', 'nested'); + opacity: baseAttrs.opacity + }, + 'calc', + 'nested' +)); attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; attrs.hovertemplate = scatterAttrs.hovertemplate; diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index ed4cac9b55e..0dfedee705e 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -19,7 +19,14 @@ var constants = require('./constants'); var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; var TEXTOFFSETSIGN = { - start: 1, left: 1, end: -1, right: -1, middle: 0, center: 0, bottom: 1, top: -1 + start: 1, + left: 1, + end: -1, + right: -1, + middle: 0, + center: 0, + bottom: 1, + top: -1 }; var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; @@ -42,29 +49,29 @@ function convertStyle(gd, trace) { var plotGlPixelRatio = gd._context.plotGlPixelRatio; - if(trace.visible !== true) return opts; + if (trace.visible !== true) return opts; - if(subTypes.hasText(trace)) { + if (subTypes.hasText(trace)) { opts.text = convertTextStyle(gd, trace); opts.textSel = convertTextSelection(gd, trace, trace.selected); opts.textUnsel = convertTextSelection(gd, trace, trace.unselected); } - if(subTypes.hasMarkers(trace)) { + if (subTypes.hasMarkers(trace)) { opts.marker = convertMarkerStyle(gd, trace); opts.markerSel = convertMarkerSelection(gd, trace, trace.selected); opts.markerUnsel = convertMarkerSelection(gd, trace, trace.unselected); - if(!trace.unselected && isArrayOrTypedArray(trace.marker.opacity)) { + if (!trace.unselected && isArrayOrTypedArray(trace.marker.opacity)) { var mo = trace.marker.opacity; opts.markerUnsel.opacity = new Array(mo.length); - for(i = 0; i < mo.length; i++) { + for (i = 0; i < mo.length; i++) { opts.markerUnsel.opacity[i] = DESELECTDIM * mo[i]; } } } - if(subTypes.hasLines(trace)) { + if (subTypes.hasLines(trace)) { opts.line = { overlay: true, thickness: trace.line.width * plotGlPixelRatio, @@ -73,21 +80,21 @@ function convertStyle(gd, trace) { }; var dashes = (constants.DASHES[trace.line.dash] || [1]).slice(); - for(i = 0; i < dashes.length; ++i) { + for (i = 0; i < dashes.length; ++i) { dashes[i] *= trace.line.width * plotGlPixelRatio; } opts.line.dashes = dashes; } - if(trace.error_x && trace.error_x.visible) { + if (trace.error_x && trace.error_x.visible) { opts.errorX = convertErrorBarStyle(trace, trace.error_x, plotGlPixelRatio); } - if(trace.error_y && trace.error_y.visible) { + if (trace.error_y && trace.error_y.visible) { opts.errorY = convertErrorBarStyle(trace, trace.error_y, plotGlPixelRatio); } - if(!!trace.fill && trace.fill !== 'none') { + if (!!trace.fill && trace.fill !== 'none') { opts.fill = { closed: true, fill: trace.fillcolor, @@ -115,18 +122,22 @@ function convertTextStyle(gd, trace) { var plotGlPixelRatio = gd._context.plotGlPixelRatio; var texttemplate = trace.texttemplate; - if(texttemplate) { + if (texttemplate) { optsOut.text = []; var d3locale = fullLayout._d3locale; var isArray = Array.isArray(texttemplate); var N = isArray ? Math.min(texttemplate.length, count) : count; - var txt = isArray ? - function(i) { return texttemplate[i]; } : - function() { return texttemplate; }; - - for(i = 0; i < N; i++) { - var d = {i: i}; + var txt = isArray + ? function (i) { + return texttemplate[i]; + } + : function () { + return texttemplate; + }; + + for (i = 0; i < N; i++) { + var d = { i: i }; var labels = trace._module.formatLabels(d, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, i); @@ -134,7 +145,7 @@ function convertTextStyle(gd, trace) { optsOut.text.push(Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta)); } } else { - if(isArrayOrTypedArray(trace.text) && trace.text.length < count) { + if (isArrayOrTypedArray(trace.text) && trace.text.length < count) { // if text array is shorter, we'll need to append to it, so let's slice to prevent mutating optsOut.text = trace.text.slice(); } else { @@ -142,8 +153,8 @@ function convertTextStyle(gd, trace) { } } // pad text array with empty strings - if(isArrayOrTypedArray(optsOut.text)) { - for(i = optsOut.text.length; i < count; i++) { + if (isArrayOrTypedArray(optsOut.text)) { + for (i = optsOut.text.length; i < count; i++) { optsOut.text[i] = ''; } } @@ -153,10 +164,10 @@ function convertTextStyle(gd, trace) { optsOut.align = []; optsOut.baseline = []; - for(i = 0; i < textPos.length; i++) { + for (i = 0; i < textPos.length; i++) { var tp = textPos[i].split(/\s+/); - switch(tp[1]) { + switch (tp[1]) { case 'left': optsOut.align.push('right'); break; @@ -166,7 +177,7 @@ function convertTextStyle(gd, trace) { default: optsOut.align.push(tp[1]); } - switch(tp[0]) { + switch (tp[0]) { case 'top': optsOut.baseline.push('bottom'); break; @@ -178,16 +189,16 @@ function convertTextStyle(gd, trace) { } } - if(isArrayOrTypedArray(tfc)) { + if (isArrayOrTypedArray(tfc)) { optsOut.color = new Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { optsOut.color[i] = tfc[i]; } } else { optsOut.color = tfc; } - if( + if ( isArrayOrTypedArray(tfs) || Array.isArray(tff) || isArrayOrTypedArray(tfw) || @@ -196,15 +207,12 @@ function convertTextStyle(gd, trace) { ) { // if any textfont param is array - make render a batch optsOut.font = new Array(count); - for(i = 0; i < count; i++) { - var fonti = optsOut.font[i] = {}; + for (i = 0; i < count; i++) { + var fonti = (optsOut.font[i] = {}); - fonti.size = ( - Lib.isTypedArray(tfs) ? tfs[i] : - isArrayOrTypedArray(tfs) ? ( - isNumeric(tfs[i]) ? tfs[i] : 0 - ) : tfs - ) * plotGlPixelRatio; + fonti.size = + (Lib.isTypedArray(tfs) ? tfs[i] : isArrayOrTypedArray(tfs) ? (isNumeric(tfs[i]) ? tfs[i] : 0) : tfs) * + plotGlPixelRatio; fonti.family = Array.isArray(tff) ? tff[i] : tff; fonti.weight = weightFallBack(isArrayOrTypedArray(tfw) ? tfw[i] : tfw); @@ -228,7 +236,7 @@ function convertTextStyle(gd, trace) { // scattergl rendering pipeline has limited support of numeric weight values // Here we map the numbers to be either bold or normal. function weightFallBack(w) { - if(w <= 1000) { + if (w <= 1000) { return w > 500 ? 'bold' : 'normal'; } return w; @@ -249,10 +257,10 @@ function convertMarkerStyle(gd, trace) { var multiLineWidth = isArrayOrTypedArray(optsIn.line.width); var isOpen; - if(!multiSymbol) isOpen = helpers.isOpenSymbol(optsIn.symbol); + if (!multiSymbol) isOpen = helpers.isOpenSymbol(optsIn.symbol); // prepare colors - if(multiSymbol || multiColor || multiLineColor || multiOpacity || multiAngle) { + if (multiSymbol || multiColor || multiLineColor || multiOpacity || multiAngle) { optsOut.symbols = new Array(count); optsOut.angles = new Array(count); optsOut.colors = new Array(count); @@ -263,31 +271,31 @@ function convertMarkerStyle(gd, trace) { var colors = formatColor(optsIn, optsIn.opacity, count); var borderColors = formatColor(optsIn.line, optsIn.opacity, count); - if(!isArrayOrTypedArray(borderColors[0])) { + if (!isArrayOrTypedArray(borderColors[0])) { var borderColor = borderColors; borderColors = Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { borderColors[i] = borderColor; } } - if(!isArrayOrTypedArray(colors[0])) { + if (!isArrayOrTypedArray(colors[0])) { var color = colors; colors = Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { colors[i] = color; } } - if(!isArrayOrTypedArray(symbols)) { + if (!isArrayOrTypedArray(symbols)) { var symbol = symbols; symbols = Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { symbols[i] = symbol; } } - if(!isArrayOrTypedArray(angles)) { + if (!isArrayOrTypedArray(angles)) { var angle = angles; angles = Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { angles[i] = angle; } } @@ -297,11 +305,11 @@ function convertMarkerStyle(gd, trace) { optsOut.colors = colors; optsOut.borderColors = borderColors; - for(i = 0; i < count; i++) { - if(multiSymbol) { + for (i = 0; i < count; i++) { + if (multiSymbol) { isOpen = helpers.isOpenSymbol(optsIn.symbol[i]); } - if(isOpen) { + if (isOpen) { borderColors[i] = colors[i].slice(); colors[i] = colors[i].slice(); colors[i][3] = 0; @@ -311,14 +319,17 @@ function convertMarkerStyle(gd, trace) { optsOut.opacity = trace.opacity; optsOut.markers = new Array(count); - for(i = 0; i < count; i++) { - optsOut.markers[i] = getSymbolSdf({ - mx: optsOut.symbols[i], - ma: optsOut.angles[i] - }, trace); + for (i = 0; i < count; i++) { + optsOut.markers[i] = getSymbolSdf( + { + mx: optsOut.symbols[i], + ma: optsOut.angles[i] + }, + trace + ); } } else { - if(isOpen) { + if (isOpen) { optsOut.color = rgba(optsIn.color, 'uint8'); optsOut.color[3] = 0; optsOut.borderColor = rgba(optsIn.color, 'uint8'); @@ -329,10 +340,13 @@ function convertMarkerStyle(gd, trace) { optsOut.opacity = trace.opacity * optsIn.opacity; - optsOut.marker = getSymbolSdf({ - mx: optsIn.symbol, - ma: optsIn.angle - }, trace); + optsOut.marker = getSymbolSdf( + { + mx: optsIn.symbol, + ma: optsIn.angle + }, + trace + ); } // prepare sizes @@ -340,40 +354,40 @@ function convertMarkerStyle(gd, trace) { var markerSizeFunc = makeBubbleSizeFn(trace, sizeFactor); var s; - if(multiSize || multiLineWidth) { - var sizes = optsOut.sizes = new Array(count); - var borderSizes = optsOut.borderSizes = new Array(count); + if (multiSize || multiLineWidth) { + var sizes = (optsOut.sizes = new Array(count)); + var borderSizes = (optsOut.borderSizes = new Array(count)); var sizeTotal = 0; var sizeAvg; - if(multiSize) { - for(i = 0; i < count; i++) { + if (multiSize) { + for (i = 0; i < count; i++) { sizes[i] = markerSizeFunc(optsIn.size[i]); sizeTotal += sizes[i]; } sizeAvg = sizeTotal / count; } else { s = markerSizeFunc(optsIn.size); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { sizes[i] = s; } } // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - if(multiLineWidth) { - for(i = 0; i < count; i++) { + if (multiLineWidth) { + for (i = 0; i < count; i++) { borderSizes[i] = optsIn.line.width[i]; } } else { s = optsIn.line.width; - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { borderSizes[i] = s; } } optsOut.sizeAvg = sizeAvg; } else { - optsOut.size = markerSizeFunc(optsIn && optsIn.size || 10); + optsOut.size = markerSizeFunc((optsIn && optsIn.size) || 10); optsOut.borderSizes = markerSizeFunc(optsIn.line.width); } @@ -384,14 +398,14 @@ function convertMarkerSelection(gd, trace, target) { var optsIn = trace.marker; var optsOut = {}; - if(!target) return optsOut; + if (!target) return optsOut; - if(target.marker && target.marker.symbol) { + if (target.marker && target.marker.symbol) { optsOut = convertMarkerStyle(gd, Lib.extendFlat({}, optsIn, target.marker)); - } else if(target.marker) { - if(target.marker.size) optsOut.size = target.marker.size; - if(target.marker.color) optsOut.colors = target.marker.color; - if(target.marker.opacity !== undefined) optsOut.opacity = target.marker.opacity; + } else if (target.marker) { + if (target.marker.size) optsOut.size = target.marker.size; + if (target.marker.color) optsOut.colors = target.marker.color; + if (target.marker.opacity !== undefined) optsOut.opacity = target.marker.opacity; } return optsOut; @@ -400,9 +414,9 @@ function convertMarkerSelection(gd, trace, target) { function convertTextSelection(gd, trace, target) { var optsOut = {}; - if(!target) return optsOut; + if (!target) return optsOut; - if(target.textfont) { + if (target.textfont) { var optsIn = { opacity: 1, text: trace.text, @@ -410,7 +424,7 @@ function convertTextSelection(gd, trace, target) { textposition: trace.textposition, textfont: Lib.extendFlat({}, trace.textfont) }; - if(target.textfont) { + if (target.textfont) { Lib.extendFlat(optsIn.textfont, target.textfont); } optsOut = convertTextStyle(gd, optsIn); @@ -426,7 +440,7 @@ function convertErrorBarStyle(trace, target, plotGlPixelRatio) { color: target.color }; - if(target.copy_ystyle) { + if (target.copy_ystyle) { optsOut = trace.error_y; } @@ -441,7 +455,7 @@ var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); function getSymbolSdf(d, trace) { var symbol = d.mx; - if(symbol === 'circle') return null; + if (symbol === 'circle') return null; var symbolPath, symbolSdf; var symbolNumber = Drawing.symbolNumber(symbol); @@ -452,13 +466,13 @@ function getSymbolSdf(d, trace) { var isDot = helpers.isDotSymbol(symbol); // until we may handle angles in shader? - if(d.ma) symbol += '_' + d.ma; + if (d.ma) symbol += '_' + d.ma; // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) return SYMBOL_SDF[symbol]; + if (SYMBOL_SDF[symbol]) return SYMBOL_SDF[symbol]; var angle = Drawing.getMarkerAngle(d, trace); - if(isDot && !symbolNoDot) { + if (isDot && !symbolNoDot) { symbolPath = symbolFunc(SYMBOL_SIZE * 1.1, angle) + SYMBOL_SVG_CIRCLE; } else { symbolPath = symbolFunc(SYMBOL_SIZE, angle); @@ -482,15 +496,15 @@ function convertLinePositions(gd, trace, positions) { var linePositions; var i; - if(subTypes.hasLines(trace) && count) { - if(trace.line.shape === 'hv') { + if (subTypes.hasLines(trace) && count) { + if (trace.line.shape === 'hv') { linePositions = []; - for(i = 0; i < count - 1; i++) { - if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) { + for (i = 0; i < count - 1; i++) { + if (isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) { linePositions.push(NaN, NaN, NaN, NaN); } else { linePositions.push(positions[i * 2], positions[i * 2 + 1]); - if(!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) { + if (!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) { linePositions.push(positions[i * 2 + 2], positions[i * 2 + 1]); } else { linePositions.push(NaN, NaN); @@ -498,11 +512,16 @@ function convertLinePositions(gd, trace, positions) { } } linePositions.push(positions[len - 2], positions[len - 1]); - } else if(trace.line.shape === 'hvh') { + } else if (trace.line.shape === 'hvh') { linePositions = []; - for(i = 0; i < count - 1; i++) { - if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1]) || isNaN(positions[i * 2 + 2]) || isNaN(positions[i * 2 + 3])) { - if(!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) { + for (i = 0; i < count - 1; i++) { + if ( + isNaN(positions[i * 2]) || + isNaN(positions[i * 2 + 1]) || + isNaN(positions[i * 2 + 2]) || + isNaN(positions[i * 2 + 3]) + ) { + if (!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) { linePositions.push(positions[i * 2], positions[i * 2 + 1]); } else { linePositions.push(NaN, NaN); @@ -521,11 +540,16 @@ function convertLinePositions(gd, trace, positions) { } } linePositions.push(positions[len - 2], positions[len - 1]); - } else if(trace.line.shape === 'vhv') { + } else if (trace.line.shape === 'vhv') { linePositions = []; - for(i = 0; i < count - 1; i++) { - if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1]) || isNaN(positions[i * 2 + 2]) || isNaN(positions[i * 2 + 3])) { - if(!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) { + for (i = 0; i < count - 1; i++) { + if ( + isNaN(positions[i * 2]) || + isNaN(positions[i * 2 + 1]) || + isNaN(positions[i * 2 + 2]) || + isNaN(positions[i * 2 + 3]) + ) { + if (!isNaN(positions[i * 2]) && !isNaN(positions[i * 2 + 1])) { linePositions.push(positions[i * 2], positions[i * 2 + 1]); } else { linePositions.push(NaN, NaN); @@ -544,14 +568,14 @@ function convertLinePositions(gd, trace, positions) { } } linePositions.push(positions[len - 2], positions[len - 1]); - } else if(trace.line.shape === 'vh') { + } else if (trace.line.shape === 'vh') { linePositions = []; - for(i = 0; i < count - 1; i++) { - if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) { + for (i = 0; i < count - 1; i++) { + if (isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) { linePositions.push(NaN, NaN, NaN, NaN); } else { linePositions.push(positions[i * 2], positions[i * 2 + 1]); - if(!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) { + if (!isNaN(positions[i * 2 + 2]) && !isNaN(positions[i * 2 + 3])) { linePositions.push(positions[i * 2], positions[i * 2 + 3]); } else { linePositions.push(NaN, NaN); @@ -567,23 +591,27 @@ function convertLinePositions(gd, trace, positions) { // If we have data with gaps, we ought to use rect joins // FIXME: get rid of this var hasNaN = false; - for(i = 0; i < linePositions.length; i++) { - if(isNaN(linePositions[i])) { + for (i = 0; i < linePositions.length; i++) { + if (isNaN(linePositions[i])) { hasNaN = true; break; } } - var join = (hasNaN || linePositions.length > constants.TOO_MANY_POINTS) ? 'rect' : - subTypes.hasMarkers(trace) ? 'rect' : 'round'; + var join = + hasNaN || linePositions.length > constants.TOO_MANY_POINTS + ? 'rect' + : subTypes.hasMarkers(trace) + ? 'rect' + : 'round'; // fill gaps - if(hasNaN && trace.connectgaps) { + if (hasNaN && trace.connectgaps) { var lastX = linePositions[0]; var lastY = linePositions[1]; - for(i = 0; i < linePositions.length; i += 2) { - if(isNaN(linePositions[i]) || isNaN(linePositions[i + 1])) { + for (i = 0; i < linePositions.length; i += 2) { + if (isNaN(linePositions[i]) || isNaN(linePositions[i + 1])) { linePositions[i] = lastX; linePositions[i + 1] = lastY; } else { @@ -610,24 +638,24 @@ function convertErrorBarPositions(gd, trace, positions, x, y) { var axLetter = ax._id.charAt(0); var opts = trace['error_' + axLetter]; - if(opts && opts.visible && (ax.type === 'linear' || ax.type === 'log')) { + if (opts && opts.visible && (ax.type === 'linear' || ax.type === 'log')) { var computeError = makeComputeError(opts); - var pOffset = {x: 0, y: 1}[axLetter]; - var eOffset = {x: [0, 1, 2, 3], y: [2, 3, 0, 1]}[axLetter]; + var pOffset = { x: 0, y: 1 }[axLetter]; + var eOffset = { x: [0, 1, 2, 3], y: [2, 3, 0, 1] }[axLetter]; var errors = new Float64Array(4 * count); var minShoe = Infinity; var maxHat = -Infinity; - for(var i = 0, j = 0; i < count; i++, j += 4) { + for (var i = 0, j = 0; i < count; i++, j += 4) { var dc = coords[i]; - if(isNumeric(dc)) { + if (isNumeric(dc)) { var dl = positions[i * 2 + pOffset]; var vals = computeError(dc, i); var lv = vals[0]; var hv = vals[1]; - if(isNumeric(lv) && isNumeric(hv)) { + if (isNumeric(lv) && isNumeric(hv)) { var shoe = dc - lv; var hat = dc + hv; @@ -661,28 +689,24 @@ function convertTextPosition(gd, trace, textOpts, markerOpts) { var i; // corresponds to textPointPosition from component.drawing - if(subTypes.hasMarkers(trace)) { + if (subTypes.hasMarkers(trace)) { var fontOpts = textOpts.font; var align = textOpts.align; var baseline = textOpts.baseline; out.offset = new Array(count); - for(i = 0; i < count; i++) { + for (i = 0; i < count; i++) { var ms = markerOpts.sizes ? markerOpts.sizes[i] : markerOpts.size; var fs = isArrayOrTypedArray(fontOpts) ? fontOpts[i].size : fontOpts.size; - var a = isArrayOrTypedArray(align) ? - (align.length > 1 ? align[i] : align[0]) : - align; - var b = isArrayOrTypedArray(baseline) ? - (baseline.length > 1 ? baseline[i] : baseline[0]) : - baseline; + var a = isArrayOrTypedArray(align) ? (align.length > 1 ? align[i] : align[0]) : align; + var b = isArrayOrTypedArray(baseline) ? (baseline.length > 1 ? baseline[i] : baseline[0]) : baseline; var hSign = TEXTOFFSETSIGN[a]; var vSign = TEXTOFFSETSIGN[b]; var xPad = ms ? ms / 0.8 + 1 : 0; var yPad = -vSign * xPad - vSign * 0.5; - out.offset[i] = [hSign * xPad / fs, yPad / fs]; + out.offset[i] = [(hSign * xPad) / fs, yPad / fs]; } } diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index bcb4e79edc0..ecda5087fdf 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -23,7 +23,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var isBubble = subTypes.isBubble(traceIn); var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -39,23 +39,23 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertemplate'); coerce('mode', defaultMode); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noAngleRef: true, noStandOff: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); coerce('marker.line.width', isOpen || isBubble ? 1 : 0); } - if(subTypes.hasLines(traceOut)) { + if (subTypes.hasLines(traceOut)) { coerce('connectgaps'); handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('line.shape'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noFontShadow: true, noFontLineposition: true, - noFontTextcase: true, + noFontTextcase: true }); } @@ -63,13 +63,13 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var markerColor = (traceOut.marker || {}).color; coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, {axis: 'x', inherit: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'y' }); + errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'x', inherit: 'y' }); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/scattermap/attributes.js b/src/traces/scattermap/attributes.js index c4c9fb05a9d..132a7b9229c 100644 --- a/src/traces/scattermap/attributes.js +++ b/src/traces/scattermap/attributes.js @@ -16,167 +16,169 @@ var mapLayoutAtributes = require('../../plots/map/layout_attributes'); var lineAttrs = scatterGeoAttrs.line; var markerAttrs = scatterGeoAttrs.marker; -module.exports = overrideAll({ - lon: scatterGeoAttrs.lon, - lat: scatterGeoAttrs.lat, - - cluster: { - enabled: { - valType: 'boolean', - description: 'Determines whether clustering is enabled or disabled.' +module.exports = overrideAll( + { + lon: scatterGeoAttrs.lon, + lat: scatterGeoAttrs.lat, + + cluster: { + enabled: { + valType: 'boolean', + description: 'Determines whether clustering is enabled or disabled.' + }, + maxzoom: extendFlat({}, mapLayoutAtributes.layers.maxzoom, { + description: [ + 'Sets the maximum zoom level.', + 'At zoom levels equal to or greater than this, points will never be clustered.' + ].join(' ') + }), + step: { + valType: 'number', + arrayOk: true, + dflt: -1, + min: -1, + description: [ + 'Sets how many points it takes to create a cluster or advance to the next cluster step.', + 'Use this in conjunction with arrays for `size` and / or `color`.', + 'If an integer, steps start at multiples of this number.', + 'If an array, each step extends from the given value until one less than the next value.' + ].join(' ') + }, + size: { + valType: 'number', + arrayOk: true, + dflt: 20, + min: 0, + description: ['Sets the size for each cluster step.'].join(' ') + }, + color: { + valType: 'color', + arrayOk: true, + description: ['Sets the color for each cluster step.'].join(' ') + }, + opacity: extendFlat({}, markerAttrs.opacity, { + dflt: 1 + }) }, - maxzoom: extendFlat({}, mapLayoutAtributes.layers.maxzoom, { + + // locations + // locationmode + + mode: extendFlat({}, scatterAttrs.mode, { + dflt: 'markers', description: [ - 'Sets the maximum zoom level.', - 'At zoom levels equal to or greater than this, points will never be clustered.' + 'Determines the drawing mode for this scatter trace.', + 'If the provided `mode` includes *text* then the `text` elements', + 'appear at the coordinates. Otherwise, the `text` elements', + 'appear on hover.' ].join(' ') }), - step: { - valType: 'number', - arrayOk: true, - dflt: -1, - min: -1, - description: [ - 'Sets how many points it takes to create a cluster or advance to the next cluster step.', - 'Use this in conjunction with arrays for `size` and / or `color`.', - 'If an integer, steps start at multiples of this number.', - 'If an array, each step extends from the given value until one less than the next value.' - ].join(' ') - }, - size: { - valType: 'number', - arrayOk: true, - dflt: 20, - min: 0, + + text: extendFlat({}, scatterAttrs.text, { description: [ - 'Sets the size for each cluster step.' + 'Sets text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) coordinates.", + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' ].join(' ') - }, - color: { - valType: 'color', - arrayOk: true, + }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['lat', 'lon', 'text'] + } + ), + hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ - 'Sets the color for each cluster step.' + 'Sets hover text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) coordinates.", + 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') - }, - opacity: extendFlat({}, markerAttrs.opacity, { - dflt: 1 - }) - }, + }), - // locations - // locationmode - - mode: extendFlat({}, scatterAttrs.mode, { - dflt: 'markers', - description: [ - 'Determines the drawing mode for this scatter trace.', - 'If the provided `mode` includes *text* then the `text` elements', - 'appear at the coordinates. Otherwise, the `text` elements', - 'appear on hover.' - ].join(' ') - }), - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (lon,lat) pair', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['lat', 'lon', 'text'] - }), - hovertext: extendFlat({}, scatterAttrs.hovertext, { - description: [ - 'Sets hover text elements associated with each (lon,lat) pair', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.', - 'To be seen, trace `hoverinfo` must contain a *text* flag.' - ].join(' ') - }), - - line: { - color: lineAttrs.color, - width: lineAttrs.width - - // TODO - // dash: dash - }, + line: { + color: lineAttrs.color, + width: lineAttrs.width - connectgaps: scatterAttrs.connectgaps, + // TODO + // dash: dash + }, - marker: extendFlat({ - symbol: { + connectgaps: scatterAttrs.connectgaps, + + marker: extendFlat( + { + symbol: { + valType: 'string', + dflt: 'circle', + arrayOk: true, + description: [ + 'Sets the marker symbol.', + 'Full list: https://www.mapbox.com/maki-icons/', + 'Note that the array `marker.color` and `marker.size`', + 'are only available for *circle* symbols.' + ].join(' ') + }, + angle: { + valType: 'number', + dflt: 'auto', + arrayOk: true, + description: [ + 'Sets the marker orientation from true North, in degrees clockwise.', + 'When using the *auto* default, no rotation would be applied', + 'in perspective views which is different from using a zero angle.' + ].join(' ') + }, + allowoverlap: { + valType: 'boolean', + dflt: false, + description: ['Flag to draw all symbols, even if they overlap.'].join(' ') + }, + opacity: markerAttrs.opacity, + size: markerAttrs.size, + sizeref: markerAttrs.sizeref, + sizemin: markerAttrs.sizemin, + sizemode: markerAttrs.sizemode + }, + colorScaleAttrs('marker') + // line + ), + + fill: scatterGeoAttrs.fill, + fillcolor: makeFillcolorAttr(), + + textfont: mapAttrs.layers.symbol.textfont, + textposition: mapAttrs.layers.symbol.textposition, + + below: { valType: 'string', - dflt: 'circle', - arrayOk: true, description: [ - 'Sets the marker symbol.', - 'Full list: https://www.mapbox.com/maki-icons/', - 'Note that the array `marker.color` and `marker.size`', - 'are only available for *circle* symbols.' + "Determines if this scattermap trace's layers are to be inserted", + 'before the layer with the specified ID.', + 'By default, scattermap layers are inserted', + 'above all the base layers.', + "To place the scattermap layers above every other layer, set `below` to *''*." ].join(' ') }, - angle: { - valType: 'number', - dflt: 'auto', - arrayOk: true, - description: [ - 'Sets the marker orientation from true North, in degrees clockwise.', - 'When using the *auto* default, no rotation would be applied', - 'in perspective views which is different from using a zero angle.' - ].join(' ') + + selected: { + marker: scatterAttrs.selected.marker }, - allowoverlap: { - valType: 'boolean', - dflt: false, - description: [ - 'Flag to draw all symbols, even if they overlap.' - ].join(' ') + unselected: { + marker: scatterAttrs.unselected.marker }, - opacity: markerAttrs.opacity, - size: markerAttrs.size, - sizeref: markerAttrs.sizeref, - sizemin: markerAttrs.sizemin, - sizemode: markerAttrs.sizemode - }, - colorScaleAttrs('marker') - // line - ), - - fill: scatterGeoAttrs.fill, - fillcolor: makeFillcolorAttr(), - - textfont: mapAttrs.layers.symbol.textfont, - textposition: mapAttrs.layers.symbol.textposition, - - below: { - valType: 'string', - description: [ - 'Determines if this scattermap trace\'s layers are to be inserted', - 'before the layer with the specified ID.', - 'By default, scattermap layers are inserted', - 'above all the base layers.', - 'To place the scattermap layers above every other layer, set `below` to *\'\'*.' - ].join(' ') - }, - selected: { - marker: scatterAttrs.selected.marker - }, - unselected: { - marker: scatterAttrs.unselected.marker + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: ['lon', 'lat', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs() }, - - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: ['lon', 'lat', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), -}, 'calc', 'nested'); + 'calc', + 'nested' +); diff --git a/src/traces/scattermap/convert.js b/src/traces/scattermap/convert.js index a281512acdd..653fb160ff4 100644 --- a/src/traces/scattermap/convert.js +++ b/src/traces/scattermap/convert.js @@ -20,13 +20,13 @@ var BR_TAG_ALL = require('../../lib/svg_text_utils').BR_TAG_ALL; module.exports = function convert(gd, calcTrace) { var trace = calcTrace[0].trace; - var isVisible = (trace.visible === true && trace._length !== 0); - var hasFill = (trace.fill !== 'none'); + var isVisible = trace.visible === true && trace._length !== 0; + var hasFill = trace.fill !== 'none'; var hasLines = subTypes.hasLines(trace); var hasMarkers = subTypes.hasMarkers(trace); var hasText = subTypes.hasText(trace); - var hasCircles = (hasMarkers && trace.marker.symbol === 'circle'); - var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle'); + var hasCircles = hasMarkers && trace.marker.symbol === 'circle'; + var hasSymbols = hasMarkers && trace.marker.symbol !== 'circle'; var hasCluster = trace.cluster && trace.cluster.enabled; var fill = initContainer('fill'); @@ -42,15 +42,15 @@ module.exports = function convert(gd, calcTrace) { }; // early return if not visible or placeholder - if(!isVisible) return opts; + if (!isVisible) return opts; // fill layer and line layer use the same coords var lineCoords; - if(hasFill || hasLines) { + if (hasFill || hasLines) { lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace); } - if(hasFill) { + if (hasFill) { fill.geojson = geoJsonUtils.makePolygon(lineCoords); fill.layout.visibility = 'visible'; @@ -59,7 +59,7 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasLines) { + if (hasLines) { line.geojson = geoJsonUtils.makeLine(lineCoords); line.layout.visibility = 'visible'; @@ -72,21 +72,21 @@ module.exports = function convert(gd, calcTrace) { // TODO convert line.dash into line-dasharray } - if(hasCircles) { + if (hasCircles) { var circleOpts = makeCircleOpts(calcTrace); circle.geojson = circleOpts.geojson; circle.layout.visibility = 'visible'; - if(hasCluster) { + if (hasCluster) { circle.filter = ['!', ['has', 'point_count']]; opts.cluster = { type: 'circle', filter: ['has', 'point_count'], - layout: {visibility: 'visible'}, + layout: { visibility: 'visible' }, paint: { 'circle-color': arrayifyAttribute(trace.cluster.color, trace.cluster.step), 'circle-radius': arrayifyAttribute(trace.cluster.size, trace.cluster.step), - 'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step), - }, + 'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step) + } }; opts.clusterCount = { type: 'symbol', @@ -107,11 +107,11 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasCircles && hasCluster) { + if (hasCircles && hasCluster) { circle.filter = ['!', ['has', 'point_count']]; } - if(hasSymbols || hasText) { + if (hasSymbols || hasText) { symbol.geojson = makeSymbolGeoJSON(calcTrace, gd); Lib.extendFlat(symbol.layout, { @@ -120,17 +120,18 @@ module.exports = function convert(gd, calcTrace) { 'text-field': '{text}' }); - if(hasSymbols) { + if (hasSymbols) { Lib.extendFlat(symbol.layout, { 'icon-size': trace.marker.size / 10 }); - if('angle' in trace.marker && trace.marker.angle !== 'auto') { + if ('angle' in trace.marker && trace.marker.angle !== 'auto') { Lib.extendFlat(symbol.layout, { - // unfortunately cant use {angle} do to this issue: - // https://github.com/mapbox/mapbox-gl-js/issues/873 + // unfortunately cant use {angle} do to this issue: + // https://github.com/mapbox/mapbox-gl-js/issues/873 'icon-rotate': { - type: 'identity', property: 'angle' + type: 'identity', + property: 'angle' }, 'icon-rotation-alignment': 'map' }); @@ -146,7 +147,7 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasText) { + if (hasText) { var iconSize = (trace.marker || {}).size; var textOpts = convertTextOpts(trace.textposition, iconSize); @@ -188,13 +189,17 @@ function makeCircleOpts(calcTrace) { var arrayOpacity = Lib.isArrayOrTypedArray(marker.opacity); var i; - function addTraceOpacity(o) { return trace.opacity * o; } + function addTraceOpacity(o) { + return trace.opacity * o; + } - function size2radius(s) { return s / 2; } + function size2radius(s) { + return s / 2; + } var colorFn; - if(arrayColor) { - if(Colorscale.hasColorscale(trace, 'marker')) { + if (arrayColor) { + if (Colorscale.hasColorscale(trace, 'marker')) { colorFn = Colorscale.makeColorScaleFuncFromTrace(marker); } else { colorFn = Lib.identity; @@ -202,30 +207,30 @@ function makeCircleOpts(calcTrace) { } var sizeFn; - if(arraySize) { + if (arraySize) { sizeFn = makeBubbleSizeFn(trace); } var opacityFn; - if(arrayOpacity) { - opacityFn = function(mo) { + if (arrayOpacity) { + opacityFn = function (mo) { var mo2 = isNumeric(mo) ? +Lib.constrain(mo, 0, 1) : 0; return addTraceOpacity(mo2); }; } var features = []; - for(i = 0; i < calcTrace.length; i++) { + for (i = 0; i < calcTrace.length; i++) { var calcPt = calcTrace[i]; var lonlat = calcPt.lonlat; - if(isBADNUM(lonlat)) continue; + if (isBADNUM(lonlat)) continue; var props = {}; - if(colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc); - if(sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms); - if(opacityFn) props.mo = opacityFn(calcPt.mo); - if(selectedpoints) props.selected = calcPt.selected || 0; + if (colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc); + if (sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms); + if (opacityFn) props.mo = opacityFn(calcPt.mo); + if (selectedpoints) props.selected = calcPt.selected || 0; features.push({ type: 'Feature', @@ -236,35 +241,33 @@ function makeCircleOpts(calcTrace) { } var fns; - if(selectedpoints) { + if (selectedpoints) { fns = Drawing.makeSelectedPointStyleFns(trace); - for(i = 0; i < features.length; i++) { + for (i = 0; i < features.length; i++) { var d = features[i].properties; - if(fns.selectedOpacityFn) { + if (fns.selectedOpacityFn) { d.mo = addTraceOpacity(fns.selectedOpacityFn(d)); } - if(fns.selectedColorFn) { + if (fns.selectedColorFn) { d.mcc = fns.selectedColorFn(d); } - if(fns.selectedSizeFn) { + if (fns.selectedSizeFn) { d.mrc = fns.selectedSizeFn(d); } } } return { - geojson: {type: 'FeatureCollection', features: features}, - mcc: arrayColor || (fns && fns.selectedColorFn) ? - {type: 'identity', property: 'mcc'} : - marker.color, - mrc: arraySize || (fns && fns.selectedSizeFn) ? - {type: 'identity', property: 'mrc'} : - size2radius(marker.size), - mo: arrayOpacity || (fns && fns.selectedOpacityFn) ? - {type: 'identity', property: 'mo'} : - addTraceOpacity(marker.opacity) + geojson: { type: 'FeatureCollection', features: features }, + mcc: arrayColor || (fns && fns.selectedColorFn) ? { type: 'identity', property: 'mcc' } : marker.color, + mrc: + arraySize || (fns && fns.selectedSizeFn) ? { type: 'identity', property: 'mrc' } : size2radius(marker.size), + mo: + arrayOpacity || (fns && fns.selectedOpacityFn) + ? { type: 'identity', property: 'mo' } + : addTraceOpacity(marker.opacity) }; } @@ -276,31 +279,24 @@ function makeSymbolGeoJSON(calcTrace, gd) { var symbol = marker.symbol; var angle = marker.angle; - var fillSymbol = (symbol !== 'circle') ? - getFillFunc(symbol) : - blankFillFunc; - - var fillAngle = (angle !== 'auto') ? - getFillFunc(angle, true) : - blankFillFunc; + var fillSymbol = symbol !== 'circle' ? getFillFunc(symbol) : blankFillFunc; - var fillText = subTypes.hasText(trace) ? - getFillFunc(trace.text) : - blankFillFunc; + var fillAngle = angle !== 'auto' ? getFillFunc(angle, true) : blankFillFunc; + var fillText = subTypes.hasText(trace) ? getFillFunc(trace.text) : blankFillFunc; var features = []; - for(var i = 0; i < calcTrace.length; i++) { + for (var i = 0; i < calcTrace.length; i++) { var calcPt = calcTrace[i]; - if(isBADNUM(calcPt.lonlat)) continue; + if (isBADNUM(calcPt.lonlat)) continue; var texttemplate = trace.texttemplate; var text; - if(texttemplate) { - var tt = Array.isArray(texttemplate) ? (texttemplate[i] || '') : texttemplate; + if (texttemplate) { + var tt = Array.isArray(texttemplate) ? texttemplate[i] || '' : texttemplate; var labels = trace._module.formatLabels(calcPt, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); @@ -310,7 +306,7 @@ function makeSymbolGeoJSON(calcTrace, gd) { text = fillText(i); } - if(text) { + if (text) { text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); } @@ -335,19 +331,27 @@ function makeSymbolGeoJSON(calcTrace, gd) { } function getFillFunc(attr, numeric) { - if(Lib.isArrayOrTypedArray(attr)) { - if(numeric) { - return function(i) { return isNumeric(attr[i]) ? +attr[i] : 0; }; + if (Lib.isArrayOrTypedArray(attr)) { + if (numeric) { + return function (i) { + return isNumeric(attr[i]) ? +attr[i] : 0; + }; } - return function(i) { return attr[i]; }; - } else if(attr) { - return function() { return attr; }; + return function (i) { + return attr[i]; + }; + } else if (attr) { + return function () { + return attr; + }; } else { return blankFillFunc; } } -function blankFillFunc() { return ''; } +function blankFillFunc() { + return ''; +} // only need to check lon (OR lat) function isBADNUM(lonlat) { @@ -356,10 +360,10 @@ function isBADNUM(lonlat) { function arrayifyAttribute(values, step) { var newAttribute; - if(Lib.isArrayOrTypedArray(values) && Lib.isArrayOrTypedArray(step)) { + if (Lib.isArrayOrTypedArray(values) && Lib.isArrayOrTypedArray(step)) { newAttribute = ['step', ['get', 'point_count'], values[0]]; - for(var idx = 1; idx < values.length; idx++) { + for (var idx = 1; idx < values.length; idx++) { newAttribute.push(step[idx - 1], values[idx]); } } else { @@ -376,49 +380,50 @@ function getTextFont(trace) { var parts = family.split(' '); var isItalic = parts[parts.length - 1] === 'Italic'; - if(isItalic) parts.pop(); + if (isItalic) parts.pop(); isItalic = isItalic || style === 'italic'; var str = parts.join(' '); - if(weight === 'bold' && parts.indexOf('Bold') === -1) { + if (weight === 'bold' && parts.indexOf('Bold') === -1) { str += ' Bold'; - } else if(weight <= 1000) { // numeric font-weight + } else if (weight <= 1000) { + // numeric font-weight // See supportedFonts - if(parts[0] === 'Metropolis') { + if (parts[0] === 'Metropolis') { str = 'Metropolis'; - if(weight > 850) str += ' Black'; - else if(weight > 750) str += ' Extra Bold'; - else if(weight > 650) str += ' Bold'; - else if(weight > 550) str += ' Semi Bold'; - else if(weight > 450) str += ' Medium'; - else if(weight > 350) str += ' Regular'; - else if(weight > 250) str += ' Light'; - else if(weight > 150) str += ' Extra Light'; + if (weight > 850) str += ' Black'; + else if (weight > 750) str += ' Extra Bold'; + else if (weight > 650) str += ' Bold'; + else if (weight > 550) str += ' Semi Bold'; + else if (weight > 450) str += ' Medium'; + else if (weight > 350) str += ' Regular'; + else if (weight > 250) str += ' Light'; + else if (weight > 150) str += ' Extra Light'; else str += ' Thin'; - } else if(parts.slice(0, 2).join(' ') === 'Open Sans') { + } else if (parts.slice(0, 2).join(' ') === 'Open Sans') { str = 'Open Sans'; - if(weight > 750) str += ' Extrabold'; - else if(weight > 650) str += ' Bold'; - else if(weight > 550) str += ' Semibold'; - else if(weight > 350) str += ' Regular'; + if (weight > 750) str += ' Extrabold'; + else if (weight > 650) str += ' Bold'; + else if (weight > 550) str += ' Semibold'; + else if (weight > 350) str += ' Regular'; else str += ' Light'; - } else if(parts.slice(0, 3).join(' ') === 'Klokantech Noto Sans') { + } else if (parts.slice(0, 3).join(' ') === 'Klokantech Noto Sans') { str = 'Klokantech Noto Sans'; - if(parts[3] === 'CJK') str += ' CJK'; - str += (weight > 500) ? ' Bold' : ' Regular'; + if (parts[3] === 'CJK') str += ' CJK'; + str += weight > 500 ? ' Bold' : ' Regular'; } } - if(isItalic) str += ' Italic'; + if (isItalic) str += ' Italic'; - if(str === 'Open Sans Regular Italic') str = 'Open Sans Italic'; - else if(str === 'Open Sans Regular Bold') str = 'Open Sans Bold'; - else if(str === 'Open Sans Regular Bold Italic') str = 'Open Sans Bold Italic'; - else if(str === 'Klokantech Noto Sans Regular Italic') str = 'Klokantech Noto Sans Italic'; + if (str === 'Open Sans Regular Italic') str = 'Open Sans Italic'; + else if (str === 'Open Sans Regular Bold') str = 'Open Sans Bold'; + else if (str === 'Open Sans Regular Bold Italic') str = 'Open Sans Bold Italic'; + else if (str === 'Klokantech Noto Sans Regular Italic') str = 'Klokantech Noto Sans Italic'; // Ensure the result is a supported font - if(!isSupportedFont(str)) { + if (!isSupportedFont(str)) { str = family; } diff --git a/src/traces/scattermap/defaults.js b/src/traces/scattermap/defaults.js index 2147f8ef0b6..0fe5eb99aea 100644 --- a/src/traces/scattermap/defaults.js +++ b/src/traces/scattermap/defaults.js @@ -20,7 +20,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleLonLatDefaults(traceIn, traceOut, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -32,22 +32,22 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode'); coerce('below'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noLine: true, noAngle: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noLine: true, noAngle: true }); coerce('marker.allowoverlap'); coerce('marker.angle'); // array marker.size and marker.color are only supported with circles var marker = traceOut.marker; - if(marker.symbol !== 'circle') { - if(Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0]; - if(Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0]; + if (marker.symbol !== 'circle') { + if (Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0]; + if (Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0]; } } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noDash: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noDash: true }); coerce('connectgaps'); } @@ -66,28 +66,27 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var clusterEnabled = coerce('cluster.enabled', clusterEnabledDflt); - if(clusterEnabled || subTypes.hasText(traceOut)) { + if (clusterEnabled || subTypes.hasText(traceOut)) { var layoutFontFamily = layout.font.family; - handleTextDefaults(traceIn, traceOut, layout, coerce, - { - noSelect: true, - noFontVariant: true, - noFontShadow: true, - noFontLineposition: true, - noFontTextcase: true, - font: { - family: isSupportedFont(layoutFontFamily) ? layoutFontFamily : 'Open Sans Regular', - weight: layout.font.weight, - style: layout.font.style, - size: layout.font.size, - color: layout.font.color - } - }); + handleTextDefaults(traceIn, traceOut, layout, coerce, { + noSelect: true, + noFontVariant: true, + noFontShadow: true, + noFontLineposition: true, + noFontTextcase: true, + font: { + family: isSupportedFont(layoutFontFamily) ? layoutFontFamily : 'Open Sans Regular', + weight: layout.font.weight, + style: layout.font.style, + size: layout.font.size, + color: layout.font.color + } + }); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 7686403e253..521415041a9 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -16,167 +16,169 @@ var mapboxLayoutAtributes = require('../../plots/mapbox/layout_attributes'); var lineAttrs = scatterGeoAttrs.line; var markerAttrs = scatterGeoAttrs.marker; -module.exports = overrideAll({ - lon: scatterGeoAttrs.lon, - lat: scatterGeoAttrs.lat, - - cluster: { - enabled: { - valType: 'boolean', - description: 'Determines whether clustering is enabled or disabled.' +module.exports = overrideAll( + { + lon: scatterGeoAttrs.lon, + lat: scatterGeoAttrs.lat, + + cluster: { + enabled: { + valType: 'boolean', + description: 'Determines whether clustering is enabled or disabled.' + }, + maxzoom: extendFlat({}, mapboxLayoutAtributes.layers.maxzoom, { + description: [ + 'Sets the maximum zoom level.', + 'At zoom levels equal to or greater than this, points will never be clustered.' + ].join(' ') + }), + step: { + valType: 'number', + arrayOk: true, + dflt: -1, + min: -1, + description: [ + 'Sets how many points it takes to create a cluster or advance to the next cluster step.', + 'Use this in conjunction with arrays for `size` and / or `color`.', + 'If an integer, steps start at multiples of this number.', + 'If an array, each step extends from the given value until one less than the next value.' + ].join(' ') + }, + size: { + valType: 'number', + arrayOk: true, + dflt: 20, + min: 0, + description: ['Sets the size for each cluster step.'].join(' ') + }, + color: { + valType: 'color', + arrayOk: true, + description: ['Sets the color for each cluster step.'].join(' ') + }, + opacity: extendFlat({}, markerAttrs.opacity, { + dflt: 1 + }) }, - maxzoom: extendFlat({}, mapboxLayoutAtributes.layers.maxzoom, { + + // locations + // locationmode + + mode: extendFlat({}, scatterAttrs.mode, { + dflt: 'markers', description: [ - 'Sets the maximum zoom level.', - 'At zoom levels equal to or greater than this, points will never be clustered.' + 'Determines the drawing mode for this scatter trace.', + 'If the provided `mode` includes *text* then the `text` elements', + 'appear at the coordinates. Otherwise, the `text` elements', + 'appear on hover.' ].join(' ') }), - step: { - valType: 'number', - arrayOk: true, - dflt: -1, - min: -1, - description: [ - 'Sets how many points it takes to create a cluster or advance to the next cluster step.', - 'Use this in conjunction with arrays for `size` and / or `color`.', - 'If an integer, steps start at multiples of this number.', - 'If an array, each step extends from the given value until one less than the next value.' - ].join(' ') - }, - size: { - valType: 'number', - arrayOk: true, - dflt: 20, - min: 0, + + text: extendFlat({}, scatterAttrs.text, { description: [ - 'Sets the size for each cluster step.' + 'Sets text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) coordinates.", + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' ].join(' ') - }, - color: { - valType: 'color', - arrayOk: true, + }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['lat', 'lon', 'text'] + } + ), + hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ - 'Sets the color for each cluster step.' + 'Sets hover text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + "this trace's (lon,lat) coordinates.", + 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') - }, - opacity: extendFlat({}, markerAttrs.opacity, { - dflt: 1 - }) - }, + }), - // locations - // locationmode - - mode: extendFlat({}, scatterAttrs.mode, { - dflt: 'markers', - description: [ - 'Determines the drawing mode for this scatter trace.', - 'If the provided `mode` includes *text* then the `text` elements', - 'appear at the coordinates. Otherwise, the `text` elements', - 'appear on hover.' - ].join(' ') - }), - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (lon,lat) pair', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['lat', 'lon', 'text'] - }), - hovertext: extendFlat({}, scatterAttrs.hovertext, { - description: [ - 'Sets hover text elements associated with each (lon,lat) pair', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.', - 'To be seen, trace `hoverinfo` must contain a *text* flag.' - ].join(' ') - }), - - line: { - color: lineAttrs.color, - width: lineAttrs.width - - // TODO - // dash: dash - }, + line: { + color: lineAttrs.color, + width: lineAttrs.width - connectgaps: scatterAttrs.connectgaps, + // TODO + // dash: dash + }, - marker: extendFlat({ - symbol: { + connectgaps: scatterAttrs.connectgaps, + + marker: extendFlat( + { + symbol: { + valType: 'string', + dflt: 'circle', + arrayOk: true, + description: [ + 'Sets the marker symbol.', + 'Full list: https://www.mapbox.com/maki-icons/', + 'Note that the array `marker.color` and `marker.size`', + 'are only available for *circle* symbols.' + ].join(' ') + }, + angle: { + valType: 'number', + dflt: 'auto', + arrayOk: true, + description: [ + 'Sets the marker orientation from true North, in degrees clockwise.', + 'When using the *auto* default, no rotation would be applied', + 'in perspective views which is different from using a zero angle.' + ].join(' ') + }, + allowoverlap: { + valType: 'boolean', + dflt: false, + description: ['Flag to draw all symbols, even if they overlap.'].join(' ') + }, + opacity: markerAttrs.opacity, + size: markerAttrs.size, + sizeref: markerAttrs.sizeref, + sizemin: markerAttrs.sizemin, + sizemode: markerAttrs.sizemode + }, + colorScaleAttrs('marker') + // line + ), + + fill: scatterGeoAttrs.fill, + fillcolor: makeFillcolorAttr(), + + textfont: mapboxAttrs.layers.symbol.textfont, + textposition: mapboxAttrs.layers.symbol.textposition, + + below: { valType: 'string', - dflt: 'circle', - arrayOk: true, description: [ - 'Sets the marker symbol.', - 'Full list: https://www.mapbox.com/maki-icons/', - 'Note that the array `marker.color` and `marker.size`', - 'are only available for *circle* symbols.' + "Determines if this scattermapbox trace's layers are to be inserted", + 'before the layer with the specified ID.', + 'By default, scattermapbox layers are inserted', + 'above all the base layers.', + "To place the scattermapbox layers above every other layer, set `below` to *''*." ].join(' ') }, - angle: { - valType: 'number', - dflt: 'auto', - arrayOk: true, - description: [ - 'Sets the marker orientation from true North, in degrees clockwise.', - 'When using the *auto* default, no rotation would be applied', - 'in perspective views which is different from using a zero angle.' - ].join(' ') + + selected: { + marker: scatterAttrs.selected.marker }, - allowoverlap: { - valType: 'boolean', - dflt: false, - description: [ - 'Flag to draw all symbols, even if they overlap.' - ].join(' ') + unselected: { + marker: scatterAttrs.unselected.marker }, - opacity: markerAttrs.opacity, - size: markerAttrs.size, - sizeref: markerAttrs.sizeref, - sizemin: markerAttrs.sizemin, - sizemode: markerAttrs.sizemode - }, - colorScaleAttrs('marker') - // line - ), - - fill: scatterGeoAttrs.fill, - fillcolor: makeFillcolorAttr(), - - textfont: mapboxAttrs.layers.symbol.textfont, - textposition: mapboxAttrs.layers.symbol.textposition, - - below: { - valType: 'string', - description: [ - 'Determines if this scattermapbox trace\'s layers are to be inserted', - 'before the layer with the specified ID.', - 'By default, scattermapbox layers are inserted', - 'above all the base layers.', - 'To place the scattermapbox layers above every other layer, set `below` to *\'\'*.' - ].join(' ') - }, - selected: { - marker: scatterAttrs.selected.marker - }, - unselected: { - marker: scatterAttrs.unselected.marker + hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { + flags: ['lon', 'lat', 'text', 'name'] + }), + hovertemplate: hovertemplateAttrs() }, - - hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: ['lon', 'lat', 'text', 'name'] - }), - hovertemplate: hovertemplateAttrs(), -}, 'calc', 'nested'); + 'calc', + 'nested' +); diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index dcf174c314e..b935bf5256e 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -20,13 +20,13 @@ var BR_TAG_ALL = require('../../lib/svg_text_utils').BR_TAG_ALL; module.exports = function convert(gd, calcTrace) { var trace = calcTrace[0].trace; - var isVisible = (trace.visible === true && trace._length !== 0); - var hasFill = (trace.fill !== 'none'); + var isVisible = trace.visible === true && trace._length !== 0; + var hasFill = trace.fill !== 'none'; var hasLines = subTypes.hasLines(trace); var hasMarkers = subTypes.hasMarkers(trace); var hasText = subTypes.hasText(trace); - var hasCircles = (hasMarkers && trace.marker.symbol === 'circle'); - var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle'); + var hasCircles = hasMarkers && trace.marker.symbol === 'circle'; + var hasSymbols = hasMarkers && trace.marker.symbol !== 'circle'; var hasCluster = trace.cluster && trace.cluster.enabled; var fill = initContainer('fill'); @@ -42,15 +42,15 @@ module.exports = function convert(gd, calcTrace) { }; // early return if not visible or placeholder - if(!isVisible) return opts; + if (!isVisible) return opts; // fill layer and line layer use the same coords var lineCoords; - if(hasFill || hasLines) { + if (hasFill || hasLines) { lineCoords = geoJsonUtils.calcTraceToLineCoords(calcTrace); } - if(hasFill) { + if (hasFill) { fill.geojson = geoJsonUtils.makePolygon(lineCoords); fill.layout.visibility = 'visible'; @@ -59,7 +59,7 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasLines) { + if (hasLines) { line.geojson = geoJsonUtils.makeLine(lineCoords); line.layout.visibility = 'visible'; @@ -72,21 +72,21 @@ module.exports = function convert(gd, calcTrace) { // TODO convert line.dash into line-dasharray } - if(hasCircles) { + if (hasCircles) { var circleOpts = makeCircleOpts(calcTrace); circle.geojson = circleOpts.geojson; circle.layout.visibility = 'visible'; - if(hasCluster) { + if (hasCluster) { circle.filter = ['!', ['has', 'point_count']]; opts.cluster = { type: 'circle', filter: ['has', 'point_count'], - layout: {visibility: 'visible'}, + layout: { visibility: 'visible' }, paint: { 'circle-color': arrayifyAttribute(trace.cluster.color, trace.cluster.step), 'circle-radius': arrayifyAttribute(trace.cluster.size, trace.cluster.step), - 'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step), - }, + 'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step) + } }; opts.clusterCount = { type: 'symbol', @@ -107,11 +107,11 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasCircles && hasCluster) { + if (hasCircles && hasCluster) { circle.filter = ['!', ['has', 'point_count']]; } - if(hasSymbols || hasText) { + if (hasSymbols || hasText) { symbol.geojson = makeSymbolGeoJSON(calcTrace, gd); Lib.extendFlat(symbol.layout, { @@ -120,17 +120,18 @@ module.exports = function convert(gd, calcTrace) { 'text-field': '{text}' }); - if(hasSymbols) { + if (hasSymbols) { Lib.extendFlat(symbol.layout, { 'icon-size': trace.marker.size / 10 }); - if('angle' in trace.marker && trace.marker.angle !== 'auto') { + if ('angle' in trace.marker && trace.marker.angle !== 'auto') { Lib.extendFlat(symbol.layout, { - // unfortunately cant use {angle} do to this issue: - // https://github.com/mapbox/mapbox-gl-js/issues/873 + // unfortunately cant use {angle} do to this issue: + // https://github.com/mapbox/mapbox-gl-js/issues/873 'icon-rotate': { - type: 'identity', property: 'angle' + type: 'identity', + property: 'angle' }, 'icon-rotation-alignment': 'map' }); @@ -146,7 +147,7 @@ module.exports = function convert(gd, calcTrace) { }); } - if(hasText) { + if (hasText) { var iconSize = (trace.marker || {}).size; var textOpts = convertTextOpts(trace.textposition, iconSize); @@ -188,13 +189,17 @@ function makeCircleOpts(calcTrace) { var arrayOpacity = Lib.isArrayOrTypedArray(marker.opacity); var i; - function addTraceOpacity(o) { return trace.opacity * o; } + function addTraceOpacity(o) { + return trace.opacity * o; + } - function size2radius(s) { return s / 2; } + function size2radius(s) { + return s / 2; + } var colorFn; - if(arrayColor) { - if(Colorscale.hasColorscale(trace, 'marker')) { + if (arrayColor) { + if (Colorscale.hasColorscale(trace, 'marker')) { colorFn = Colorscale.makeColorScaleFuncFromTrace(marker); } else { colorFn = Lib.identity; @@ -202,30 +207,30 @@ function makeCircleOpts(calcTrace) { } var sizeFn; - if(arraySize) { + if (arraySize) { sizeFn = makeBubbleSizeFn(trace); } var opacityFn; - if(arrayOpacity) { - opacityFn = function(mo) { + if (arrayOpacity) { + opacityFn = function (mo) { var mo2 = isNumeric(mo) ? +Lib.constrain(mo, 0, 1) : 0; return addTraceOpacity(mo2); }; } var features = []; - for(i = 0; i < calcTrace.length; i++) { + for (i = 0; i < calcTrace.length; i++) { var calcPt = calcTrace[i]; var lonlat = calcPt.lonlat; - if(isBADNUM(lonlat)) continue; + if (isBADNUM(lonlat)) continue; var props = {}; - if(colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc); - if(sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms); - if(opacityFn) props.mo = opacityFn(calcPt.mo); - if(selectedpoints) props.selected = calcPt.selected || 0; + if (colorFn) props.mcc = calcPt.mcc = colorFn(calcPt.mc); + if (sizeFn) props.mrc = calcPt.mrc = sizeFn(calcPt.ms); + if (opacityFn) props.mo = opacityFn(calcPt.mo); + if (selectedpoints) props.selected = calcPt.selected || 0; features.push({ type: 'Feature', @@ -236,35 +241,33 @@ function makeCircleOpts(calcTrace) { } var fns; - if(selectedpoints) { + if (selectedpoints) { fns = Drawing.makeSelectedPointStyleFns(trace); - for(i = 0; i < features.length; i++) { + for (i = 0; i < features.length; i++) { var d = features[i].properties; - if(fns.selectedOpacityFn) { + if (fns.selectedOpacityFn) { d.mo = addTraceOpacity(fns.selectedOpacityFn(d)); } - if(fns.selectedColorFn) { + if (fns.selectedColorFn) { d.mcc = fns.selectedColorFn(d); } - if(fns.selectedSizeFn) { + if (fns.selectedSizeFn) { d.mrc = fns.selectedSizeFn(d); } } } return { - geojson: {type: 'FeatureCollection', features: features}, - mcc: arrayColor || (fns && fns.selectedColorFn) ? - {type: 'identity', property: 'mcc'} : - marker.color, - mrc: arraySize || (fns && fns.selectedSizeFn) ? - {type: 'identity', property: 'mrc'} : - size2radius(marker.size), - mo: arrayOpacity || (fns && fns.selectedOpacityFn) ? - {type: 'identity', property: 'mo'} : - addTraceOpacity(marker.opacity) + geojson: { type: 'FeatureCollection', features: features }, + mcc: arrayColor || (fns && fns.selectedColorFn) ? { type: 'identity', property: 'mcc' } : marker.color, + mrc: + arraySize || (fns && fns.selectedSizeFn) ? { type: 'identity', property: 'mrc' } : size2radius(marker.size), + mo: + arrayOpacity || (fns && fns.selectedOpacityFn) + ? { type: 'identity', property: 'mo' } + : addTraceOpacity(marker.opacity) }; } @@ -276,31 +279,24 @@ function makeSymbolGeoJSON(calcTrace, gd) { var symbol = marker.symbol; var angle = marker.angle; - var fillSymbol = (symbol !== 'circle') ? - getFillFunc(symbol) : - blankFillFunc; - - var fillAngle = (angle !== 'auto') ? - getFillFunc(angle, true) : - blankFillFunc; + var fillSymbol = symbol !== 'circle' ? getFillFunc(symbol) : blankFillFunc; - var fillText = subTypes.hasText(trace) ? - getFillFunc(trace.text) : - blankFillFunc; + var fillAngle = angle !== 'auto' ? getFillFunc(angle, true) : blankFillFunc; + var fillText = subTypes.hasText(trace) ? getFillFunc(trace.text) : blankFillFunc; var features = []; - for(var i = 0; i < calcTrace.length; i++) { + for (var i = 0; i < calcTrace.length; i++) { var calcPt = calcTrace[i]; - if(isBADNUM(calcPt.lonlat)) continue; + if (isBADNUM(calcPt.lonlat)) continue; var texttemplate = trace.texttemplate; var text; - if(texttemplate) { - var tt = Array.isArray(texttemplate) ? (texttemplate[i] || '') : texttemplate; + if (texttemplate) { + var tt = Array.isArray(texttemplate) ? texttemplate[i] || '' : texttemplate; var labels = trace._module.formatLabels(calcPt, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); @@ -310,7 +306,7 @@ function makeSymbolGeoJSON(calcTrace, gd) { text = fillText(i); } - if(text) { + if (text) { text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); } @@ -335,19 +331,27 @@ function makeSymbolGeoJSON(calcTrace, gd) { } function getFillFunc(attr, numeric) { - if(Lib.isArrayOrTypedArray(attr)) { - if(numeric) { - return function(i) { return isNumeric(attr[i]) ? +attr[i] : 0; }; + if (Lib.isArrayOrTypedArray(attr)) { + if (numeric) { + return function (i) { + return isNumeric(attr[i]) ? +attr[i] : 0; + }; } - return function(i) { return attr[i]; }; - } else if(attr) { - return function() { return attr; }; + return function (i) { + return attr[i]; + }; + } else if (attr) { + return function () { + return attr; + }; } else { return blankFillFunc; } } -function blankFillFunc() { return ''; } +function blankFillFunc() { + return ''; +} // only need to check lon (OR lat) function isBADNUM(lonlat) { @@ -356,10 +360,10 @@ function isBADNUM(lonlat) { function arrayifyAttribute(values, step) { var newAttribute; - if(Lib.isArrayOrTypedArray(values) && Lib.isArrayOrTypedArray(step)) { + if (Lib.isArrayOrTypedArray(values) && Lib.isArrayOrTypedArray(step)) { newAttribute = ['step', ['get', 'point_count'], values[0]]; - for(var idx = 1; idx < values.length; idx++) { + for (var idx = 1; idx < values.length; idx++) { newAttribute.push(step[idx - 1], values[idx]); } } else { @@ -376,49 +380,50 @@ function getTextFont(trace) { var parts = family.split(' '); var isItalic = parts[parts.length - 1] === 'Italic'; - if(isItalic) parts.pop(); + if (isItalic) parts.pop(); isItalic = isItalic || style === 'italic'; var str = parts.join(' '); - if(weight === 'bold' && parts.indexOf('Bold') === -1) { + if (weight === 'bold' && parts.indexOf('Bold') === -1) { str += ' Bold'; - } else if(weight <= 1000) { // numeric font-weight + } else if (weight <= 1000) { + // numeric font-weight // See supportedFonts - if(parts[0] === 'Metropolis') { + if (parts[0] === 'Metropolis') { str = 'Metropolis'; - if(weight > 850) str += ' Black'; - else if(weight > 750) str += ' Extra Bold'; - else if(weight > 650) str += ' Bold'; - else if(weight > 550) str += ' Semi Bold'; - else if(weight > 450) str += ' Medium'; - else if(weight > 350) str += ' Regular'; - else if(weight > 250) str += ' Light'; - else if(weight > 150) str += ' Extra Light'; + if (weight > 850) str += ' Black'; + else if (weight > 750) str += ' Extra Bold'; + else if (weight > 650) str += ' Bold'; + else if (weight > 550) str += ' Semi Bold'; + else if (weight > 450) str += ' Medium'; + else if (weight > 350) str += ' Regular'; + else if (weight > 250) str += ' Light'; + else if (weight > 150) str += ' Extra Light'; else str += ' Thin'; - } else if(parts.slice(0, 2).join(' ') === 'Open Sans') { + } else if (parts.slice(0, 2).join(' ') === 'Open Sans') { str = 'Open Sans'; - if(weight > 750) str += ' Extrabold'; - else if(weight > 650) str += ' Bold'; - else if(weight > 550) str += ' Semibold'; - else if(weight > 350) str += ' Regular'; + if (weight > 750) str += ' Extrabold'; + else if (weight > 650) str += ' Bold'; + else if (weight > 550) str += ' Semibold'; + else if (weight > 350) str += ' Regular'; else str += ' Light'; - } else if(parts.slice(0, 3).join(' ') === 'Klokantech Noto Sans') { + } else if (parts.slice(0, 3).join(' ') === 'Klokantech Noto Sans') { str = 'Klokantech Noto Sans'; - if(parts[3] === 'CJK') str += ' CJK'; - str += (weight > 500) ? ' Bold' : ' Regular'; + if (parts[3] === 'CJK') str += ' CJK'; + str += weight > 500 ? ' Bold' : ' Regular'; } } - if(isItalic) str += ' Italic'; + if (isItalic) str += ' Italic'; - if(str === 'Open Sans Regular Italic') str = 'Open Sans Italic'; - else if(str === 'Open Sans Regular Bold') str = 'Open Sans Bold'; - else if(str === 'Open Sans Regular Bold Italic') str = 'Open Sans Bold Italic'; - else if(str === 'Klokantech Noto Sans Regular Italic') str = 'Klokantech Noto Sans Italic'; + if (str === 'Open Sans Regular Italic') str = 'Open Sans Italic'; + else if (str === 'Open Sans Regular Bold') str = 'Open Sans Bold'; + else if (str === 'Open Sans Regular Bold Italic') str = 'Open Sans Bold Italic'; + else if (str === 'Klokantech Noto Sans Regular Italic') str = 'Klokantech Noto Sans Italic'; // Ensure the result is a supported font - if(!isSupportedFont(str)) { + if (!isSupportedFont(str)) { str = family; } diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index 2147f8ef0b6..0fe5eb99aea 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -20,7 +20,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleLonLatDefaults(traceIn, traceOut, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -32,22 +32,22 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode'); coerce('below'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noLine: true, noAngle: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noLine: true, noAngle: true }); coerce('marker.allowoverlap'); coerce('marker.angle'); // array marker.size and marker.color are only supported with circles var marker = traceOut.marker; - if(marker.symbol !== 'circle') { - if(Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0]; - if(Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0]; + if (marker.symbol !== 'circle') { + if (Lib.isArrayOrTypedArray(marker.size)) marker.size = marker.size[0]; + if (Lib.isArrayOrTypedArray(marker.color)) marker.color = marker.color[0]; } } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noDash: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noDash: true }); coerce('connectgaps'); } @@ -66,28 +66,27 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var clusterEnabled = coerce('cluster.enabled', clusterEnabledDflt); - if(clusterEnabled || subTypes.hasText(traceOut)) { + if (clusterEnabled || subTypes.hasText(traceOut)) { var layoutFontFamily = layout.font.family; - handleTextDefaults(traceIn, traceOut, layout, coerce, - { - noSelect: true, - noFontVariant: true, - noFontShadow: true, - noFontLineposition: true, - noFontTextcase: true, - font: { - family: isSupportedFont(layoutFontFamily) ? layoutFontFamily : 'Open Sans Regular', - weight: layout.font.weight, - style: layout.font.style, - size: layout.font.size, - color: layout.font.color - } - }); + handleTextDefaults(traceIn, traceOut, layout, coerce, { + noSelect: true, + noFontVariant: true, + noFontShadow: true, + noFontLineposition: true, + noFontTextcase: true, + font: { + family: isSupportedFont(layoutFontFamily) ? layoutFontFamily : 'Open Sans Regular', + weight: layout.font.weight, + style: layout.font.style, + size: layout.font.size, + color: layout.font.color + } + }); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } diff --git a/src/traces/scatterpolar/attributes.js b/src/traces/scatterpolar/attributes.js index 34a859df181..283f43c27bc 100644 --- a/src/traces/scatterpolar/attributes.js +++ b/src/traces/scatterpolar/attributes.js @@ -57,7 +57,7 @@ module.exports = { editType: 'calc', description: [ 'Sets the theta coordinate step.', - 'By default, the `dtheta` step equals the subplot\'s period divided', + "By default, the `dtheta` step equals the subplot's period divided", 'by the length of the `r` coordinates.' ].join(' ') }, @@ -74,9 +74,12 @@ module.exports = { }, text: scatterAttrs.text, - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['r', 'theta', 'text'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['r', 'theta', 'text'] + } + ), hovertext: scatterAttrs.hovertext, line: { @@ -93,7 +96,7 @@ module.exports = { connectgaps: scatterAttrs.connectgaps, marker: scatterAttrs.marker, - cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {dflt: false}), + cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, { dflt: false }), textposition: scatterAttrs.textposition, textfont: scatterAttrs.textfont, diff --git a/src/traces/scatterpolar/defaults.js b/src/traces/scatterpolar/defaults.js index 15b529f3c0e..0458cd3d942 100644 --- a/src/traces/scatterpolar/defaults.js +++ b/src/traces/scatterpolar/defaults.js @@ -18,7 +18,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { } var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -27,26 +27,26 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {backoff: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { backoff: true }); handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce); } var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('cliponaxis'); coerce('marker.maxdisplayed'); dfltHoverOn.push('points'); @@ -54,12 +54,12 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); @@ -72,17 +72,17 @@ function handleRThetaDefaults(traceIn, traceOut, layout, coerce) { var theta = coerce('theta'); // TODO: handle this case outside supply defaults step - if(Lib.isTypedArray(r)) { + if (Lib.isTypedArray(r)) { traceOut.r = r = Array.from(r); } - if(Lib.isTypedArray(theta)) { + if (Lib.isTypedArray(theta)) { traceOut.theta = theta = Array.from(theta); } var len; - if(r) { - if(theta) { + if (r) { + if (theta) { len = Math.min(r.length, theta.length); } else { len = r.length; @@ -90,7 +90,7 @@ function handleRThetaDefaults(traceIn, traceOut, layout, coerce) { coerce('dtheta'); } } else { - if(!theta) return 0; + if (!theta) return 0; len = traceOut.theta.length; coerce('r0'); coerce('dr'); diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js index a7322d3fe22..cc2b511a94c 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -15,9 +15,12 @@ module.exports = { thetaunit: scatterPolarAttrs.thetaunit, text: scatterPolarAttrs.text, - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['r', 'theta', 'text'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['r', 'theta', 'text'] + } + ), hovertext: scatterPolarAttrs.hovertext, hovertemplate: scatterPolarAttrs.hovertemplate, diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index b82262ebf40..e4a0a46255b 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -18,7 +18,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleRThetaDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -27,28 +27,28 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noAngleRef: true, noStandOff: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); } - if(subTypes.hasLines(traceOut)) { + if (subTypes.hasLines(traceOut)) { handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noFontShadow: true, noFontLineposition: true, - noFontTextcase: true, + noFontTextcase: true }); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } diff --git a/src/traces/scattersmith/attributes.js b/src/traces/scattersmith/attributes.js index 5a8e931013d..72b99d44d40 100644 --- a/src/traces/scattersmith/attributes.js +++ b/src/traces/scattersmith/attributes.js @@ -30,9 +30,12 @@ module.exports = { }, text: scatterAttrs.text, - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['real', 'imag', 'text'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['real', 'imag', 'text'] + } + ), hovertext: scatterAttrs.hovertext, line: { @@ -49,7 +52,7 @@ module.exports = { connectgaps: scatterAttrs.connectgaps, marker: scatterAttrs.marker, - cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, {dflt: false}), + cliponaxis: extendFlat({}, scatterAttrs.cliponaxis, { dflt: false }), textposition: scatterAttrs.textposition, textfont: scatterAttrs.textfont, diff --git a/src/traces/scattersmith/defaults.js b/src/traces/scattersmith/defaults.js index 622901b83c5..05792c3a759 100644 --- a/src/traces/scattersmith/defaults.js +++ b/src/traces/scattersmith/defaults.js @@ -18,7 +18,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleRealImagDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -26,26 +26,26 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {backoff: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { backoff: true }); handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce); } var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('cliponaxis'); coerce('marker.maxdisplayed'); dfltHoverOn.push('points'); @@ -53,12 +53,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); @@ -71,15 +71,15 @@ function handleRealImagDefaults(traceIn, traceOut, layout, coerce) { var imag = coerce('imag'); var len; - if(real && imag) { + if (real && imag) { len = Math.min(real.length, imag.length); } // TODO: handle this case outside supply defaults step - if(Lib.isTypedArray(real)) { + if (Lib.isTypedArray(real)) { traceOut.real = real = Array.from(real); } - if(Lib.isTypedArray(imag)) { + if (Lib.isTypedArray(imag)) { traceOut.imag = imag = Array.from(imag); } diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 67d5baf5feb..58518b17404 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -61,7 +61,7 @@ module.exports = { '0 (or missing) means to use ternary.sum' ].join(' ') }, - mode: extendFlat({}, scatterAttrs.mode, {dflt: 'markers'}), + mode: extendFlat({}, scatterAttrs.mode, { dflt: 'markers' }), text: extendFlat({}, scatterAttrs.text, { description: [ 'Sets text elements associated with each (a,b,c) point.', @@ -73,9 +73,12 @@ module.exports = { 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: ['a', 'b', 'c', 'text'] - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: ['a', 'b', 'c', 'text'] + } + ), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (a,b,c) point.', @@ -91,8 +94,7 @@ module.exports = { width: scatterLineAttrs.width, dash: dash, backoff: scatterLineAttrs.backoff, - shape: extendFlat({}, scatterLineAttrs.shape, - {values: ['linear', 'spline']}), + shape: extendFlat({}, scatterLineAttrs.shape, { values: ['linear', 'spline'] }), smoothing: scatterLineAttrs.smoothing, editType: 'calc' }, @@ -114,26 +116,28 @@ module.exports = { ].join(' ') }), fillcolor: makeFillcolorAttr(), - marker: extendFlat({ - symbol: scatterMarkerAttrs.symbol, - opacity: scatterMarkerAttrs.opacity, - angle: scatterMarkerAttrs.angle, - angleref: scatterMarkerAttrs.angleref, - standoff: scatterMarkerAttrs.standoff, - maxdisplayed: scatterMarkerAttrs.maxdisplayed, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - line: extendFlat({ - width: scatterMarkerLineAttrs.width, + marker: extendFlat( + { + symbol: scatterMarkerAttrs.symbol, + opacity: scatterMarkerAttrs.opacity, + angle: scatterMarkerAttrs.angle, + angleref: scatterMarkerAttrs.angleref, + standoff: scatterMarkerAttrs.standoff, + maxdisplayed: scatterMarkerAttrs.maxdisplayed, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + line: extendFlat( + { + width: scatterMarkerLineAttrs.width, + editType: 'calc' + }, + colorScaleAttrs('marker.line') + ), + gradient: scatterMarkerAttrs.gradient, editType: 'calc' }, - colorScaleAttrs('marker.line') - ), - gradient: scatterMarkerAttrs.gradient, - editType: 'calc' - }, colorScaleAttrs('marker') ), @@ -147,5 +151,5 @@ module.exports = { flags: ['a', 'b', 'c', 'text', 'name'] }), hoveron: scatterAttrs.hoveron, - hovertemplate: hovertemplateAttrs(), + hovertemplate: hovertemplateAttrs() }; diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 0b7e3a0e630..895d060b01d 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -12,7 +12,6 @@ var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); var attributes = require('./attributes'); - module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); @@ -28,18 +27,18 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // are truthy even if empty) or undefined. As in scatter, an empty array // is different from undefined, because it can signify that this data is // not known yet but expected in the future - if(a) { + if (a) { len = a.length; - if(b) { + if (b) { len = Math.min(len, b.length); - if(c) len = Math.min(len, c.length); - } else if(c) len = Math.min(len, c.length); + if (c) len = Math.min(len, c.length); + } else if (c) len = Math.min(len, c.length); else len = 0; - } else if(b && c) { + } else if (b && c) { len = Math.min(b.length, c.length); } - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -50,41 +49,41 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); - if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('mode', defaultMode); - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {gradient: true}); + if (subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); } - if(subTypes.hasLines(traceOut)) { - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, {backoff: true}); + if (subTypes.hasLines(traceOut)) { + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce, { backoff: true }); handleLineShapeDefaults(traceIn, traceOut, coerce); coerce('connectgaps'); } - if(subTypes.hasText(traceOut)) { + if (subTypes.hasText(traceOut)) { coerce('texttemplate'); handleTextDefaults(traceIn, traceOut, layout, coerce); } var dfltHoverOn = []; - if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { + if (subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) { coerce('cliponaxis'); coerce('marker.maxdisplayed'); dfltHoverOn.push('points'); } coerce('fill'); - if(traceOut.fill !== 'none') { + if (traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); + if (!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { + if (traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 0c1c09b8457..e7be4a0971e 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -12,15 +12,15 @@ var extendFlat = require('../../lib/extend').extendFlat; var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var markerLineAttrs = extendFlat(colorScaleAttrs('marker.line', {editTypeOverride: 'calc'}), { - width: extendFlat({}, scatterMarkerLineAttrs.width, {editType: 'calc'}), +var markerLineAttrs = extendFlat(colorScaleAttrs('marker.line', { editTypeOverride: 'calc' }), { + width: extendFlat({}, scatterMarkerLineAttrs.width, { editType: 'calc' }), editType: 'calc' }); var markerAttrs = extendFlat(colorScaleAttrs('marker'), { symbol: scatterMarkerAttrs.symbol, angle: scatterMarkerAttrs.angle, - size: extendFlat({}, scatterMarkerAttrs.size, {editType: 'markerSize'}), + size: extendFlat({}, scatterMarkerAttrs.size, { editType: 'markerSize' }), sizeref: scatterMarkerAttrs.sizeref, sizemin: scatterMarkerAttrs.sizemin, sizemode: scatterMarkerAttrs.sizemode, @@ -49,7 +49,7 @@ function makeAxesValObject(axLetter) { 'where N is the number of input dimensions.', 'Note that, in case where `diagonal.visible` is false and `showupperhalf`', 'or `showlowerhalf` is false, this splom trace will generate', - 'one less x-axis and one less y-axis.', + 'one less x-axis and one less y-axis.' ].join(' ') }; } @@ -83,7 +83,7 @@ module.exports = { values: ['linear', 'log', 'date', 'category'], editType: 'calc+clearAxisTypes', description: [ - 'Sets the axis type for this dimension\'s generated', + "Sets the axis type for this dimension's generated", 'x and y axes.', 'Note that the axis `type` values set in layout take', 'precedence over this attribute.' @@ -123,7 +123,7 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' + "this trace's (x,y) coordinates." ].join(' ') }), hovertext: extendFlat({}, scatterGlAttrs.hovertext, { @@ -145,9 +145,7 @@ module.exports = { valType: 'boolean', dflt: true, editType: 'calc', - description: [ - 'Determines whether or not subplots on the diagonal are displayed.' - ].join(' ') + description: ['Determines whether or not subplots on the diagonal are displayed.'].join(' ') }, // type: 'scattergl' | 'histogram' | 'box' | 'violin' @@ -161,19 +159,17 @@ module.exports = { valType: 'boolean', dflt: true, editType: 'calc', - description: [ - 'Determines whether or not subplots on the upper half', - 'from the diagonal are displayed.' - ].join(' ') + description: ['Determines whether or not subplots on the upper half', 'from the diagonal are displayed.'].join( + ' ' + ) }, showlowerhalf: { valType: 'boolean', dflt: true, editType: 'calc', - description: [ - 'Determines whether or not subplots on the lower half', - 'from the diagonal are displayed.' - ].join(' ') + description: ['Determines whether or not subplots on the lower half', 'from the diagonal are displayed.'].join( + ' ' + ) }, selected: { diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index 72cb247733a..34b2737140f 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -25,7 +25,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var dimLength = mergeLength(traceOut, dimensions, 'values'); - if(!dimLength || (!showDiag && !showUpper && !showLower)) { + if (!dimLength || (!showDiag && !showUpper && !showLower)) { traceOut.visible = false; return; } @@ -36,7 +36,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('xhoverformat'); coerce('yhoverformat'); - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noAngleRef: true, noStandOff: true}); + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); var isOpen = isOpenSymbol(traceOut.marker.symbol); var isBubble = subTypes.isBubble(traceOut); @@ -55,7 +55,7 @@ function dimensionDefaults(dimIn, dimOut) { coerce('label'); var values = coerce('values'); - if(!(values && values.length)) dimOut.visible = false; + if (!(values && values.length)) dimOut.visible = false; else coerce('visible'); coerce('axis.type'); @@ -73,7 +73,7 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { var xAxesDflt = new Array(dimLength); var yAxesDflt = new Array(dimLength); - for(i = 0; i < dimLength; i++) { + for (i = 0; i < dimLength; i++) { var suffix = i ? i + 1 : ''; xAxesDflt[i] = 'x' + suffix; yAxesDflt[i] = 'y' + suffix; @@ -84,7 +84,7 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { // build list of [x,y] axis corresponding to each dimensions[i], // very useful for passing options to regl-splom - var diag = traceOut._diag = new Array(dimLength); + var diag = (traceOut._diag = new Array(dimLength)); // lookup for 'drawn' x|y axes, to avoid costly indexOf downstream traceOut._xaxes = {}; @@ -95,7 +95,7 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { var yList = []; function fillAxisStashes(axId, counterAxId, dim, list) { - if(!axId) return; + if (!axId) return; var axLetter = axId.charAt(0); var stash = layout._splomAxes[axLetter]; @@ -103,13 +103,13 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { traceOut['_' + axLetter + 'axes'][axId] = 1; list.push(axId); - if(!(axId in stash)) { - var s = stash[axId] = {}; - if(dim) { + if (!(axId in stash)) { + var s = (stash[axId] = {}); + if (dim) { s.label = dim.label || ''; - if(dim.visible && dim.axis) { - if(dim.axis.type) s.type = dim.axis.type; - if(dim.axis.matches) s.matches = counterAxId; + if (dim.visible && dim.axis) { + if (dim.axis.type) s.type = dim.axis.type; + if (dim.axis.matches) s.matches = counterAxId; } } } @@ -122,18 +122,14 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { var mustShiftY = !showDiag && !showUpper; traceOut._axesDim = {}; - for(i = 0; i < dimLength; i++) { + for (i = 0; i < dimLength; i++) { var dim = dimensions[i]; var i0 = i === 0; var iN = i === dimLength - 1; - var xaId = (i0 && mustShiftX) || (iN && mustShiftY) ? - undefined : - xaxes[i]; + var xaId = (i0 && mustShiftX) || (iN && mustShiftY) ? undefined : xaxes[i]; - var yaId = (i0 && mustShiftY) || (iN && mustShiftX) ? - undefined : - yaxes[i]; + var yaId = (i0 && mustShiftY) || (iN && mustShiftX) ? undefined : yaxes[i]; fillAxisStashes(xaId, yaId, dim, xList); fillAxisStashes(yaId, xaId, dim, yList); @@ -143,15 +139,15 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { } // fill in splom subplot keys - for(i = 0; i < xList.length; i++) { - for(j = 0; j < yList.length; j++) { + for (i = 0; i < xList.length; i++) { + for (j = 0; j < yList.length; j++) { var id = xList[i] + yList[j]; - if(i > j && showUpper) { + if (i > j && showUpper) { layout._splomSubplots[id] = 1; - } else if(i < j && showLower) { + } else if (i < j && showLower) { layout._splomSubplots[id] = 1; - } else if(i === j && (showDiag || !showLower || !showUpper)) { + } else if (i === j && (showDiag || !showLower || !showUpper)) { // need to include diagonal subplots when // hiding one half and the diagonal layout._splomSubplots[id] = 1; @@ -162,7 +158,7 @@ function handleAxisDefaults(traceIn, traceOut, layout, coerce) { // when lower half is omitted, or when just the diagonal is gone, // override grid default to make sure axes remain on // the left/bottom of the plot area - if(!showLower || (!showDiag && showUpper && showLower)) { + if (!showLower || (!showDiag && showUpper && showLower)) { layout._splomGridDflt.xside = 'bottom'; layout._splomGridDflt.yside = 'left'; } diff --git a/src/traces/streamtube/attributes.js b/src/traces/streamtube/attributes.js index b9c0a4663d5..c78ceaf6bee 100644 --- a/src/traces/streamtube/attributes.js +++ b/src/traces/streamtube/attributes.js @@ -45,23 +45,17 @@ var attrs = { x: { valType: 'data_array', editType: 'calc', - description: [ - 'Sets the x components of the starting position of the streamtubes', - ].join(' ') + description: ['Sets the x components of the starting position of the streamtubes'].join(' ') }, y: { valType: 'data_array', editType: 'calc', - description: [ - 'Sets the y components of the starting position of the streamtubes', - ].join(' ') + description: ['Sets the y components of the starting position of the streamtubes'].join(' ') }, z: { valType: 'data_array', editType: 'calc', - description: [ - 'Sets the z components of the starting position of the streamtubes', - ].join(' ') + description: ['Sets the z components of the starting position of the streamtubes'].join(' ') }, editType: 'calc' }, @@ -71,9 +65,7 @@ var attrs = { min: 0, dflt: 1000, editType: 'calc', - description: [ - 'The maximum number of displayed segments in a streamtube.' - ].join(' ') + description: ['The maximum number of displayed segments in a streamtube.'].join(' ') }, // TODO @@ -123,13 +115,12 @@ var attrs = { editType: 'calc', description: 'Same as `text`.' }, - hovertemplate: hovertemplateAttrs({editType: 'calc'}, { - keys: [ - 'tubex', 'tubey', 'tubez', - 'tubeu', 'tubev', 'tubew', - 'norm', 'divergence' - ] - }), + hovertemplate: hovertemplateAttrs( + { editType: 'calc' }, + { + keys: ['tubex', 'tubey', 'tubez', 'tubeu', 'tubev', 'tubew', 'norm', 'divergence'] + } + ), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), @@ -137,17 +128,20 @@ var attrs = { yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z'), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }; -extendFlat(attrs, colorScaleAttrs('', { - colorAttr: 'u/v/w norm', - showScaleDflt: true, - editTypeOverride: 'calc' -})); +extendFlat( + attrs, + colorScaleAttrs('', { + colorAttr: 'u/v/w norm', + showScaleDflt: true, + editTypeOverride: 'calc' + }) +); var fromMesh3d = ['opacity', 'lightposition', 'lighting']; -fromMesh3d.forEach(function(k) { +fromMesh3d.forEach(function (k) { attrs[k] = mesh3dAttrs[k]; }); diff --git a/src/traces/streamtube/defaults.js b/src/traces/streamtube/defaults.js index 9b5135890f9..34f5e8266bb 100644 --- a/src/traces/streamtube/defaults.js +++ b/src/traces/streamtube/defaults.js @@ -18,9 +18,19 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var y = coerce('y'); var z = coerce('z'); - if( - !u || !u.length || !v || !v.length || !w || !w.length || - !x || !x.length || !y || !y.length || !z || !z.length + if ( + !u || + !u.length || + !v || + !v.length || + !w || + !w.length || + !x || + !x.length || + !y || + !y.length || + !z || + !z.length ) { traceOut.visible = false; return; @@ -42,7 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('lightposition.y'); coerce('lightposition.z'); - colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'c' }); coerce('text'); coerce('hovertext'); diff --git a/src/traces/sunburst/attributes.js b/src/traces/sunburst/attributes.js index 73a769b8009..10ccc3b507f 100644 --- a/src/traces/sunburst/attributes.js +++ b/src/traces/sunburst/attributes.js @@ -15,16 +15,14 @@ module.exports = { labels: { valType: 'data_array', editType: 'calc', - description: [ - 'Sets the labels of each of the sectors.' - ].join(' ') + description: ['Sets the labels of each of the sectors.'].join(' ') }, parents: { valType: 'data_array', editType: 'calc', description: [ 'Sets the parent sectors for each of the sectors.', - 'Empty string items \'\' are understood to reference', + "Empty string items '' are understood to reference", 'the root node in the hierarchy.', 'If `ids` is filled, `parents` items are understood to be "ids" themselves.', 'When `ids` is not set, plotly attempts to find matching items in `labels`,', @@ -54,10 +52,7 @@ module.exports = { }, count: { valType: 'flaglist', - flags: [ - 'branches', - 'leaves' - ], + flags: ['branches', 'leaves'], dflt: 'leaves', editType: 'calc', description: [ @@ -72,7 +67,7 @@ module.exports = { anim: true, description: [ 'Sets the level from which this trace hierarchy is rendered.', - 'Set `level` to `\'\'` to start from the root node in the hierarchy.', + "Set `level` to `''` to start from the root node in the hierarchy.", 'Must be an "id" if `ids` is filled in, otherwise plotly attempts to find a matching', 'item in `labels`.' ].join(' ') @@ -87,36 +82,37 @@ module.exports = { ].join(' ') }, - marker: extendFlat({ - colors: { - valType: 'data_array', - editType: 'calc', - description: [ - 'Sets the color of each sector of this trace.', - 'If not specified, the default trace color set is used', - 'to pick the sector colors.' - ].join(' ') - }, - - // colorinheritance: { - // valType: 'enumerated', - // values: ['per-branch', 'per-label', false] - // }, - - line: { - color: extendFlat({}, pieAttrs.marker.line.color, { - dflt: null, + marker: extendFlat( + { + colors: { + valType: 'data_array', + editType: 'calc', description: [ - 'Sets the color of the line enclosing each sector.', - 'Defaults to the `paper_bgcolor` value.' + 'Sets the color of each sector of this trace.', + 'If not specified, the default trace color set is used', + 'to pick the sector colors.' ].join(' ') - }), - width: extendFlat({}, pieAttrs.marker.line.width, {dflt: 1}), + }, + + // colorinheritance: { + // valType: 'enumerated', + // values: ['per-branch', 'per-label', false] + // }, + + line: { + color: extendFlat({}, pieAttrs.marker.line.color, { + dflt: null, + description: [ + 'Sets the color of the line enclosing each sector.', + 'Defaults to the `paper_bgcolor` value.' + ].join(' ') + }), + width: extendFlat({}, pieAttrs.marker.line.width, { dflt: 1 }), + editType: 'calc' + }, + pattern: pattern, editType: 'calc' }, - pattern: pattern, - editType: 'calc' - }, colorScaleAttrs('marker', { colorAttr: 'colors', anim: false // TODO: set to anim: true? @@ -140,44 +136,31 @@ module.exports = { text: pieAttrs.text, textinfo: { valType: 'flaglist', - flags: [ - 'label', - 'text', - 'value', - 'current path', - 'percent root', - 'percent entry', - 'percent parent' - ], + flags: ['label', 'text', 'value', 'current path', 'percent root', 'percent entry', 'percent parent'], extras: ['none'], editType: 'plot', - description: [ - 'Determines which trace information appear on the graph.' - ].join(' ') + description: ['Determines which trace information appear on the graph.'].join(' ') }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys.concat(['label', 'value']) - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys.concat(['label', 'value']) + } + ), hovertext: pieAttrs.hovertext, hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { - flags: [ - 'label', - 'text', - 'value', - 'name', - 'current path', - 'percent root', - 'percent entry', - 'percent parent' - ], + flags: ['label', 'text', 'value', 'name', 'current path', 'percent root', 'percent entry', 'percent parent'], dflt: 'label+text+value+name' }), - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), textfont: pieAttrs.textfont, insidetextorientation: pieAttrs.insidetextorientation, @@ -188,7 +171,7 @@ module.exports = { 'This option refers to the root of the hierarchy', 'presented at the center of a sunburst graph.', 'Please note that if a hierarchy has multiple root nodes,', - 'this option won\'t have any effect and `insidetextfont` would be used.' + "this option won't have any effect and `insidetextfont` would be used." ].join(' ') }), rotation: { @@ -197,7 +180,7 @@ module.exports = { editType: 'plot', description: [ 'Rotates the whole diagram counterclockwise by some angle.', - 'By default the first slice starts at 3 o\'clock.' + "By default the first slice starts at 3 o'clock." ].join(' ') }, sort: pieAttrs.sort, @@ -215,5 +198,5 @@ module.exports = { editType: 'calc' }, - domain: domainAttrs({name: 'sunburst', trace: true, editType: 'calc'}) + domain: domainAttrs({ name: 'sunburst', trace: true, editType: 'calc' }) }; diff --git a/src/traces/sunburst/defaults.js b/src/traces/sunburst/defaults.js index 9732f3f5cbd..c62da03ccbf 100644 --- a/src/traces/sunburst/defaults.js +++ b/src/traces/sunburst/defaults.js @@ -18,13 +18,13 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var labels = coerce('labels'); var parents = coerce('parents'); - if(!labels || !labels.length || !parents || !parents.length) { + if (!labels || !labels.length || !parents || !parents.length) { traceOut.visible = false; return; } var vals = coerce('values'); - if(vals && vals.length) { + if (vals && vals.length) { coerce('branchvalues'); } else { coerce('count'); @@ -35,19 +35,17 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleMarkerDefaults(traceIn, traceOut, layout, coerce); - 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'}); + 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); var text = coerce('text'); coerce('texttemplate'); - if(!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); + if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); diff --git a/src/traces/sunburst/plot.js b/src/traces/sunburst/plot.js index 386dc1bf4ce..11e07cb3049 100644 --- a/src/traces/sunburst/plot.js +++ b/src/traces/sunburst/plot.js @@ -20,7 +20,7 @@ var attachFxHandlers = require('./fx'); var constants = require('./constants'); var helpers = require('./helpers'); -exports.plot = function(gd, cdmodule, transitionOpts, makeOnCompleteCallback) { +exports.plot = function (gd, cdmodule, transitionOpts, makeOnCompleteCallback) { var fullLayout = gd._fullLayout; var layer = fullLayout._sunburstlayer; var join, onComplete; @@ -32,49 +32,52 @@ exports.plot = function(gd, cdmodule, transitionOpts, makeOnCompleteCallback) { clearMinTextSize('sunburst', fullLayout); - join = layer.selectAll('g.trace.sunburst') - .data(cdmodule, function(cd) { return cd[0].trace.uid; }); + join = layer.selectAll('g.trace.sunburst').data(cdmodule, function (cd) { + return cd[0].trace.uid; + }); // using same 'stroke-linejoin' as pie traces - join.enter().append('g') - .classed('trace', true) - .classed('sunburst', true) - .attr('stroke-linejoin', 'round'); + join.enter().append('g').classed('trace', true).classed('sunburst', true).attr('stroke-linejoin', 'round'); join.order(); - if(hasTransition) { - if(makeOnCompleteCallback) { + if (hasTransition) { + 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() + var transition = d3 + .transition() .duration(transitionOpts.duration) .ease(transitionOpts.easing) - .each('end', function() { onComplete && onComplete(); }) - .each('interrupt', function() { onComplete && onComplete(); }); + .each('end', function () { + onComplete && onComplete(); + }) + .each('interrupt', function () { + onComplete && onComplete(); + }); - transition.each(function() { + 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) { + layer.selectAll('g.trace').each(function (cd) { plotOne(gd, cd, this, transitionOpts); }); }); } else { - join.each(function(cd) { + join.each(function (cd) { plotOne(gd, cd, this, transitionOpts); }); - if(fullLayout.uniformtext.mode) { + if (fullLayout.uniformtext.mode) { resizeText(gd, fullLayout._sunburstlayer.selectAll('.trace'), 'sunburst'); } } - if(isFullReplot) { + if (isFullReplot) { join.exit().remove(); } }; @@ -99,10 +102,10 @@ function plotOne(gd, cd, element, transitionOpts) { var vpw = gs.w * (domain.x[1] - domain.x[0]); var vph = gs.h * (domain.y[1] - domain.y[0]); var rMax = 0.5 * Math.min(vpw, vph); - var cx = cd0.cx = gs.l + gs.w * (domain.x[1] + domain.x[0]) / 2; - var cy = cd0.cy = gs.t + gs.h * (1 - domain.y[0]) - vph / 2; + var cx = (cd0.cx = gs.l + (gs.w * (domain.x[1] + domain.x[0])) / 2); + var cy = (cd0.cy = gs.t + gs.h * (1 - domain.y[0]) - vph / 2); - if(!entry) { + if (!entry) { return slices.remove(); } @@ -111,9 +114,9 @@ function plotOne(gd, cd, element, transitionOpts) { // stash of 'previous' position data used by tweening functions var prevLookup = {}; - if(hasTransition) { + if (hasTransition) { // Important: do this before binding new sliceData! - slices.each(function(pt) { + slices.each(function (pt) { prevLookup[helpers.getPtId(pt)] = { rpx0: pt.rpx0, rpx1: pt.rpx1, @@ -122,7 +125,7 @@ function plotOne(gd, cd, element, transitionOpts) { transform: pt.transform }; - if(!prevEntry && helpers.isEntry(pt)) { + if (!prevEntry && helpers.isEntry(pt)) { prevEntry = pt; } }); @@ -136,7 +139,7 @@ function plotOne(gd, cd, element, transitionOpts) { var yOffset = 0; var cutoff = maxDepth; // N.B. handle multiple-root special case - if(cd0.hasMultipleRoots && helpers.isHierarchyRoot(entry)) { + if (cd0.hasMultipleRoots && helpers.isHierarchyRoot(entry)) { sliceData = sliceData.slice(1); maxHeight -= 1; yOffset = 1; @@ -144,11 +147,13 @@ function plotOne(gd, cd, element, transitionOpts) { } // filter out slices that won't show up on graph - sliceData = sliceData.filter(function(pt) { return pt.y1 <= cutoff; }); + sliceData = sliceData.filter(function (pt) { + return pt.y1 <= cutoff; + }); var baseX = getRotationAngle(trace.rotation); - if(baseX) { - sliceData.forEach(function(pt) { + if (baseX) { + sliceData.forEach(function (pt) { pt.x0 += baseX; pt.x1 += baseX; }); @@ -156,30 +161,43 @@ function plotOne(gd, cd, element, transitionOpts) { // partition span ('y') to sector radial px value var maxY = Math.min(maxHeight, maxDepth); - var y2rpx = function(y) { return (y - yOffset) / maxY * rMax; }; + var y2rpx = function (y) { + return ((y - yOffset) / maxY) * rMax; + }; // (radial px value, partition angle ('x')) to px [x,y] - var rx2px = function(r, x) { return [r * Math.cos(x), -r * Math.sin(x)]; }; + var rx2px = function (r, x) { + return [r * Math.cos(x), -r * Math.sin(x)]; + }; // slice path generation fn - var pathSlice = function(d) { return Lib.pathAnnulus(d.rpx0, d.rpx1, d.x0, d.x1, cx, cy); }; + var pathSlice = function (d) { + return Lib.pathAnnulus(d.rpx0, d.rpx1, d.x0, d.x1, cx, cy); + }; // slice text translate x/y - var getTargetX = function(d) { return cx + getTextXY(d)[0] * (d.transform.rCenter || 0) + (d.transform.x || 0); }; - var getTargetY = function(d) { return cy + getTextXY(d)[1] * (d.transform.rCenter || 0) + (d.transform.y || 0); }; + var getTargetX = function (d) { + return cx + getTextXY(d)[0] * (d.transform.rCenter || 0) + (d.transform.x || 0); + }; + var getTargetY = function (d) { + return cy + getTextXY(d)[1] * (d.transform.rCenter || 0) + (d.transform.y || 0); + }; slices = slices.data(sliceData, helpers.getPtId); - slices.enter().append('g') - .classed('slice', true); + slices.enter().append('g').classed('slice', true); - if(hasTransition) { - slices.exit().transition() - .each(function() { + if (hasTransition) { + slices + .exit() + .transition() + .each(function () { var sliceTop = d3.select(this); var slicePath = sliceTop.select('path.surface'); - slicePath.transition().attrTween('d', function(pt2) { + slicePath.transition().attrTween('d', function (pt2) { var interp = makeExitSliceInterpolator(pt2); - return function(t) { return pathSlice(interp(t)); }; + return function (t) { + return pathSlice(interp(t)); + }; }); var sliceTextGroup = sliceTop.select('g.slicetext'); @@ -194,18 +212,18 @@ function plotOne(gd, cd, element, transitionOpts) { // next x1 (i.e. sector end angle) of previous entry var nextX1ofPrevEntry = null; - if(hasTransition && prevEntry) { + if (hasTransition && prevEntry) { var prevEntryId = helpers.getPtId(prevEntry); - slices.each(function(pt) { - if(nextX1ofPrevEntry === null && (helpers.getPtId(pt) === prevEntryId)) { + slices.each(function (pt) { + if (nextX1ofPrevEntry === null && helpers.getPtId(pt) === prevEntryId) { nextX1ofPrevEntry = pt.x1; } }); } var updateSlices = slices; - if(hasTransition) { - updateSlices = updateSlices.transition().each('end', function() { + 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); @@ -217,10 +235,10 @@ function plotOne(gd, cd, element, transitionOpts) { }); } - updateSlices.each(function(pt) { + updateSlices.each(function (pt) { var sliceTop = d3.select(this); - var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function(s) { + var slicePath = Lib.ensureSingle(sliceTop, 'path', 'surface', function (s) { s.style('pointer-events', isStatic ? 'none' : 'all'); }); @@ -232,13 +250,15 @@ function plotOne(gd, cd, element, transitionOpts) { pt.startangle = -(pt.x0 - Math.PI / 2); pt.stopangle = -(pt.x1 - Math.PI / 2); pt.halfangle = 0.5 * Math.min(Lib.angleDelta(pt.x0, pt.x1) || Math.PI, Math.PI); - pt.ring = 1 - (pt.rpx0 / pt.rpx1); + pt.ring = 1 - pt.rpx0 / pt.rpx1; pt.rInscribed = getInscribedRadiusFraction(pt, trace); - if(hasTransition) { - slicePath.transition().attrTween('d', function(pt2) { + if (hasTransition) { + slicePath.transition().attrTween('d', function (pt2) { var interp = makeUpdateSliceInterpolator(pt2); - return function(t) { return pathSlice(interp(t)); }; + return function (t) { + return pathSlice(interp(t)); + }; }); } else { slicePath.attr('d', pathSlice); @@ -259,7 +279,7 @@ function plotOne(gd, cd, element, transitionOpts) { slicePath.call(styleOne, pt, trace, gd); var sliceTextGroup = Lib.ensureSingle(sliceTop, 'g', 'slicetext'); - var sliceText = Lib.ensureSingle(sliceTextGroup, 'text', '', function(s) { + 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); @@ -267,7 +287,8 @@ function plotOne(gd, cd, element, transitionOpts) { var font = Lib.ensureUniformFontSize(gd, helpers.determineTextFont(trace, pt, fullLayout.font)); - sliceText.text(exports.formatSliceLabel(pt, entry, trace, cd, fullLayout)) + sliceText + .text(exports.formatSliceLabel(pt, entry, trace, cd, fullLayout)) .classed('slicetext', true) .attr('text-anchor', 'middle') .call(Drawing.font, font) @@ -279,7 +300,7 @@ function plotOne(gd, cd, element, transitionOpts) { pt.transform.targetX = getTargetX(pt); pt.transform.targetY = getTargetY(pt); - var strTransform = function(d, textBB) { + var strTransform = function (d, textBB) { var transform = d.transform; computeTransform(transform, textBB); @@ -289,10 +310,12 @@ function plotOne(gd, cd, element, transitionOpts) { return Lib.getTextTransform(transform); }; - if(hasTransition) { - sliceText.transition().attrTween('transform', function(pt2) { + if (hasTransition) { + sliceText.transition().attrTween('transform', function (pt2) { var interp = makeUpdateTextInterpolator(pt2); - return function(t) { return strTransform(interp(t), textBB); }; + return function (t) { + return strTransform(interp(t), textBB); + }; }); } else { sliceText.attr('transform', strTransform(pt, textBB)); @@ -305,36 +328,39 @@ function plotOne(gd, cd, element, transitionOpts) { var entryPrev = prevLookup[helpers.getPtId(entry)]; var next; - if(entryPrev) { + if (entryPrev) { var a = (pt.x1 > entryPrev.x1 ? 2 * Math.PI : 0) + baseX; // if pt to remove: // - if 'below' where the root-node used to be: shrink it radially inward // - otherwise, collapse it clockwise or counterclockwise which ever is shortest to theta=0 - next = pt.rpx1 < entryPrev.rpx1 ? - {x0: pt.x0, x1: pt.x1, rpx0: 0, rpx1: 0} : - {x0: a, x1: a, rpx0: pt.rpx0, rpx1: pt.rpx1}; + next = + pt.rpx1 < entryPrev.rpx1 + ? { x0: pt.x0, x1: pt.x1, rpx0: 0, rpx1: 0 } + : { x0: a, x1: a, rpx0: pt.rpx0, rpx1: pt.rpx1 }; } else { // this happens when maxdepth is set, when leaves must // be removed and the rootPt is new (i.e. does not have a 'prev' object) var parent; var parentId = helpers.getPtId(pt.parent); - slices.each(function(pt2) { - if(helpers.getPtId(pt2) === parentId) { - return parent = pt2; + slices.each(function (pt2) { + if (helpers.getPtId(pt2) === parentId) { + return (parent = pt2); } }); var parentChildren = parent.children; var ci; - parentChildren.forEach(function(pt2, i) { - if(helpers.getPtId(pt2) === id) { - return ci = i; + parentChildren.forEach(function (pt2, i) { + if (helpers.getPtId(pt2) === id) { + return (ci = i); } }); var n = parentChildren.length; var interp = interpolate(parent.x0, parent.x1); next = { - rpx0: rMax, rpx1: rMax, - x0: interp(ci / n), x1: interp((ci + 1) / n) + rpx0: rMax, + rpx1: rMax, + x0: interp(ci / n), + x1: interp((ci + 1) / n) }; } @@ -344,36 +370,36 @@ function plotOne(gd, cd, element, transitionOpts) { function makeUpdateSliceInterpolator(pt) { var prev0 = prevLookup[helpers.getPtId(pt)]; var prev; - var next = {x0: pt.x0, x1: pt.x1, rpx0: pt.rpx0, rpx1: pt.rpx1}; + var next = { x0: pt.x0, x1: pt.x1, rpx0: pt.rpx0, rpx1: pt.rpx1 }; - if(prev0) { + if (prev0) { // if pt already on graph, this is easy prev = prev0; } else { // for new pts: - if(prevEntry) { + if (prevEntry) { // if trace was visible before - if(pt.parent) { - if(nextX1ofPrevEntry) { + if (pt.parent) { + if (nextX1ofPrevEntry) { // if new branch, twist it in clockwise or // counterclockwise which ever is shorter to // its final angle var a = (pt.x1 > nextX1ofPrevEntry ? 2 * Math.PI : 0) + baseX; - prev = {x0: a, x1: a}; + prev = { x0: a, x1: a }; } else { // if new leaf (when maxdepth is set), // grow it radially and angularly from // its parent node - prev = {rpx0: rMax, rpx1: rMax}; + prev = { rpx0: rMax, rpx1: rMax }; Lib.extendFlat(prev, interpX0X1FromParent(pt)); } } else { // if new root-node, grow it radially - prev = {rpx0: 0, rpx1: 0}; + prev = { rpx0: 0, rpx1: 0 }; } } else { // start sector of new traces from theta=0 - prev = {x0: baseX, x1: baseX}; + prev = { x0: baseX, x1: baseX }; } } @@ -385,7 +411,7 @@ function plotOne(gd, cd, element, transitionOpts) { var prev; var transform = pt.transform; - if(prev0) { + if (prev0) { prev = prev0; } else { prev = { @@ -401,10 +427,10 @@ function plotOne(gd, cd, element, transitionOpts) { }; // for new pts: - if(prevEntry) { + if (prevEntry) { // if trace was visible before - if(pt.parent) { - if(nextX1ofPrevEntry) { + if (pt.parent) { + if (nextX1ofPrevEntry) { // if new branch, twist it in clockwise or // counterclockwise which ever is shorter to // its final angle @@ -433,13 +459,13 @@ function plotOne(gd, cd, element, transitionOpts) { // smooth out start/end from entry, to try to keep text inside sector // while keeping transition smooth - var pow = transform.rCenter === 0 ? 3 : - prev.transform.rCenter === 0 ? 1 / 3 : - 1; + var pow = transform.rCenter === 0 ? 3 : prev.transform.rCenter === 0 ? 1 / 3 : 1; var _rCenterFn = interpolate(prev.transform.rCenter, transform.rCenter); - var rCenterFn = function(t) { return _rCenterFn(Math.pow(t, pow)); }; + var rCenterFn = function (t) { + return _rCenterFn(Math.pow(t, pow)); + }; - return function(t) { + return function (t) { var rpx1 = rpx1Fn(t); var x0 = x0Fn(t); var x1 = x1Fn(t); @@ -476,7 +502,7 @@ function plotOne(gd, cd, element, transitionOpts) { var parentPrev = prevLookup[helpers.getPtId(parent)]; var out = {}; - if(parentPrev) { + if (parentPrev) { // if parent is visible var parentChildren = parent.children; var ci = parentChildren.indexOf(pt); @@ -497,15 +523,14 @@ function plotOne(gd, cd, element, transitionOpts) { // x[0-1] keys are angles [radians] // y[0-1] keys are hierarchy heights [integers] function partition(entry) { - return d3Hierarchy.partition() - .size([2 * Math.PI, entry.height + 1])(entry); + return d3Hierarchy.partition().size([2 * Math.PI, entry.height + 1])(entry); } -exports.formatSliceLabel = function(pt, entry, trace, cd, fullLayout) { +exports.formatSliceLabel = function (pt, entry, trace, cd, fullLayout) { var texttemplate = trace.texttemplate; var textinfo = trace.textinfo; - if(!texttemplate && (!textinfo || textinfo === 'none')) { + if (!texttemplate && (!textinfo || textinfo === 'none')) { return ''; } @@ -517,112 +542,105 @@ exports.formatSliceLabel = function(pt, entry, trace, cd, fullLayout) { var parent = helpers.getParent(hierarchy, pt); var val = helpers.getValue(pt); - if(!texttemplate) { + if (!texttemplate) { var parts = textinfo.split('+'); - var hasFlag = function(flag) { return parts.indexOf(flag) !== -1; }; + var hasFlag = function (flag) { + return parts.indexOf(flag) !== -1; + }; var thisText = []; var tx; - if(hasFlag('label') && cdi.label) { + if (hasFlag('label') && cdi.label) { thisText.push(cdi.label); } - if(cdi.hasOwnProperty('v') && hasFlag('value')) { + if (cdi.hasOwnProperty('v') && hasFlag('value')) { thisText.push(helpers.formatValue(cdi.v, separators)); } - if(!isRoot) { - if(hasFlag('current path')) { + if (!isRoot) { + if (hasFlag('current path')) { thisText.push(helpers.getPath(pt.data)); } var nPercent = 0; - if(hasFlag('percent parent')) nPercent++; - if(hasFlag('percent entry')) nPercent++; - if(hasFlag('percent root')) nPercent++; + if (hasFlag('percent parent')) nPercent++; + if (hasFlag('percent entry')) nPercent++; + if (hasFlag('percent root')) nPercent++; var hasMultiplePercents = nPercent > 1; - if(nPercent) { + if (nPercent) { var percent; - var addPercent = function(key) { + var addPercent = function (key) { tx = helpers.formatPercent(percent, separators); - if(hasMultiplePercents) tx += ' of ' + key; + if (hasMultiplePercents) tx += ' of ' + key; thisText.push(tx); }; - if(hasFlag('percent parent') && !isRoot) { + if (hasFlag('percent parent') && !isRoot) { percent = val / helpers.getValue(parent); addPercent('parent'); } - if(hasFlag('percent entry')) { + if (hasFlag('percent entry')) { percent = val / helpers.getValue(entry); addPercent('entry'); } - if(hasFlag('percent root')) { + if (hasFlag('percent root')) { percent = val / helpers.getValue(hierarchy); addPercent('root'); } } } - if(hasFlag('text')) { + if (hasFlag('text')) { tx = Lib.castOption(trace, cdi.i, 'text'); - if(Lib.isValidTextValue(tx)) thisText.push(tx); + if (Lib.isValidTextValue(tx)) thisText.push(tx); } return thisText.join('
'); } var txt = Lib.castOption(trace, cdi.i, 'texttemplate'); - if(!txt) return ''; + if (!txt) return ''; var obj = {}; - if(cdi.label) obj.label = cdi.label; - if(cdi.hasOwnProperty('v')) { + if (cdi.label) obj.label = cdi.label; + if (cdi.hasOwnProperty('v')) { obj.value = cdi.v; obj.valueLabel = helpers.formatValue(cdi.v, separators); } obj.currentPath = helpers.getPath(pt.data); - if(!isRoot) { + if (!isRoot) { obj.percentParent = val / helpers.getValue(parent); - obj.percentParentLabel = helpers.formatPercent( - obj.percentParent, separators - ); + obj.percentParentLabel = helpers.formatPercent(obj.percentParent, separators); obj.parent = helpers.getPtLabel(parent); } obj.percentEntry = val / helpers.getValue(entry); - obj.percentEntryLabel = helpers.formatPercent( - obj.percentEntry, separators - ); + obj.percentEntryLabel = helpers.formatPercent(obj.percentEntry, separators); obj.entry = helpers.getPtLabel(entry); obj.percentRoot = val / helpers.getValue(hierarchy); - obj.percentRootLabel = helpers.formatPercent( - obj.percentRoot, separators - ); + obj.percentRootLabel = helpers.formatPercent(obj.percentRoot, separators); obj.root = helpers.getPtLabel(hierarchy); - if(cdi.hasOwnProperty('color')) { + if (cdi.hasOwnProperty('color')) { obj.color = cdi.color; } var ptTx = Lib.castOption(trace, cdi.i, 'text'); - if(Lib.isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; + if (Lib.isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; obj.customdata = Lib.castOption(trace, cdi.i, 'customdata'); return Lib.texttemplateString(txt, obj, fullLayout._d3locale, obj, trace._meta || {}); }; function getInscribedRadiusFraction(pt) { - if(pt.rpx0 === 0 && Lib.isFullCircle([pt.x0, pt.x1])) { + if (pt.rpx0 === 0 && Lib.isFullCircle([pt.x0, pt.x1])) { // special case of 100% with no hole return 1; } else { - return Math.max(0, Math.min( - 1 / (1 + 1 / Math.sin(pt.halfangle)), - pt.ring / 2 - )); + return Math.max(0, Math.min(1 / (1 + 1 / Math.sin(pt.halfangle)), pt.ring / 2)); } } diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index 5713887146c..5f8bdbfc084 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -15,7 +15,9 @@ function makeContourProjAttr(axLetter) { dflt: false, description: [ 'Determines whether or not these contour lines are projected', - 'on the', axLetter, 'plane.', + 'on the', + axLetter, + 'plane.', 'If `highlight` is set to *true* (the default), the projected', 'lines are shown on hover.', 'If `show` is set to *true*, the projected lines are shown', @@ -29,41 +31,31 @@ function makeContourAttr(axLetter) { show: { valType: 'boolean', dflt: false, - description: [ - 'Determines whether or not contour lines about the', axLetter, - 'dimension are drawn.' - ].join(' ') + description: ['Determines whether or not contour lines about the', axLetter, 'dimension are drawn.'].join( + ' ' + ) }, start: { valType: 'number', dflt: null, editType: 'plot', - // impliedEdits: {'^autocontour': false}, - description: [ - 'Sets the starting contour level value.', - 'Must be less than `contours.end`' - ].join(' ') + // impliedEdits: {'^autocontour': false}, + description: ['Sets the starting contour level value.', 'Must be less than `contours.end`'].join(' ') }, end: { valType: 'number', dflt: null, editType: 'plot', - // impliedEdits: {'^autocontour': false}, - description: [ - 'Sets the end contour level value.', - 'Must be more than `contours.start`' - ].join(' ') + // impliedEdits: {'^autocontour': false}, + description: ['Sets the end contour level value.', 'Must be more than `contours.start`'].join(' ') }, size: { valType: 'number', dflt: null, min: 0, editType: 'plot', - // impliedEdits: {'^autocontour': false}, - description: [ - 'Sets the step between each contour level.', - 'Must be positive.' - ].join(' ') + // impliedEdits: {'^autocontour': false}, + description: ['Sets the step between each contour level.', 'Must be positive.'].join(' ') }, project: { x: makeContourProjAttr('x'), @@ -95,7 +87,8 @@ function makeContourAttr(axLetter) { valType: 'boolean', dflt: true, description: [ - 'Determines whether or not contour lines about the', axLetter, + 'Determines whether or not contour lines about the', + axLetter, 'dimension are highlighted on hover.' ].join(' ') }, @@ -114,182 +107,191 @@ function makeContourAttr(axLetter) { }; } -var attrs = module.exports = overrideAll(extendFlat({ - z: { - valType: 'data_array', - description: 'Sets the z coordinates.' - }, - x: { - valType: 'data_array', - description: 'Sets the x coordinates.' - }, - y: { - valType: 'data_array', - description: 'Sets the y coordinates.' - }, +var attrs = (module.exports = overrideAll( + extendFlat( + { + z: { + valType: 'data_array', + description: 'Sets the z coordinates.' + }, + x: { + valType: 'data_array', + description: 'Sets the x coordinates.' + }, + y: { + valType: 'data_array', + description: 'Sets the y coordinates.' + }, - text: { - valType: 'string', - dflt: '', - arrayOk: true, - description: [ - 'Sets the text elements associated with each z value.', - 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', - 'these elements will be seen in the hover labels.' - ].join(' ') - }, - hovertext: { - valType: 'string', - dflt: '', - arrayOk: true, - description: 'Same as `text`.' - }, - hovertemplate: hovertemplateAttrs(), + text: { + valType: 'string', + dflt: '', + arrayOk: true, + description: [ + 'Sets the text elements associated with each z value.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }, + hovertext: { + valType: 'string', + dflt: '', + arrayOk: true, + description: 'Same as `text`.' + }, + hovertemplate: hovertemplateAttrs(), - xhoverformat: axisHoverFormat('x'), - yhoverformat: axisHoverFormat('y'), - zhoverformat: axisHoverFormat('z'), + xhoverformat: axisHoverFormat('x'), + yhoverformat: axisHoverFormat('y'), + zhoverformat: axisHoverFormat('z'), - connectgaps: { - valType: 'boolean', - dflt: false, - editType: 'calc', - description: [ - 'Determines whether or not gaps', - '(i.e. {nan} or missing values)', - 'in the `z` data are filled in.' - ].join(' ') - }, + connectgaps: { + valType: 'boolean', + dflt: false, + editType: 'calc', + description: [ + 'Determines whether or not gaps', + '(i.e. {nan} or missing values)', + 'in the `z` data are filled in.' + ].join(' ') + }, - surfacecolor: { - valType: 'data_array', - description: [ - 'Sets the surface color values,', - 'used for setting a color scale independent of `z`.' - ].join(' ') - }, -}, + surfacecolor: { + valType: 'data_array', + description: [ + 'Sets the surface color values,', + 'used for setting a color scale independent of `z`.' + ].join(' ') + } + }, -colorScaleAttrs('', { - colorAttr: 'z or surfacecolor', - showScaleDflt: true, - autoColorDflt: false, - editTypeOverride: 'calc' -}), { - contours: { - x: makeContourAttr('x'), - y: makeContourAttr('y'), - z: makeContourAttr('z') - }, - hidesurface: { - valType: 'boolean', - dflt: false, - description: [ - 'Determines whether or not a surface is drawn.', - 'For example, set `hidesurface` to *false*', - '`contours.x.show` to *true* and', - '`contours.y.show` to *true* to draw a wire frame plot.' - ].join(' ') - }, + colorScaleAttrs('', { + colorAttr: 'z or surfacecolor', + showScaleDflt: true, + autoColorDflt: false, + editTypeOverride: 'calc' + }), + { + contours: { + x: makeContourAttr('x'), + y: makeContourAttr('y'), + z: makeContourAttr('z') + }, + hidesurface: { + valType: 'boolean', + dflt: false, + description: [ + 'Determines whether or not a surface is drawn.', + 'For example, set `hidesurface` to *false*', + '`contours.x.show` to *true* and', + '`contours.y.show` to *true* to draw a wire frame plot.' + ].join(' ') + }, - lightposition: { - x: { - valType: 'number', - min: -1e5, - max: 1e5, - dflt: 10, - description: 'Numeric vector, representing the X coordinate for each vertex.' - }, - y: { - valType: 'number', - min: -1e5, - max: 1e5, - dflt: 1e4, - description: 'Numeric vector, representing the Y coordinate for each vertex.' - }, - z: { - valType: 'number', - min: -1e5, - max: 1e5, - dflt: 0, - description: 'Numeric vector, representing the Z coordinate for each vertex.' - } - }, + lightposition: { + x: { + valType: 'number', + min: -1e5, + max: 1e5, + dflt: 10, + description: 'Numeric vector, representing the X coordinate for each vertex.' + }, + y: { + valType: 'number', + min: -1e5, + max: 1e5, + dflt: 1e4, + description: 'Numeric vector, representing the Y coordinate for each vertex.' + }, + z: { + valType: 'number', + min: -1e5, + max: 1e5, + dflt: 0, + description: 'Numeric vector, representing the Z coordinate for each vertex.' + } + }, - lighting: { - ambient: { - valType: 'number', - min: 0.00, - max: 1.0, - dflt: 0.8, - description: 'Ambient light increases overall color visibility but can wash out the image.' - }, - diffuse: { - valType: 'number', - min: 0.00, - max: 1.00, - dflt: 0.8, - description: 'Represents the extent that incident rays are reflected in a range of angles.' - }, - specular: { - valType: 'number', - min: 0.00, - max: 2.00, - dflt: 0.05, - description: 'Represents the level that incident rays are reflected in a single direction, causing shine.' - }, - roughness: { - valType: 'number', - min: 0.00, - max: 1.00, - dflt: 0.5, - description: 'Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.' - }, - fresnel: { - valType: 'number', - min: 0.00, - max: 5.00, - dflt: 0.2, - description: [ - 'Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective', - 'when viewing it from the edge of the paper (almost 90 degrees), causing shine.' - ].join(' ') - } - }, + lighting: { + ambient: { + valType: 'number', + min: 0.0, + max: 1.0, + dflt: 0.8, + description: 'Ambient light increases overall color visibility but can wash out the image.' + }, + diffuse: { + valType: 'number', + min: 0.0, + max: 1.0, + dflt: 0.8, + description: 'Represents the extent that incident rays are reflected in a range of angles.' + }, + specular: { + valType: 'number', + min: 0.0, + max: 2.0, + dflt: 0.05, + description: + 'Represents the level that incident rays are reflected in a single direction, causing shine.' + }, + roughness: { + valType: 'number', + min: 0.0, + max: 1.0, + dflt: 0.5, + description: + 'Alters specular reflection; the rougher the surface, the wider and less contrasty the shine.' + }, + fresnel: { + valType: 'number', + min: 0.0, + max: 5.0, + dflt: 0.2, + description: [ + 'Represents the reflectance as a dependency of the viewing angle; e.g. paper is reflective', + 'when viewing it from the edge of the paper (almost 90 degrees), causing shine.' + ].join(' ') + } + }, - opacity: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - description: [ - 'Sets the opacity of the surface.', - 'Please note that in the case of using high `opacity` values for example a value', - 'greater than or equal to 0.5 on two surfaces (and 0.25 with four surfaces), an', - 'overlay of multiple transparent surfaces may not perfectly be sorted in depth by the', - 'webgl API. This behavior may be improved in the near future and is subject to change.' - ].join(' ') - }, + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + description: [ + 'Sets the opacity of the surface.', + 'Please note that in the case of using high `opacity` values for example a value', + 'greater than or equal to 0.5 on two surfaces (and 0.25 with four surfaces), an', + 'overlay of multiple transparent surfaces may not perfectly be sorted in depth by the', + 'webgl API. This behavior may be improved in the near future and is subject to change.' + ].join(' ') + }, - opacityscale: { - valType: 'any', - editType: 'calc', - description: [ - 'Sets the opacityscale.', - 'The opacityscale must be an array containing', - 'arrays mapping a normalized value to an opacity value.', - 'At minimum, a mapping for the lowest (0) and highest (1)', - 'values are required. For example,', - '`[[0, 1], [0.5, 0.2], [1, 1]]` means that higher/lower values would have', - 'higher opacity values and those in the middle would be more transparent', - 'Alternatively, `opacityscale` may be a palette name string', - 'of the following list: \'min\', \'max\', \'extremes\' and \'uniform\'.', - 'The default is \'uniform\'.' - ].join(' ') - }, + opacityscale: { + valType: 'any', + editType: 'calc', + description: [ + 'Sets the opacityscale.', + 'The opacityscale must be an array containing', + 'arrays mapping a normalized value to an opacity value.', + 'At minimum, a mapping for the lowest (0) and highest (1)', + 'values are required. For example,', + '`[[0, 1], [0.5, 0.2], [1, 1]]` means that higher/lower values would have', + 'higher opacity values and those in the middle would be more transparent', + 'Alternatively, `opacityscale` may be a palette name string', + "of the following list: 'min', 'max', 'extremes' and 'uniform'.", + "The default is 'uniform'." + ].join(' ') + }, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}), -}), 'calc', 'nested'); + hoverinfo: extendFlat({}, baseAttrs.hoverinfo), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + } + ), + 'calc', + 'nested' +)); attrs.x.editType = attrs.y.editType = attrs.z.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 243d0b7ccd7..195bc21c4b5 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -11,13 +11,10 @@ var MIN = 0.1; // Note: often we don't want the data cube to be disappeared function createWave(n, minOpacity) { var arr = []; var steps = 32; // Max: 256 - for(var i = 0; i < steps; i++) { + for (var i = 0; i < steps; i++) { var u = i / (steps - 1); var v = minOpacity + (1 - minOpacity) * (1 - Math.pow(Math.sin(n * u * Math.PI), 2)); - arr.push([ - u, - Math.max(0, Math.min(1, v)) - ]); + arr.push([u, Math.max(0, Math.min(1, v))]); } return arr; } @@ -25,16 +22,16 @@ function createWave(n, minOpacity) { function isValidScaleArray(scl) { var highestVal = 0; - if(!Array.isArray(scl) || scl.length < 2) return false; + if (!Array.isArray(scl) || scl.length < 2) return false; - if(!scl[0] || !scl[scl.length - 1]) return false; + if (!scl[0] || !scl[scl.length - 1]) return false; - if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false; + if (+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false; - for(var i = 0; i < scl.length; i++) { + for (var i = 0; i < scl.length; i++) { var si = scl[i]; - if(si.length !== 2 || +si[0] < highestVal) { + if (si.length !== 2 || +si[0] < highestVal) { return false; } @@ -55,15 +52,12 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var y = coerce('y'); var z = coerce('z'); - if(!z || !z.length || - (x ? (x.length < 1) : false) || - (y ? (y.length < 1) : false) - ) { + if (!z || !z.length || (x ? x.length < 1 : false) || (y ? y.length < 1 : false)) { traceOut.visible = false; return; } - traceOut._xlength = (Array.isArray(x) && Lib.isArrayOrTypedArray(x[0])) ? z.length : z[0].length; + traceOut._xlength = Array.isArray(x) && Lib.isArrayOrTypedArray(x[0]) ? z.length : z[0].length; traceOut._ylength = z.length; var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); @@ -89,29 +83,31 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { 'hidesurface', 'connectgaps', 'opacity' - ].forEach(function(x) { coerce(x); }); + ].forEach(function (x) { + coerce(x); + }); var surfaceColor = coerce('surfacecolor'); var dims = ['x', 'y', 'z']; - for(i = 0; i < 3; ++i) { + for (i = 0; i < 3; ++i) { var contourDim = 'contours.' + dims[i]; var show = coerce(contourDim + '.show'); var highlight = coerce(contourDim + '.highlight'); - if(show || highlight) { - for(j = 0; j < 3; ++j) { + if (show || highlight) { + for (j = 0; j < 3; ++j) { coerce(contourDim + '.project.' + dims[j]); } } - if(show) { + if (show) { coerce(contourDim + '.color'); coerce(contourDim + '.width'); coerce(contourDim + '.usecolormap'); } - if(highlight) { + if (highlight) { coerce(contourDim + '.highlightcolor'); coerce(contourDim + '.highlightwidth'); } @@ -124,9 +120,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { // TODO if contours.?.usecolormap are false and hidesurface is true // the colorbar shouldn't be shown by default - colorscaleDefaults( - traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'} - ); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'c' }); opacityscaleDefaults(traceIn, traceOut, layout, coerce); @@ -137,13 +131,19 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { function opacityscaleDefaults(traceIn, traceOut, layout, coerce) { var opacityscale = coerce('opacityscale'); - if(opacityscale === 'max') { - traceOut.opacityscale = [[0, MIN], [1, 1]]; - } else if(opacityscale === 'min') { - traceOut.opacityscale = [[0, 1], [1, MIN]]; - } else if(opacityscale === 'extremes') { + if (opacityscale === 'max') { + traceOut.opacityscale = [ + [0, MIN], + [1, 1] + ]; + } else if (opacityscale === 'min') { + traceOut.opacityscale = [ + [0, 1], + [1, MIN] + ]; + } else if (opacityscale === 'extremes') { traceOut.opacityscale = createWave(1, MIN); - } else if(!isValidScaleArray(opacityscale)) { + } else if (!isValidScaleArray(opacityscale)) { traceOut.opacityscale = undefined; } } diff --git a/src/traces/treemap/attributes.js b/src/traces/treemap/attributes.js index cc23edbb184..e3c862bf2fa 100644 --- a/src/traces/treemap/attributes.js +++ b/src/traces/treemap/attributes.js @@ -25,14 +25,7 @@ module.exports = { tiling: { packing: { valType: 'enumerated', - values: [ - 'squarify', - 'binary', - 'dice', - 'slice', - 'slice-dice', - 'dice-slice' - ], + values: ['squarify', 'binary', 'dice', 'slice', 'slice-dice', 'dice-slice'], dflt: 'squarify', editType: 'plot', description: [ @@ -60,15 +53,10 @@ module.exports = { flip: { valType: 'flaglist', - flags: [ - 'x', - 'y' - ], + flags: ['x', 'y'], dflt: '', editType: 'plot', - description: [ - 'Determines if the positions obtained from solver are flipped on each axis.' - ].join(' ') + description: ['Determines if the positions obtained from solver are flipped on each axis.'].join(' ') }, pad: { @@ -76,86 +64,75 @@ module.exports = { min: 0, dflt: 3, editType: 'plot', - description: [ - 'Sets the inner padding (in px).' - ].join(' ') + description: ['Sets the inner padding (in px).'].join(' ') }, - editType: 'calc', + editType: 'calc' }, - marker: extendFlat({ - pad: { - t: { - valType: 'number', - min: 0, - editType: 'plot', - description: [ - 'Sets the padding form the top (in px).' - ].join(' ') + marker: extendFlat( + { + pad: { + t: { + valType: 'number', + min: 0, + editType: 'plot', + description: ['Sets the padding form the top (in px).'].join(' ') + }, + l: { + valType: 'number', + min: 0, + editType: 'plot', + description: ['Sets the padding form the left (in px).'].join(' ') + }, + r: { + valType: 'number', + min: 0, + editType: 'plot', + description: ['Sets the padding form the right (in px).'].join(' ') + }, + b: { + valType: 'number', + min: 0, + editType: 'plot', + description: ['Sets the padding form the bottom (in px).'].join(' ') + }, + + editType: 'calc' }, - l: { - valType: 'number', - min: 0, - editType: 'plot', - description: [ - 'Sets the padding form the left (in px).' - ].join(' ') - }, - r: { - valType: 'number', - min: 0, - editType: 'plot', + + colors: sunburstAttrs.marker.colors, + + pattern: pattern, + + depthfade: { + valType: 'enumerated', + values: [true, false, 'reversed'], + editType: 'style', description: [ - 'Sets the padding form the right (in px).' + 'Determines if the sector colors are faded towards', + 'the background from the leaves up to the headers.', + 'This option is unavailable when a `colorscale` is present,', + 'defaults to false when `marker.colors` is set,', + 'but otherwise defaults to true.', + 'When set to *reversed*, the fading direction is inverted,', + 'that is the top elements within hierarchy are drawn with fully saturated colors', + 'while the leaves are faded towards the background color.' ].join(' ') }, - b: { + + line: sunburstAttrs.marker.line, + + cornerradius: { valType: 'number', min: 0, + dflt: 0, editType: 'plot', - description: [ - 'Sets the padding form the bottom (in px).' - ].join(' ') + description: ['Sets the maximum rounding of corners (in px).'].join(' ') }, editType: 'calc' }, - - colors: sunburstAttrs.marker.colors, - - pattern: pattern, - - depthfade: { - valType: 'enumerated', - values: [true, false, 'reversed'], - editType: 'style', - description: [ - 'Determines if the sector colors are faded towards', - 'the background from the leaves up to the headers.', - 'This option is unavailable when a `colorscale` is present,', - 'defaults to false when `marker.colors` is set,', - 'but otherwise defaults to true.', - 'When set to *reversed*, the fading direction is inverted,', - 'that is the top elements within hierarchy are drawn with fully saturated colors', - 'while the leaves are faded towards the background color.' - ].join(' ') - }, - - line: sunburstAttrs.marker.line, - - cornerradius: { - valType: 'number', - min: 0, - dflt: 0, - editType: 'plot', - description: [ - 'Sets the maximum rounding of corners (in px).' - ].join(' ') - }, - - editType: 'calc', - }, colorScaleAttrs('marker', { colorAttr: 'colors', anim: false // TODO: set to anim: true? @@ -176,32 +153,18 @@ module.exports = { side: { valType: 'enumerated', - values: [ - 'top', - 'bottom' - ], + values: ['top', 'bottom'], dflt: 'top', editType: 'plot', - description: [ - 'Determines on which side of the the treemap the', - '`pathbar` should be presented.' - ].join(' ') + description: ['Determines on which side of the the treemap the', '`pathbar` should be presented.'].join(' ') }, edgeshape: { valType: 'enumerated', - values: [ - '>', - '<', - '|', - '/', - '\\' - ], + values: ['>', '<', '|', '/', '\\'], dflt: '>', editType: 'plot', - description: [ - 'Determines which shape is used for edges between `barpath` labels.' - ].join(' ') + description: ['Determines which shape is used for edges between `barpath` labels.'].join(' ') }, thickness: { @@ -224,15 +187,21 @@ module.exports = { text: pieAttrs.text, textinfo: sunburstAttrs.textinfo, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys.concat(['label', 'value']) - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys.concat(['label', 'value']) + } + ), hovertext: pieAttrs.hovertext, hoverinfo: sunburstAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), textfont: pieAttrs.textfont, insidetextfont: pieAttrs.insidetextfont, @@ -242,25 +211,29 @@ module.exports = { 'This option refers to the root of the hierarchy', 'presented on top left corner of a treemap graph.', 'Please note that if a hierarchy has multiple root nodes,', - 'this option won\'t have any effect and `insidetextfont` would be used.' + "this option won't have any effect and `insidetextfont` would be used." ].join(' ') }), textposition: { valType: 'enumerated', values: [ - 'top left', 'top center', 'top right', - 'middle left', 'middle center', 'middle right', - 'bottom left', 'bottom center', 'bottom right' + 'top left', + 'top center', + 'top right', + 'middle left', + 'middle center', + 'middle right', + 'bottom left', + 'bottom center', + 'bottom right' ], dflt: 'top left', editType: 'plot', - description: [ - 'Sets the positions of the `text` elements.' - ].join(' ') + description: ['Sets the positions of the `text` elements.'].join(' ') }, sort: pieAttrs.sort, root: sunburstAttrs.root, - domain: domainAttrs({name: 'treemap', trace: true, editType: 'calc'}), + domain: domainAttrs({ name: 'treemap', trace: true, editType: 'calc' }) }; diff --git a/src/traces/treemap/defaults.js b/src/traces/treemap/defaults.js index 930ca32d5ef..089bf65f318 100644 --- a/src/traces/treemap/defaults.js +++ b/src/traces/treemap/defaults.js @@ -20,13 +20,13 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var labels = coerce('labels'); var parents = coerce('parents'); - if(!labels || !labels.length || !parents || !parents.length) { + if (!labels || !labels.length || !parents || !parents.length) { traceOut.visible = false; return; } var vals = coerce('values'); - if(vals && vals.length) { + if (vals && vals.length) { coerce('branchvalues'); } else { coerce('count'); @@ -36,7 +36,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('maxdepth'); var packing = coerce('tiling.packing'); - if(packing === 'squarify') { + if (packing === 'squarify') { coerce('tiling.squarifyratio'); } @@ -45,7 +45,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var text = coerce('text'); coerce('texttemplate'); - if(!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); + if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); @@ -66,12 +66,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var bottomText = traceOut.textposition.indexOf('bottom') !== -1; handleMarkerDefaults(traceIn, traceOut, layout, coerce); - 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'}); + 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' }); } else { coerce('marker.depthfade', !(traceOut.marker.colors || []).length); } @@ -94,7 +92,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } }; - if(hasPathbar) { + if (hasPathbar) { // This works even for multi-line labels as treemap pathbar trim out line breaks coerce('pathbar.thickness', traceOut.pathbar.textfont.size + 2 * TEXTPAD); diff --git a/src/traces/violin/attributes.js b/src/traces/violin/attributes.js index ca1eecd2500..63289c66427 100644 --- a/src/traces/violin/attributes.js +++ b/src/traces/violin/attributes.js @@ -38,7 +38,7 @@ module.exports = { editType: 'calc', description: [ 'Sets the bandwidth used to compute the kernel density estimate.', - 'By default, the bandwidth is determined by Silverman\'s rule of thumb.' + "By default, the bandwidth is determined by Silverman's rule of thumb." ].join(' ') }, @@ -50,7 +50,7 @@ module.exports = { 'If there are multiple violins that should be sized according to', 'to some metric (see `scalemode`), link them by providing a non-empty group id here', 'shared by every trace in the same group.', - 'If a violin\'s `width` is undefined, `scalegroup` will default to the trace\'s name.', + "If a violin's `width` is undefined, `scalegroup` will default to the trace's name.", 'In this case, violins with the same names will be linked together' ].join(' ') }, @@ -74,17 +74,17 @@ module.exports = { editType: 'calc', description: [ 'Sets the method by which the span in data space where the density function will be computed.', - '*soft* means the span goes from the sample\'s minimum value minus two bandwidths', - 'to the sample\'s maximum value plus two bandwidths.', - '*hard* means the span goes from the sample\'s minimum to its maximum value.', + "*soft* means the span goes from the sample's minimum value minus two bandwidths", + "to the sample's maximum value plus two bandwidths.", + "*hard* means the span goes from the sample's minimum to its maximum value.", 'For custom span settings, use mode *manual* and fill in the `span` attribute.' ].join(' ') }, span: { valType: 'info_array', items: [ - {valType: 'any', editType: 'calc'}, - {valType: 'any', editType: 'calc'} + { valType: 'any', editType: 'calc' }, + { valType: 'any', editType: 'calc' } ], editType: 'calc', description: [ @@ -145,7 +145,7 @@ module.exports = { description: [ 'Sets the width of the violin in data coordinates.', 'If *0* (default value) the width is automatically selected based on the positions', - 'of other violin traces in the same subplot.', + 'of other violin traces in the same subplot.' ].join(' ') }), @@ -161,9 +161,7 @@ module.exports = { valType: 'boolean', dflt: false, editType: 'plot', - description: [ - 'Determines if an miniature box plot is drawn inside the violins. ' - ].join(' ') + description: ['Determines if an miniature box plot is drawn inside the violins. '].join(' ') }, width: { valType: 'number', @@ -173,7 +171,7 @@ module.exports = { editType: 'plot', description: [ 'Sets the width of the inner box plots relative to', - 'the violins\' width.', + "the violins' width.", 'For example, with 1, the inner box plots are as wide as the violins.' ].join(' ') }, @@ -205,7 +203,7 @@ module.exports = { dflt: false, editType: 'plot', description: [ - 'Determines if a line corresponding to the sample\'s mean is shown', + "Determines if a line corresponding to the sample's mean is shown", 'inside the violins.', 'If `box.visible` is turned on, the mean line is drawn inside the inner box.', 'Otherwise, the mean line is drawn from one side of the violin to other.' diff --git a/src/traces/volume/attributes.js b/src/traces/volume/attributes.js index f54948f84fb..3913bd7cf2e 100644 --- a/src/traces/volume/attributes.js +++ b/src/traces/volume/attributes.js @@ -8,65 +8,71 @@ var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; var overrideAll = require('../../plot_api/edit_types').overrideAll; -var attrs = module.exports = overrideAll(extendFlat({ - x: isosurfaceAttrs.x, - y: isosurfaceAttrs.y, - z: isosurfaceAttrs.z, - value: isosurfaceAttrs.value, - isomin: isosurfaceAttrs.isomin, - isomax: isosurfaceAttrs.isomax, - surface: isosurfaceAttrs.surface, - spaceframe: { - show: { - valType: 'boolean', - dflt: false, - description: [ - 'Displays/hides tetrahedron shapes between minimum and', - 'maximum iso-values. Often useful when either caps or', - 'surfaces are disabled or filled with values less than 1.' - ].join(' ') - }, - fill: { - valType: 'number', - min: 0, - max: 1, - dflt: 1, - description: [ - 'Sets the fill ratio of the `spaceframe` elements. The default fill value', - 'is 1 meaning that they are entirely shaded. Applying a `fill` ratio less', - 'than one would allow the creation of openings parallel to the edges.' - ].join(' ') - } - }, - - slices: isosurfaceAttrs.slices, - caps: isosurfaceAttrs.caps, - text: isosurfaceAttrs.text, - hovertext: isosurfaceAttrs.hovertext, - xhoverformat: isosurfaceAttrs.xhoverformat, - yhoverformat: isosurfaceAttrs.yhoverformat, - zhoverformat: isosurfaceAttrs.zhoverformat, - valuehoverformat: isosurfaceAttrs.valuehoverformat, - hovertemplate: isosurfaceAttrs.hovertemplate -}, +var attrs = (module.exports = overrideAll( + extendFlat( + { + x: isosurfaceAttrs.x, + y: isosurfaceAttrs.y, + z: isosurfaceAttrs.z, + value: isosurfaceAttrs.value, + isomin: isosurfaceAttrs.isomin, + isomax: isosurfaceAttrs.isomax, + surface: isosurfaceAttrs.surface, + spaceframe: { + show: { + valType: 'boolean', + dflt: false, + description: [ + 'Displays/hides tetrahedron shapes between minimum and', + 'maximum iso-values. Often useful when either caps or', + 'surfaces are disabled or filled with values less than 1.' + ].join(' ') + }, + fill: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + description: [ + 'Sets the fill ratio of the `spaceframe` elements. The default fill value', + 'is 1 meaning that they are entirely shaded. Applying a `fill` ratio less', + 'than one would allow the creation of openings parallel to the edges.' + ].join(' ') + } + }, -colorScaleAttrs('', { - colorAttr: '`value`', - showScaleDflt: true, - editTypeOverride: 'calc' -}), { + slices: isosurfaceAttrs.slices, + caps: isosurfaceAttrs.caps, + text: isosurfaceAttrs.text, + hovertext: isosurfaceAttrs.hovertext, + xhoverformat: isosurfaceAttrs.xhoverformat, + yhoverformat: isosurfaceAttrs.yhoverformat, + zhoverformat: isosurfaceAttrs.zhoverformat, + valuehoverformat: isosurfaceAttrs.valuehoverformat, + hovertemplate: isosurfaceAttrs.hovertemplate + }, - colorbar: isosurfaceAttrs.colorbar, - opacity: isosurfaceAttrs.opacity, - opacityscale: surfaceAttrs.opacityscale, + colorScaleAttrs('', { + colorAttr: '`value`', + showScaleDflt: true, + editTypeOverride: 'calc' + }), + { + colorbar: isosurfaceAttrs.colorbar, + opacity: isosurfaceAttrs.opacity, + opacityscale: surfaceAttrs.opacityscale, - lightposition: isosurfaceAttrs.lightposition, - lighting: isosurfaceAttrs.lighting, - flatshading: isosurfaceAttrs.flatshading, - contour: isosurfaceAttrs.contour, + lightposition: isosurfaceAttrs.lightposition, + lighting: isosurfaceAttrs.lighting, + flatshading: isosurfaceAttrs.flatshading, + contour: isosurfaceAttrs.contour, - hoverinfo: extendFlat({}, baseAttrs.hoverinfo), - showlegend: extendFlat({}, baseAttrs.showlegend, {dflt: false}) -}), 'calc', 'nested'); + hoverinfo: extendFlat({}, baseAttrs.hoverinfo), + showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) + } + ), + 'calc', + 'nested' +)); attrs.x.editType = attrs.y.editType = attrs.z.editType = attrs.value.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/waterfall/attributes.js b/src/traces/waterfall/attributes.js index afd5e9f29ad..468eec1543e 100644 --- a/src/traces/waterfall/attributes.js +++ b/src/traces/waterfall/attributes.js @@ -29,7 +29,7 @@ function directionAttrs(dirTxt) { editType: 'style', description: 'Sets the line width of all ' + dirTxt + ' values.' }), - editType: 'style', + editType: 'style' }, editType: 'style' }, @@ -44,9 +44,9 @@ module.exports = { editType: 'calc', description: [ 'An array containing types of values.', - 'By default the values are considered as \'relative\'.', - 'However; it is possible to use \'total\' to compute the sums.', - 'Also \'absolute\' could be applied to reset the computed total', + "By default the values are considered as 'relative'.", + "However; it is possible to use 'total' to compute the sums.", + "Also 'absolute' could be applied to reset the computed total", 'or to declare an initial value where needed.' ].join(' ') }, @@ -56,9 +56,7 @@ module.exports = { dflt: null, arrayOk: false, editType: 'calc', - description: [ - 'Sets where the bar base is drawn (in position axis units).' - ].join(' ') + description: ['Sets where the bar base is drawn (in position axis units).'].join(' ') }, x: barAttrs.x, @@ -78,9 +76,12 @@ module.exports = { yhoverformat: axisHoverFormat('y'), hovertext: barAttrs.hovertext, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs( + {}, + { + keys: constants.eventDataKeys + } + ), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['name', 'x', 'y', 'text', 'initial', 'delta', 'final'] @@ -99,9 +100,12 @@ module.exports = { ].join(' ') }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs({editType: 'plot'}, { - keys: constants.eventDataKeys.concat(['label']) - }), + texttemplate: texttemplateAttrs( + { editType: 'plot' }, + { + keys: constants.eventDataKeys.concat(['label']) + } + ), text: barAttrs.text, textposition: barAttrs.textposition, insidetextanchor: barAttrs.insidetextanchor, @@ -123,9 +127,9 @@ module.exports = { connector: { line: { - color: extendFlat({}, lineAttrs.color, {dflt: Color.defaultLine}), + color: extendFlat({}, lineAttrs.color, { dflt: Color.defaultLine }), width: extendFlat({}, lineAttrs.width, { - editType: 'plot', // i.e. to adjust bars is mode: 'between'. See https://github.com/plotly/plotly.js/issues/3787 + editType: 'plot' // i.e. to adjust bars is mode: 'between'. See https://github.com/plotly/plotly.js/issues/3787 }), dash: lineAttrs.dash, editType: 'plot' @@ -135,17 +139,13 @@ module.exports = { values: ['spanning', 'between'], dflt: 'between', editType: 'plot', - description: [ - 'Sets the shape of connector lines.' - ].join(' ') + description: ['Sets the shape of connector lines.'].join(' ') }, visible: { valType: 'boolean', dflt: true, editType: 'plot', - description: [ - 'Determines if connector lines are drawn. ' - ].join(' ') + description: ['Determines if connector lines are drawn. '].join(' ') }, editType: 'plot' }, diff --git a/src/traces/waterfall/defaults.js b/src/traces/waterfall/defaults.js index 3df93af6f5d..4c43922d9b5 100644 --- a/src/traces/waterfall/defaults.js +++ b/src/traces/waterfall/defaults.js @@ -26,7 +26,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { } var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } @@ -37,7 +37,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('measure'); - coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v'); + coerce('orientation', traceOut.x && !traceOut.y ? 'h' : 'v'); coerce('base'); coerce('offset'); coerce('width'); @@ -57,10 +57,9 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { moduleHasInsideanchor: true }); - - if(traceOut.textposition !== 'none') { + if (traceOut.textposition !== 'none') { coerce('texttemplate'); - if(!traceOut.texttemplate) coerce('textinfo'); + if (!traceOut.texttemplate) coerce('textinfo'); } handleDirection(coerce, 'increasing', INCREASING_COLOR); @@ -68,10 +67,10 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { handleDirection(coerce, 'totals', TOTALS_COLOR); var connectorVisible = coerce('connector.visible'); - if(connectorVisible) { + if (connectorVisible) { coerce('connector.mode'); var connectorLineWidth = coerce('connector.line.width'); - if(connectorLineWidth) { + if (connectorLineWidth) { coerce('connector.line.color'); coerce('connector.line.dash'); } @@ -85,8 +84,8 @@ function crossTraceDefaults(fullData, fullLayout) { function coerce(attr) { return Lib.coerce(traceOut._input, traceOut, attributes, attr); } - if(fullLayout.waterfallmode === 'group') { - for(var i = 0; i < fullData.length; i++) { + if (fullLayout.waterfallmode === 'group') { + for (var i = 0; i < fullData.length; i++) { traceOut = fullData[i]; traceIn = traceOut._input; diff --git a/test/jasmine/tests/icicle_test.js b/test/jasmine/tests/icicle_test.js index 7032478731c..ed5ef817fc9 100644 --- a/test/jasmine/tests/icicle_test.js +++ b/test/jasmine/tests/icicle_test.js @@ -17,15 +17,15 @@ var checkTextTemplate = require('../assets/check_texttemplate'); var SLICES_SELECTOR = '.iciclelayer path.surface'; function _mouseEvent(type, gd, v) { - return function() { - if(Array.isArray(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}); + mouseEvent(type, 0, 0, { element: el }); } }; } @@ -34,7 +34,7 @@ function hover(gd, v) { return _mouseEvent('mouseover', gd, v); } -describe('Test icicle defaults:', function() { +describe('Test icicle defaults:', function () { var gd; var fullData; @@ -42,8 +42,8 @@ describe('Test icicle defaults:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'icicle'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'icicle' }, o || {}); }); gd.layout = layout || {}; @@ -51,23 +51,19 @@ describe('Test icicle defaults:', function() { fullData = gd._fullData; } - it('should set *visible:false* when *labels* or *parents* is missing', function() { - _supply([ - {labels: [1], parents: ['']}, - {labels: [1]}, - {parents: ['']} - ]); + 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() { + 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]} + { labels: [1], parents: [''] }, + { labels: [1], parents: [''], values: [] }, + { labels: [1], parents: [''], values: [1] } ]); expect(fullData[0].count).toBe('leaves'); @@ -75,107 +71,106 @@ describe('Test icicle defaults:', function() { expect(fullData[2].count).toBe(undefined, 'has values'); }); - it('should not coerce *branchvalues* when *values* is not set', function() { + it('should not coerce *branchvalues* when *values* is not set', function () { _supply([ - {labels: [1], parents: [''], values: [1]}, - {labels: [1], parents: ['']}, + { 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' - }); + 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() { + 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: ['']} + { 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() { + it('should default *leaf.opacity* depending on a *colorscale* being present or not', function () { _supply([ - {labels: [1], parents: ['']}, - {labels: [1], parents: [''], marker: {colorscale: 'Blues'}} + { 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() { + it('should include "text" flag in *textinfo* when *text* is set', function () { _supply([ - {labels: [1], parents: [''], text: ['A']}, - {labels: [1], parents: ['']} + { 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: ['']} - ], { + 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'); + expect(gd._fullLayout.iciclecolorway).toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); - _supply([ - {labels: [1], parents: ['']} - ], { + _supply([{ labels: [1], parents: [''] }], { colorway: ['red', 'blue', 'green'], iciclecolorway: ['cyan', 'yellow', 'black'] }); - expect(gd._fullLayout.iciclecolorway) - .toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); + 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: ['']} - ]); + 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() { + it('should default *marker.colorscale* to *Reds* when *marker.colors* is present', function () { _supply([ - {labels: [1], parents: [''], marker: { - colors: [0] - }} + { + 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)' ] + [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() { +describe('Test icicle calc:', function () { var gd; - beforeEach(function() { + beforeEach(function () { spyOn(Lib, 'warn'); }); @@ -183,8 +178,8 @@ describe('Test icicle calc:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'icicle'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'icicle' }, o || {}); }); gd.layout = layout || {}; @@ -193,22 +188,24 @@ describe('Test icicle calc:', function() { } function extract(k) { - var out = gd.calcdata.map(function(cd) { - return cd.map(function(pt) { return pt[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) { + 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() { + it('should generate *id* when it can', function () { _calc({ labels: ['Root', 'A', 'B', 'b'], parents: ['', 'Root', 'Root', 'B'] @@ -218,9 +215,9 @@ describe('Test icicle calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should generate "implied root" when it can', function() { + it('should generate "implied root" when it can', function () { _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['Root', 'Root', 'B'] }); @@ -230,23 +227,25 @@ describe('Test icicle calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when there are multiple implied roots', function() { + it('should warn when there are multiple implied roots', function () { _calc({ - labels: [ 'A', 'B', 'b'], + 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'); + 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() { + it('should generate "root of roots" when it can', function () { + spyOn(Lib, 'randstr').and.callFake(function () { return 'dummy'; }); _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['', '', 'B'] }); @@ -255,16 +254,16 @@ describe('Test icicle calc:', function() { expect(extract('label')).toEqual(['', 'A', 'B', 'b']); }); - it('should compute hierarchy values', function() { + 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'} + { 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([ @@ -277,7 +276,7 @@ describe('Test icicle calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when values under *branchvalues:total* do not add up and not show trace', function() { + 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'], @@ -288,11 +287,15 @@ describe('Test icicle calc:', function() { 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'); + 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() { + it('should warn labels/parents lead to ambiguous hierarchy', function () { _calc({ labels: ['Root', 'A', 'A', 'B'], parents: ['', 'Root', 'Root', 'A'] @@ -302,7 +305,7 @@ describe('Test icicle calc:', function() { 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() { + 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'], @@ -313,7 +316,7 @@ describe('Test icicle calc:', function() { 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() { + 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], @@ -324,7 +327,7 @@ describe('Test icicle calc:', function() { expect(extract('pid')).toEqual(['', '0', '0', '2', '2', '0', '0', '6', '0']); }); - it('should accept mix typed are ids/parents items', function() { + 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], @@ -335,18 +338,21 @@ describe('Test icicle calc:', function() { 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' - }]); + 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'], @@ -358,20 +364,25 @@ describe('Test icicle calc:', function() { ]); }); - it('should compute correct sector *value* for generated "root of roots"', function() { - spyOn(Lib, 'randstr').and.callFake(function() { return 'dummy'; }); + 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' - }]); + _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'], @@ -383,7 +394,7 @@ describe('Test icicle calc:', function() { ]); }); - it('should use *marker.colors*', function() { + 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'], @@ -403,7 +414,7 @@ describe('Test icicle calc:', function() { expect(cd[8].color).toEqual('rgba(255, 255, 255, 1)'); }); - it('should use *marker.colors* numbers with default colorscale', function() { + 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'], @@ -423,7 +434,7 @@ describe('Test icicle calc:', function() { expect(cd[8].color).toEqual('rgb(178, 10, 28)'); }); - it('should use *marker.colors* numbers with desired colorscale', function() { + 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'], @@ -443,7 +454,7 @@ describe('Test icicle calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use *marker.colors* numbers not values with colorscale', function() { + 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' }, @@ -464,7 +475,7 @@ describe('Test icicle calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use values with colorscale when *marker.colors* in empty', function() { + 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' }, @@ -486,7 +497,7 @@ describe('Test icicle calc:', function() { }); }); -describe('Test icicle hover:', function() { +describe('Test icicle hover:', function () { var gd; var labels0 = ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura']; @@ -498,31 +509,36 @@ describe('Test icicle hover:', function() { function run(spec) { gd = createGraphDiv(); - var data = (spec.traces || [{}]).map(function(t) { + var data = (spec.traces || [{}]).map(function (t) { t.type = 'icicle'; - if(!t.labels) t.labels = labels0.slice(); - if(!t.parents) t.parents = parents0.slice(); + 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 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(function () { + gd.once('plotly_hover', function (d) { + ptData = d.points[0]; + }); }) .then(hover(gd, spec.pos)) - .then(function() { + .then(function () { assertHoverLabelContent(exp.label); - for(var k in exp.ptData) { + for (var k in exp.ptData) { expect(ptData[k]).toBe(exp.ptData[k], 'pt event data key ' + k); } @@ -532,247 +548,268 @@ describe('Test icicle hover:', function() { expect(typeof ptData.bbox.y0).toEqual('number'); expect(typeof ptData.bbox.y1).toEqual('number'); - if(exp.style) { + 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: 'base', + pos: 2, + exp: { + label: { + nums: 'Seth' + }, + ptData: { + curveNumber: 0, + pointNumber: 2, + label: 'Seth', + 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 scalar hovertext', + traces: [{ hovertext: 'A' }], + pos: 3, + exp: { + label: { + nums: 'Cain\nA' + }, + ptData: { + curveNumber: 0, + pointNumber: 1, + label: 'Cain', + 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 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 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 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 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 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 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' + }, + { + 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 } } - }], - 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: '' + }, + { + 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) { + ].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() { +describe('Test icicle restyle:', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); function _restyle(updateObj) { - return function() { return Plotly.restyle(gd, updateObj); }; + return function () { + return Plotly.restyle(gd, updateObj); + }; } - it('should be able to toggle visibility', function(done) { + it('should be able to toggle visibility', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/icicle_first.json')); function _assert(msg, exp) { - return function() { + 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); + .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) { + it('should be able to restyle *maxdepth* and *level* w/o recomputing the hierarchy', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/icicle_coffee.json')); function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.iciclelayer'); expect(layer.selectAll('.slice').size()).toBe(exp, msg); // editType:plot - if(msg !== 'base') { + 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) { + .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'] - }] + data: [ + { + type: 'icicle', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + } + ] }; function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.iciclelayer'); var opacities = []; - layer.selectAll('path.surface').each(function() { + layer.selectAll('path.surface').each(function () { opacities.push(this.style.opacity); }); expect(opacities).toEqual(exp, msg); // editType:style - if(msg !== 'base') { + if (msg !== 'base') { expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); expect(gd._fullData[0]._module.plot).toHaveBeenCalledTimes(0); } @@ -780,130 +817,339 @@ describe('Test icicle restyle:', function() { } 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); + .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']], +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}', - 'value: %{value}', + [ + '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}', - '%{percentParent} of %{parent}', - 'color: %{color}' + [ + '%{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: 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' + [ + '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']], +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}', - 'text: %{text}', + [ + '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}', - '%{percentParent} of %{parent}', - 'color: %{color}' + [ + '%{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: Eve', - 'text: fourteen', - 'value: 12', - '9% of Eve', - '15% of Eve', - '3% of Eve', - '6% of Eve', - '17% of Awan', - 'color: #9467bd' + [ + '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() { +describe('icicle pathbar react', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); - it('should show and hide pathbar', function(done) { + it('should show and hide pathbar', function (done) { var fig = { - data: [{ - type: 'icicle', - parents: ['', 'A', 'B', 'C'], - labels: ['A', 'B', 'C', 'D'], - level: 'C' - }], + data: [ + { + type: 'icicle', + parents: ['', 'A', 'B', 'C'], + labels: ['A', 'B', 'C', 'D'], + level: 'C' + } + ], layout: {} }; function _assert(msg, exp) { - return function() { + return function () { var selection = d3SelectAll(SLICES_SELECTOR); var size = selection.size(); @@ -912,17 +1158,17 @@ describe('icicle pathbar react', function() { } 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); + .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); }); }); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index b8cd1bf5981..daea90b327f 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -10,37 +10,36 @@ var Plots = require('../../../src/plots/plots'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); - -describe('Test lib.js:', function() { +describe('Test lib.js:', function () { 'use strict'; - describe('interp() should', function() { - it('return 1.75 as Q1 of [1, 2, 3, 4, 5]:', function() { + describe('interp() should', function () { + it('return 1.75 as Q1 of [1, 2, 3, 4, 5]:', function () { var input = [1, 2, 3, 4, 5]; var res = Lib.interp(input, 0.25); var res0 = 1.75; expect(res).toEqual(res0); }); - it('return 4.25 as Q3 of [1, 2, 3, 4, 5]:', function() { + it('return 4.25 as Q3 of [1, 2, 3, 4, 5]:', function () { var input = [1, 2, 3, 4, 5]; var res = Lib.interp(input, 0.75); var res0 = 4.25; expect(res).toEqual(res0); }); - it('error if second input argument is a string:', function() { + it('error if second input argument is a string:', function () { var input = [1, 2, 3, 4, 5]; - expect(function() { + expect(function () { Lib.interp(input, 'apple'); }).toThrow('n should be a finite number'); }); - it('error if second input argument is a date:', function() { + it('error if second input argument is a date:', function () { var in1 = [1, 2, 3, 4, 5]; var in2 = new Date(2014, 11, 1); - expect(function() { + expect(function () { Lib.interp(in1, in2); }).toThrow('n should be a finite number'); }); - it('return the right boundary on input [-Inf, Inf]:', function() { + it('return the right boundary on input [-Inf, Inf]:', function () { var input = [-Infinity, Infinity]; var res = Lib.interp(input, 1); var res0 = Infinity; @@ -48,13 +47,9 @@ describe('Test lib.js:', function() { }); }); - describe('transposeRagged()', function() { - it('should transpose and return a rectangular array', function() { - var input = [ - [1], - [2, 3, 4], - [5, 6], - [7]]; + describe('transposeRagged()', function () { + it('should transpose and return a rectangular array', function () { + var input = [[1], [2, 3, 4], [5, 6], [7]]; var output = [ [1, 2, 5, 7], [undefined, 3, 6, undefined], @@ -65,37 +60,72 @@ describe('Test lib.js:', function() { }); }); - describe('dot()', function() { + describe('dot()', function () { var dot = Lib.dot; - it('should return null for empty or unequal-length inputs', function() { + it('should return null for empty or unequal-length inputs', function () { expect(dot([], [])).toBeNull(); expect(dot([1], [2, 3])).toBeNull(); }); - it('should dot vectors to a scalar', function() { + it('should dot vectors to a scalar', function () { expect(dot([1, 2, 3], [4, 5, 6])).toEqual(32); }); - it('should dot a vector and a matrix to a vector', function() { - expect(dot([1, 2], [[3, 4], [5, 6]])).toEqual([13, 16]); - expect(dot([[3, 4], [5, 6]], [1, 2])).toEqual([11, 17]); + it('should dot a vector and a matrix to a vector', function () { + expect( + dot( + [1, 2], + [ + [3, 4], + [5, 6] + ] + ) + ).toEqual([13, 16]); + expect( + dot( + [ + [3, 4], + [5, 6] + ], + [1, 2] + ) + ).toEqual([11, 17]); }); - it('should dot two matrices to a matrix', function() { - expect(dot([[1, 2], [3, 4]], [[5, 6], [7, 8]])) - .toEqual([[19, 22], [43, 50]]); + it('should dot two matrices to a matrix', function () { + expect( + dot( + [ + [1, 2], + [3, 4] + ], + [ + [5, 6], + [7, 8] + ] + ) + ).toEqual([ + [19, 22], + [43, 50] + ]); }); }); - describe('aggNums()', function() { + describe('aggNums()', function () { var aggNums = Lib.aggNums; - function summation(a, b) { return a + b; } + function summation(a, b) { + return a + b; + } - it('should work with 1D and 2D inputs and ignore non-numerics', function() { + it('should work with 1D and 2D inputs and ignore non-numerics', function () { var in1D = [1, 2, 3, 4, 'goose!', 5, 6]; - var in2D = [[1, 2, 3], ['', 4], [5, 'hi!', 6]]; + var in2D = [ + [1, 2, 3], + ['', 4], + [5, 'hi!', 6] + ]; expect(aggNums(Math.min, null, in1D)).toEqual(1); expect(aggNums(Math.min, null, in2D)).toEqual(1); @@ -108,132 +138,132 @@ describe('Test lib.js:', function() { }); }); - describe('mean() should', function() { - it('toss out non-numerics (strings)', function() { + describe('mean() should', function () { + it('toss out non-numerics (strings)', function () { var input = [1, 2, 'apple', 'orange']; var res = Lib.mean(input); expect(res).toEqual(1.5); }); - it('toss out non-numerics (NaN)', function() { + it('toss out non-numerics (NaN)', function () { var input = [1, 2, NaN]; var res = Lib.mean(input); expect(res).toEqual(1.5); }); - it('evaluate numbers which are passed around as text strings:', function() { + it('evaluate numbers which are passed around as text strings:', function () { var input = ['1', '2']; var res = Lib.mean(input); expect(res).toEqual(1.5); }); }); - describe('geometricMean() should', function() { - it('toss out non-numerics (strings)', function() { + describe('geometricMean() should', function () { + it('toss out non-numerics (strings)', function () { var input = [1, 2, 'apple', 'orange']; var res = Lib.geometricMean(input); expect(res).toBeCloseTo(1.414, 3); }); - it('toss out non-numerics (NaN)', function() { + it('toss out non-numerics (NaN)', function () { var input = [1, 2, NaN]; var res = Lib.geometricMean(input); expect(res).toBeCloseTo(1.414, 3); }); - it('evaluate numbers which are passed around as text strings:', function() { + it('evaluate numbers which are passed around as text strings:', function () { var input = ['1', '2']; var res = Lib.geometricMean(input); expect(res).toBeCloseTo(1.414, 3); }); }); - describe('midRange() should', function() { - it('should calculate the arithmetic mean of the maximum and minimum value of a given array', function() { + describe('midRange() should', function () { + it('should calculate the arithmetic mean of the maximum and minimum value of a given array', function () { var input = [1, 5.5, 6, 15, 10, 13]; var res = Lib.midRange(input); expect(res).toEqual(8); }); - it('toss out non-numerics (strings)', function() { + it('toss out non-numerics (strings)', function () { var input = [1, 2, 'apple', 'orange']; var res = Lib.midRange(input); expect(res).toEqual(1.5); }); - it('toss out non-numerics (NaN)', function() { + it('toss out non-numerics (NaN)', function () { var input = [1, 2, NaN]; var res = Lib.midRange(input); expect(res).toEqual(1.5); }); - it('should be able to deal with array of length 1', function() { + it('should be able to deal with array of length 1', function () { var input = [10]; var res = Lib.midRange(input); expect(res).toEqual(10); }); - it('should return undefined for an empty array', function() { + it('should return undefined for an empty array', function () { var input = []; var res = Lib.midRange(input); expect(res).toBeUndefined(); }); }); - describe('variance() should', function() { - it('return 0 on input [2, 2, 2, 2, 2]:', function() { + describe('variance() should', function () { + it('return 0 on input [2, 2, 2, 2, 2]:', function () { var input = [2, 2, 2, 2]; var res = Lib.variance(input); expect(res).toEqual(0); }); - it('return 2/3 on input [-1, 0, 1]:', function() { + it('return 2/3 on input [-1, 0, 1]:', function () { var input = [-1, 0, 1]; var res = Lib.variance(input); expect(res).toEqual(2 / 3); }); - it('toss out non-numerics (strings):', function() { + it('toss out non-numerics (strings):', function () { var input = [1, 2, 'apple', 'orange']; var res = Lib.variance(input); expect(res).toEqual(0.25); }); - it('toss out non-numerics (NaN):', function() { + it('toss out non-numerics (NaN):', function () { var input = [1, 2, NaN]; var res = Lib.variance(input); expect(res).toEqual(0.25); }); }); - describe('median() should', function() { - it('return the middle value exactly for odd number of observations:', function() { + describe('median() should', function () { + it('return the middle value exactly for odd number of observations:', function () { var input = [1, 8, 9, 2, 7, 6, 3]; var res = Lib.median(input); expect(res).toEqual(6); }); - it('return the mean of the two middle values for even number of observations', function() { + it('return the mean of the two middle values for even number of observations', function () { var input = [4, 3, 2, 1, 5, 6, 8, 9]; var res = Lib.median(input); expect(res).toEqual(4.5); }); }); - describe('stdev() should', function() { - it('return 0 on input [2, 2, 2, 2, 2]:', function() { + describe('stdev() should', function () { + it('return 0 on input [2, 2, 2, 2, 2]:', function () { var input = [2, 2, 2, 2]; var res = Lib.stdev(input); expect(res).toEqual(0); }); - it('return sqrt(2/3) on input [-1, 0, 1]:', function() { + it('return sqrt(2/3) on input [-1, 0, 1]:', function () { var input = [-1, 0, 1]; var res = Lib.stdev(input); expect(res).toEqual(Math.sqrt(2 / 3)); }); - it('toss out non-numerics (strings):', function() { + it('toss out non-numerics (strings):', function () { var input = [1, 2, 'apple', 'orange']; var res = Lib.stdev(input); expect(res).toEqual(0.5); }); - it('toss out non-numerics (NaN):', function() { + it('toss out non-numerics (NaN):', function () { var input = [1, 2, NaN]; var res = Lib.stdev(input); expect(res).toEqual(0.5); }); }); - describe('smooth()', function() { - it('should not alter the input for FWHM < 1.5', function() { + describe('smooth()', function () { + it('should not alter the input for FWHM < 1.5', function () { var input = [1, 2, 1, 2, 1]; var output = Lib.smooth(input.slice(), 1.49); @@ -244,7 +274,7 @@ describe('Test lib.js:', function() { expect(output).toEqual(input); }); - it('should preserve the length and integral even with multiple bounces', function() { + it('should preserve the length and integral even with multiple bounces', function () { var input = [1, 2, 4, 8, 16, 8, 10, 12]; var output2 = Lib.smooth(input.slice(), 2); var output30 = Lib.smooth(input.slice(), 30); @@ -252,7 +282,7 @@ describe('Test lib.js:', function() { var sum2 = 0; var sum30 = 0; - for(var i = 0; i < input.length; i++) { + for (var i = 0; i < input.length; i++) { sumIn += input[i]; sum2 += output2[i]; sum30 += output30[i]; @@ -264,48 +294,47 @@ describe('Test lib.js:', function() { expect(sum30).toBeCloseTo(sumIn, 6); }); - it('should use a hann window and bounce', function() { + it('should use a hann window and bounce', function () { var input = [0, 0, 0, 7, 0, 0, 0]; var out4 = Lib.smooth(input, 4); var out7 = Lib.smooth(input, 7); var expected4 = [ - 0.2562815664617711, 0.875, 1.4937184335382292, 1.75, - 1.493718433538229, 0.875, 0.25628156646177086 + 0.2562815664617711, 0.875, 1.4937184335382292, 1.75, 1.493718433538229, 0.875, 0.25628156646177086 ]; var expected7 = [1, 1, 1, 1, 1, 1, 1]; var i; - for(i = 0; i < input.length; i++) { + for (i = 0; i < input.length; i++) { expect(out4[i]).toBeCloseTo(expected4[i], 6); expect(out7[i]).toBeCloseTo(expected7[i], 6); } }); }); - describe('nestedProperty', function() { + describe('nestedProperty', function () { var np = Lib.nestedProperty; - it('should access simple objects', function() { - var obj = {a: 'b', c: 'd'}; + it('should access simple objects', function () { + var obj = { a: 'b', c: 'd' }; var propA = np(obj, 'a'); var propB = np(obj, 'b'); expect(propA.get()).toBe('b'); // making and reading nestedProperties shouldn't change anything - expect(obj).toEqual({a: 'b', c: 'd'}); + expect(obj).toEqual({ a: 'b', c: 'd' }); // only setting them should propA.set('cats'); - expect(obj).toEqual({a: 'cats', c: 'd'}); + expect(obj).toEqual({ a: 'cats', c: 'd' }); expect(propA.get()).toBe('cats'); propA.set('b'); expect(propB.get()).toBe(undefined); - expect(obj).toEqual({a: 'b', c: 'd'}); - propB.set({cats: true, dogs: false}); - expect(obj).toEqual({a: 'b', c: 'd', b: {cats: true, dogs: false}}); + expect(obj).toEqual({ a: 'b', c: 'd' }); + propB.set({ cats: true, dogs: false }); + expect(obj).toEqual({ a: 'b', c: 'd', b: { cats: true, dogs: false } }); }); - it('should access arrays', function() { + it('should access arrays', function () { var arr = [1, 2, 3]; var prop1 = np(arr, 1); var prop5 = np(arr, '5'); @@ -330,52 +359,54 @@ describe('Test lib.js:', function() { expect(arr.length).toBe(3); }); - it('should not access whole array elements with index -1', function() { + it('should not access whole array elements with index -1', function () { // for a lot of cases we could make this work, // but deleting the value is a mess, and anyway // we don't need this, it's better just to set the whole // array, ie np(obj, 'arr') - var obj = {arr: [1, 2, 3]}; - expect(function() { np(obj, 'arr[-1]'); }).toThrow('bad property string'); + var obj = { arr: [1, 2, 3] }; + expect(function () { + np(obj, 'arr[-1]'); + }).toThrow('bad property string'); }); - it('should access properties of objects in an array with index -1', function() { - var obj = {arr: [{a: 1}, {a: null}, {b: 3}]}; + it('should access properties of objects in an array with index -1', function () { + var obj = { arr: [{ a: 1 }, { a: null }, { b: 3 }] }; var prop = np(obj, 'arr[-1].a'); expect(prop.get()).toEqual([1, undefined, undefined]); expect(prop.get(true)).toEqual([1, null, undefined]); - expect(obj).toEqual({arr: [{a: 1}, {a: null}, {b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 1 }, { a: null }, { b: 3 }] }); prop.set(5); expect(prop.get()).toBe(5); - expect(obj).toEqual({arr: [{a: 5}, {a: 5}, {a: 5, b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 5 }, { a: 5 }, { a: 5, b: 3 }] }); prop.set(null); expect(prop.get()).toBe(undefined); - expect(obj).toEqual({arr: [{}, {}, {b: 3}]}); + expect(obj).toEqual({ arr: [{}, {}, { b: 3 }] }); prop.set([2, 3, 4]); expect(prop.get()).toEqual([2, 3, 4]); - expect(obj).toEqual({arr: [{a: 2}, {a: 3}, {a: 4, b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 2 }, { a: 3 }, { a: 4, b: 3 }] }); prop.set([6, 7, undefined]); expect(prop.get()).toEqual([6, 7, undefined]); - expect(obj).toEqual({arr: [{a: 6}, {a: 7}, {b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 6 }, { a: 7 }, { b: 3 }] }); // too short an array: wrap around prop.set([9, 10]); expect(prop.get()).toEqual([9, 10, 9]); - expect(obj).toEqual({arr: [{a: 9}, {a: 10}, {a: 9, b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 9 }, { a: 10 }, { a: 9, b: 3 }] }); // too long an array: ignore extras prop.set([11, 12, 13, 14]); expect(prop.get()).toEqual([11, 12, 13]); - expect(obj).toEqual({arr: [{a: 11}, {a: 12}, {a: 13, b: 3}]}); + expect(obj).toEqual({ arr: [{ a: 11 }, { a: 12 }, { a: 13, b: 3 }] }); }); - it('should remove a property only with undefined or null', function() { - var obj = {a: 'b', c: 'd'}; + it('should remove a property only with undefined or null', function () { + var obj = { a: 'b', c: 'd' }; var propA = np(obj, 'a'); var propC = np(obj, 'c'); @@ -387,12 +418,12 @@ describe('Test lib.js:', function() { np(obj, 'b').set(''); propC.set(0); np(obj, 'd').set(NaN); - expect(obj).toEqual({a: false, b: '', c: 0, d: NaN}); + expect(obj).toEqual({ a: false, b: '', c: 0, d: NaN }); }); - it('should not remove arrays or empty objects inside container arrays', function() { + it('should not remove arrays or empty objects inside container arrays', function () { var obj = { - annotations: [{a: [1, 2, 3]}], + annotations: [{ a: [1, 2, 3] }], c: [1, 2, 3], domain: [1, 2], range: [2, 3], @@ -414,7 +445,7 @@ describe('Test lib.js:', function() { // 'a' and 'c' are both potentially data arrays so we need to keep them expect(obj).toEqual({ - annotations: [{a: []}], + annotations: [{ a: [] }], c: [], domain: [], range: [], @@ -422,26 +453,25 @@ describe('Test lib.js:', function() { }); }); - - it('should allow empty object sub-containers', function() { + it('should allow empty object sub-containers', function () { var obj = {}; var prop = np(obj, 'a[1].b.c'); // we never set a value into a[0] so it doesn't even get {} - var expectedArr = [undefined, {b: {c: 'pizza'}}]; + var expectedArr = [undefined, { b: { c: 'pizza' } }]; expect(prop.get()).toBe(undefined); expect(obj).toEqual({}); prop.set('pizza'); - expect(obj).toEqual({a: expectedArr}); + expect(obj).toEqual({ a: expectedArr }); expect(prop.get()).toBe('pizza'); prop.set(null); expect(prop.get()).toBe(undefined); - expect(obj).toEqual({a: [undefined, {b: {}}]}); + expect(obj).toEqual({ a: [undefined, { b: {} }] }); }); - it('does not prune inside `args` arrays', function() { + it('does not prune inside `args` arrays', function () { var obj = {}; var args = np(obj, 'args'); @@ -465,59 +495,61 @@ describe('Test lib.js:', function() { expect(obj.args).toEqual([null]); }); - it('should get empty, and fail on set, with a bad input object', function() { - var badProps = [ - np(5, 'a'), - np(undefined, 'a'), - np('cats', 'a'), - np(true, 'a') - ]; + it('should get empty, and fail on set, with a bad input object', function () { + var badProps = [np(5, 'a'), np(undefined, 'a'), np('cats', 'a'), np(true, 'a')]; function badSetter(i) { - return function() { + return function () { badProps[i].set('cats'); }; } - for(var i = 0; i < badProps.length; i++) { + for (var i = 0; i < badProps.length; i++) { expect(badProps[i].get()).toBe(undefined); expect(badSetter(i)).toThrow('bad container'); } }); - it('should fail on a bad property string', function() { + it('should fail on a bad property string', function () { var badStr = [ - [], {}, false, undefined, null, NaN, Infinity, + [], + {}, + false, + undefined, + null, + NaN, + Infinity, // should guard against prototype pollution - 'x.__proto__.polluted', 'x.y.__proto__.polluted' + 'x.__proto__.polluted', + 'x.y.__proto__.polluted' ]; function badProp(i) { - return function() { + return function () { np({}, badStr[i]); }; } - for(var i = 0; i < badStr.length; i++) { + for (var i = 0; i < badStr.length; i++) { expect(badProp(i)).toThrow('bad property string'); } }); }); - describe('objectFromPath', function() { - it('should return an object', function() { + describe('objectFromPath', function () { + it('should return an object', function () { var obj = Lib.objectFromPath('test', 'object'); expect(obj).toEqual({ test: 'object' }); }); - it('should work for deep objects', function() { + it('should work for deep objects', function () { var obj = Lib.objectFromPath('deep.nested.test', 'object'); - expect(obj).toEqual({ deep: { nested: { test: 'object' }}}); + expect(obj).toEqual({ deep: { nested: { test: 'object' } } }); }); - it('should work for arrays', function() { + it('should work for arrays', function () { var obj = Lib.objectFromPath('nested[2].array', 'object'); expect(Object.keys(obj)).toEqual(['nested']); @@ -526,61 +558,61 @@ describe('Test lib.js:', function() { expect(obj.nested[2]).toEqual({ array: 'object' }); }); - it('should work for any given value', function() { + it('should work for any given value', function () { var obj = Lib.objectFromPath('test.type', { an: 'object' }); - expect(obj).toEqual({ test: { type: { an: 'object' }}}); + expect(obj).toEqual({ test: { type: { an: 'object' } } }); obj = Lib.objectFromPath('test.type', [42]); - expect(obj).toEqual({ test: { type: [42] }}); + expect(obj).toEqual({ test: { type: [42] } }); }); }); - describe('expandObjectPaths', function() { - it('returns the original object', function() { + describe('expandObjectPaths', function () { + it('returns the original object', function () { var x = {}; expect(Lib.expandObjectPaths(x)).toBe(x); }); - it('unpacks top-level paths', function() { - var input = {'marker.color': 'red', 'marker.size': [1, 2, 3]}; - var expected = {marker: {color: 'red', size: [1, 2, 3]}}; + it('unpacks top-level paths', function () { + var input = { 'marker.color': 'red', 'marker.size': [1, 2, 3] }; + var expected = { marker: { color: 'red', size: [1, 2, 3] } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('unpacks recursively', function() { - var input = {'marker.color': {'red.certainty': 'definitely'}}; - var expected = {marker: {color: {red: {certainty: 'definitely'}}}}; + it('unpacks recursively', function () { + var input = { 'marker.color': { 'red.certainty': 'definitely' } }; + var expected = { marker: { color: { red: { certainty: 'definitely' } } } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('unpacks deep paths', function() { - var input = {'foo.bar.baz': 'red'}; - var expected = {foo: {bar: {baz: 'red'}}}; + it('unpacks deep paths', function () { + var input = { 'foo.bar.baz': 'red' }; + var expected = { foo: { bar: { baz: 'red' } } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('unpacks non-top-level deep paths', function() { - var input = {color: {'foo.bar.baz': 'red'}}; - var expected = {color: {foo: {bar: {baz: 'red'}}}}; + it('unpacks non-top-level deep paths', function () { + var input = { color: { 'foo.bar.baz': 'red' } }; + var expected = { color: { foo: { bar: { baz: 'red' } } } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('merges dotted properties into objects', function() { - var input = {marker: {color: 'red'}, 'marker.size': 8}; - var expected = {marker: {color: 'red', size: 8}}; + it('merges dotted properties into objects', function () { + var input = { marker: { color: 'red' }, 'marker.size': 8 }; + var expected = { marker: { color: 'red', size: 8 } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('merges objects into dotted properties', function() { - var input = {'marker.size': 8, marker: {color: 'red'}}; - var expected = {marker: {color: 'red', size: 8}}; + it('merges objects into dotted properties', function () { + var input = { 'marker.size': 8, marker: { color: 'red' } }; + var expected = { marker: { color: 'red', size: 8 } }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('retains the identity of nested objects', function() { - var input = {marker: {size: 8}}; + it('retains the identity of nested objects', function () { + var input = { marker: { size: 8 } }; var origNested = input.marker; var expanded = Lib.expandObjectPaths(input); var newNested = expanded.marker; @@ -589,8 +621,8 @@ describe('Test lib.js:', function() { expect(origNested).toBe(newNested); }); - it('retains the identity of nested arrays', function() { - var input = {'marker.size': [1, 2, 3]}; + it('retains the identity of nested arrays', function () { + var input = { 'marker.size': [1, 2, 3] }; var origArray = input['marker.size']; var expanded = Lib.expandObjectPaths(input); var newArray = expanded.marker.size; @@ -599,64 +631,64 @@ describe('Test lib.js:', function() { expect(origArray).toBe(newArray); }); - it('expands bracketed array notation', function() { - var input = {'marker[1]': {color: 'red'}}; - var expected = {marker: [undefined, {color: 'red'}]}; + it('expands bracketed array notation', function () { + var input = { 'marker[1]': { color: 'red' } }; + var expected = { marker: [undefined, { color: 'red' }] }; expect(Lib.expandObjectPaths(input)).toLooseDeepEqual(expected); }); - it('expands nested arrays', function() { - var input = {'marker[1].range[1]': 5}; - var expected = {marker: [undefined, {range: [undefined, 5]}]}; + it('expands nested arrays', function () { + var input = { 'marker[1].range[1]': 5 }; + var expected = { marker: [undefined, { range: [undefined, 5] }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('expands bracketed array with more nested attributes', function() { - var input = {'marker[1]': {'color.alpha': 2}}; - var expected = {marker: [undefined, {color: {alpha: 2}}]}; + it('expands bracketed array with more nested attributes', function () { + var input = { 'marker[1]': { 'color.alpha': 2 } }; + var expected = { marker: [undefined, { color: { alpha: 2 } }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('expands bracketed array notation without further nesting', function() { - var input = {'marker[1]': 8}; - var expected = {marker: [undefined, 8]}; + it('expands bracketed array notation without further nesting', function () { + var input = { 'marker[1]': 8 }; + var expected = { marker: [undefined, 8] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('expands bracketed array notation with further nesting', function() { - var input = {'marker[1].size': 8}; - var expected = {marker: [undefined, {size: 8}]}; + it('expands bracketed array notation with further nesting', function () { + var input = { 'marker[1].size': 8 }; + var expected = { marker: [undefined, { size: 8 }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('expands bracketed array notation with further nesting', function() { - var input = {'marker[1].size.magnitude': 8}; - var expected = {marker: [undefined, {size: {magnitude: 8}}]}; + it('expands bracketed array notation with further nesting', function () { + var input = { 'marker[1].size.magnitude': 8 }; + var expected = { marker: [undefined, { size: { magnitude: 8 } }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('combines changes with single array nesting', function() { - var input = {'marker[1].foo': 5, 'marker[0].foo': 4}; - var expected = {marker: [{foo: 4}, {foo: 5}]}; + it('combines changes with single array nesting', function () { + var input = { 'marker[1].foo': 5, 'marker[0].foo': 4 }; + var expected = { marker: [{ foo: 4 }, { foo: 5 }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('does not skip over array container set to null values', function() { - var input = {title: 'clear annotations', annotations: null}; - var expected = {title: 'clear annotations', annotations: null}; + it('does not skip over array container set to null values', function () { + var input = { title: 'clear annotations', annotations: null }; + var expected = { title: 'clear annotations', annotations: null }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); - it('expands array containers', function() { - var input = {title: 'clear annotation 1', 'annotations[1]': { title: 'new' }}; - var expected = {title: 'clear annotation 1', annotations: [null, { title: 'new' }]}; + it('expands array containers', function () { + var input = { title: 'clear annotation 1', 'annotations[1]': { title: 'new' } }; + var expected = { title: 'clear annotation 1', annotations: [null, { title: 'new' }] }; var computed = Lib.expandObjectPaths(input); expect(computed).toLooseDeepEqual(expected); }); @@ -698,17 +730,17 @@ describe('Test lib.js:', function() { */ }); - describe('coerce', function() { + describe('coerce', function () { var coerce = Lib.coerce; var out; // TODO: I tested font and string because I changed them, but all the other types need tests still - it('should set a value and return the value it sets', function() { + it('should set a value and return the value it sets', function () { var aVal = 'aaaaah!'; - var cVal = {1: 2, 3: 4}; - var attrs = {a: {valType: 'any', dflt: aVal}, b: {c: {valType: 'any'}}}; - var obj = {b: {c: cVal}}; + var cVal = { 1: 2, 3: 4 }; + var attrs = { a: { valType: 'any', dflt: aVal }, b: { c: { valType: 'any' } } }; + var obj = { b: { c: cVal } }; var outObj = {}; var aOut = coerce(obj, outObj, attrs, 'a'); @@ -720,76 +752,71 @@ describe('Test lib.js:', function() { expect(cOut).toBe(outObj.b.c); }); - describe('data_array valType', function() { + describe('data_array valType', function () { var attrs = { - d: {valType: 'data_array'} + d: { valType: 'data_array' } }; - it('should pass ref to out object (plain array case)', function() { + it('should pass ref to out object (plain array case)', function () { var arr = [1, 2, 3]; - out = coerce({d: arr}, {}, attrs, 'd'); + out = coerce({ d: arr }, {}, attrs, 'd'); expect(out).toBe(arr); }); - it('should pass ref to out object (typed array case)', function() { + it('should pass ref to out object (typed array case)', function () { var arr = new Float32Array([1, 2, 3]); - out = coerce({d: arr}, {}, attrs, 'd'); + out = coerce({ d: arr }, {}, attrs, 'd'); expect(out).toBe(arr); }); }); - describe('string valType', function() { + describe('string valType', function () { var dflt = 'Jabberwock'; var stringAttrs = { - s: {valType: 'string', dflt: dflt}, - noBlank: {valType: 'string', dflt: dflt, noBlank: true} + s: { valType: 'string', dflt: dflt }, + noBlank: { valType: 'string', dflt: dflt, noBlank: true } }; - it('should insert the default if input is missing, or blank with noBlank', function() { + it('should insert the default if input is missing, or blank with noBlank', function () { out = coerce(undefined, {}, stringAttrs, 's'); expect(out).toEqual(dflt); out = coerce({}, {}, stringAttrs, 's'); expect(out).toEqual(dflt); - out = coerce({s: ''}, {}, stringAttrs, 's'); + out = coerce({ s: '' }, {}, stringAttrs, 's'); expect(out).toEqual(''); - out = coerce({noBlank: ''}, {}, stringAttrs, 'noBlank'); + out = coerce({ noBlank: '' }, {}, stringAttrs, 'noBlank'); expect(out).toEqual(dflt); }); - it('should always return a string for any input', function() { - expect(coerce({s: 'a string!!'}, {}, stringAttrs, 's')) - .toEqual('a string!!'); + it('should always return a string for any input', function () { + expect(coerce({ s: 'a string!!' }, {}, stringAttrs, 's')).toEqual('a string!!'); - expect(coerce({s: 42}, {}, stringAttrs, 's')) - .toEqual('42'); + expect(coerce({ s: 42 }, {}, stringAttrs, 's')).toEqual('42'); - expect(coerce({s: [1, 2, 3]}, {}, stringAttrs, 's')) - .toEqual(dflt); + expect(coerce({ s: [1, 2, 3] }, {}, stringAttrs, 's')).toEqual(dflt); - expect(coerce({s: true}, {}, stringAttrs, 's')) - .toEqual(dflt); + expect(coerce({ s: true }, {}, stringAttrs, 's')).toEqual(dflt); - expect(coerce({s: {1: 2}}, {}, stringAttrs, 's')) - .toEqual(dflt); + expect(coerce({ s: { 1: 2 } }, {}, stringAttrs, 's')).toEqual(dflt); }); }); - describe('coerce2', function() { + describe('coerce2', function () { var coerce2 = Lib.coerce2; - it('should set a value and return the value it sets when user input is valid', function() { + it('should set a value and return the value it sets when user input is valid', function () { var colVal = 'red'; var sizeVal = 0; // 0 is valid but falsey var attrs = { testMarker: { - testColor: {valType: 'color', dflt: 'rgba(0, 0, 0, 0)'}, - testSize: {valType: 'number', dflt: 20} + testColor: { valType: 'color', dflt: 'rgba(0, 0, 0, 0)' }, + testSize: { valType: 'number', dflt: 20 } } }; - var obj = {testMarker: {testColor: colVal, testSize: sizeVal}}; + var obj = { testMarker: { testColor: colVal, testSize: sizeVal } }; var outObj = {}; var colOut = coerce2(obj, outObj, attrs, 'testMarker.testColor'); var sizeOut = coerce2(obj, outObj, attrs, 'testMarker.testSize'); @@ -800,16 +827,16 @@ describe('Test lib.js:', function() { expect(sizeOut).toBe(outObj.testMarker.testSize); }); - it('should set and return the default if the user input is not valid', function() { + it('should set and return the default if the user input is not valid', function () { var colVal = 'r'; var sizeVal = 'aaaaah!'; var attrs = { testMarker: { - testColor: {valType: 'color', dflt: 'rgba(0, 0, 0, 0)'}, - testSize: {valType: 'number', dflt: 20} + testColor: { valType: 'color', dflt: 'rgba(0, 0, 0, 0)' }, + testSize: { valType: 'number', dflt: 20 } } }; - var obj = {testMarker: {testColor: colVal, testSize: sizeVal}}; + var obj = { testMarker: { testColor: colVal, testSize: sizeVal } }; var outObj = {}; var colOut = coerce2(obj, outObj, attrs, 'testMarker.testColor'); var sizeOut = coerce2(obj, outObj, attrs, 'testMarker.testSize'); @@ -820,16 +847,16 @@ describe('Test lib.js:', function() { expect(sizeOut).toBe(outObj.testMarker.testSize); }); - it('should return false if there is no user input', function() { + it('should return false if there is no user input', function () { var colVal = null; var sizeVal; // undefined var attrs = { testMarker: { - testColor: {valType: 'color', dflt: 'rgba(0, 0, 0, 0)'}, - testSize: {valType: 'number', dflt: 20} + testColor: { valType: 'color', dflt: 'rgba(0, 0, 0, 0)' }, + testSize: { valType: 'number', dflt: 20 } } }; - var obj = {testMarker: {testColor: colVal, testSize: sizeVal}}; + var obj = { testMarker: { testColor: colVal, testSize: sizeVal } }; var outObj = {}; var colOut = coerce2(obj, outObj, attrs, 'testMarker.testColor'); var sizeOut = coerce2(obj, outObj, attrs, 'testMarker.testSize'); @@ -839,14 +866,11 @@ describe('Test lib.js:', function() { }); }); - describe('info_array valType', function() { + describe('info_array valType', function () { var infoArrayAttrs = { range: { valType: 'info_array', - items: [ - { valType: 'number' }, - { valType: 'number' } - ] + items: [{ valType: 'number' }, { valType: 'number' }] }, domain: { valType: 'info_array', @@ -858,154 +882,181 @@ describe('Test lib.js:', function() { } }; - it('should insert the default if input is missing', function() { - expect(coerce(undefined, {}, infoArrayAttrs, 'domain')) - .toEqual([0, 1]); - expect(coerce(undefined, {}, infoArrayAttrs, 'domain', [0, 0.5])) - .toEqual([0, 0.5]); + it('should insert the default if input is missing', function () { + expect(coerce(undefined, {}, infoArrayAttrs, 'domain')).toEqual([0, 1]); + expect(coerce(undefined, {}, infoArrayAttrs, 'domain', [0, 0.5])).toEqual([0, 0.5]); }); - it('should dive into the items and coerce accordingly', function() { - expect(coerce({range: ['-10', 100]}, {}, infoArrayAttrs, 'range')) - .toEqual([-10, 100]); + it('should dive into the items and coerce accordingly', function () { + expect(coerce({ range: ['-10', 100] }, {}, infoArrayAttrs, 'range')).toEqual([-10, 100]); - expect(coerce({domain: [0, 0.5]}, {}, infoArrayAttrs, 'domain')) - .toEqual([0, 0.5]); + expect(coerce({ domain: [0, 0.5] }, {}, infoArrayAttrs, 'domain')).toEqual([0, 0.5]); - expect(coerce({domain: [-5, 0.5]}, {}, infoArrayAttrs, 'domain')) - .toEqual([0, 0.5]); + expect(coerce({ domain: [-5, 0.5] }, {}, infoArrayAttrs, 'domain')).toEqual([0, 0.5]); - expect(coerce({domain: [0.5, 4.5]}, {}, infoArrayAttrs, 'domain')) - .toEqual([0.5, 1]); + expect(coerce({ domain: [0.5, 4.5] }, {}, infoArrayAttrs, 'domain')).toEqual([0.5, 1]); }); - it('should coerce unexpected input as best as it can', function() { - expect(coerce({range: [12]}, {}, infoArrayAttrs, 'range')) - .toEqual([12]); + it('should coerce unexpected input as best as it can', function () { + expect(coerce({ range: [12] }, {}, infoArrayAttrs, 'range')).toEqual([12]); - expect(coerce({range: [12]}, {}, infoArrayAttrs, 'range', [-1, 20])) - .toEqual([12, 20]); + expect(coerce({ range: [12] }, {}, infoArrayAttrs, 'range', [-1, 20])).toEqual([12, 20]); - expect(coerce({domain: [0.5]}, {}, infoArrayAttrs, 'domain')) - .toEqual([0.5, 1]); + expect(coerce({ domain: [0.5] }, {}, infoArrayAttrs, 'domain')).toEqual([0.5, 1]); - expect(coerce({range: ['-10', 100, 12]}, {}, infoArrayAttrs, 'range')) - .toEqual([-10, 100]); + expect(coerce({ range: ['-10', 100, 12] }, {}, infoArrayAttrs, 'range')).toEqual([-10, 100]); - expect(coerce({domain: [0, 0.5, 1]}, {}, infoArrayAttrs, 'domain')) - .toEqual([0, 0.5]); + expect(coerce({ domain: [0, 0.5, 1] }, {}, infoArrayAttrs, 'domain')).toEqual([0, 0.5]); }); - it('supports bounded freeLength attributes', function() { + it('supports bounded freeLength attributes', function () { var attrs = { x: { valType: 'info_array', freeLength: true, items: [ - {valType: 'integer', min: 0}, - {valType: 'integer', max: -1} + { valType: 'integer', min: 0 }, + { valType: 'integer', max: -1 } ], dflt: [1, -2] - }, + } }; expect(coerce({}, {}, attrs, 'x')).toEqual([1, -2]); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([1, -2]); - expect(coerce({x: [5]}, {}, attrs, 'x')).toEqual([5, -2]); - expect(coerce({x: [-5]}, {}, attrs, 'x')).toEqual([1, -2]); - expect(coerce({x: [5, -5]}, {}, attrs, 'x')).toEqual([5, -5]); - expect(coerce({x: [3, -3, 3]}, {}, attrs, 'x')).toEqual([3, -3]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([1, -2]); + expect(coerce({ x: [5] }, {}, attrs, 'x')).toEqual([5, -2]); + expect(coerce({ x: [-5] }, {}, attrs, 'x')).toEqual([1, -2]); + expect(coerce({ x: [5, -5] }, {}, attrs, 'x')).toEqual([5, -5]); + expect(coerce({ x: [3, -3, 3] }, {}, attrs, 'x')).toEqual([3, -3]); }); - it('supports unbounded freeLength attributes', function() { + it('supports unbounded freeLength attributes', function () { var attrs = { x: { valType: 'info_array', freeLength: true, - items: {valType: 'integer', min: 0, dflt: 1} + items: { valType: 'integer', min: 0, dflt: 1 } } }; expect(coerce({}, {}, attrs, 'x')).toBeUndefined(); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([]); - expect(coerce({x: [3]}, {}, attrs, 'x')).toEqual([3]); - expect(coerce({x: [-3]}, {}, attrs, 'x')).toEqual([1]); - expect(coerce({x: [-1, 4, 'hi', 5]}, {}, attrs, 'x')) - .toEqual([1, 4, 1, 5]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([]); + expect(coerce({ x: [3] }, {}, attrs, 'x')).toEqual([3]); + expect(coerce({ x: [-3] }, {}, attrs, 'x')).toEqual([1]); + expect(coerce({ x: [-1, 4, 'hi', 5] }, {}, attrs, 'x')).toEqual([1, 4, 1, 5]); }); - it('supports 2D fixed-size arrays', function() { + it('supports 2D fixed-size arrays', function () { var attrs = { x: { valType: 'info_array', dimensions: 2, items: [ - [{valType: 'integer', min: 0, max: 2}, {valType: 'integer', min: 3, max: 5}], - [{valType: 'integer', min: 6, max: 8}, {valType: 'integer', min: 9, max: 11}] + [ + { valType: 'integer', min: 0, max: 2 }, + { valType: 'integer', min: 3, max: 5 } + ], + [ + { valType: 'integer', min: 6, max: 8 }, + { valType: 'integer', min: 9, max: 11 } + ] ], - dflt: [[1, 4], [7, 10]] + dflt: [ + [1, 4], + [7, 10] + ] } }; - expect(coerce({}, {}, attrs, 'x')).toEqual([[1, 4], [7, 10]]); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([[1, 4], [7, 10]]); - expect(coerce({x: [[0, 3], [8, 11]]}, {}, attrs, 'x')) - .toEqual([[0, 3], [8, 11]]); - expect(coerce({x: [[10, 5, 10], [6], [1, 2, 3]]}, {}, attrs, 'x')) - .toEqual([[1, 5], [6, 10]]); + expect(coerce({}, {}, attrs, 'x')).toEqual([ + [1, 4], + [7, 10] + ]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([ + [1, 4], + [7, 10] + ]); + expect( + coerce( + { + x: [ + [0, 3], + [8, 11] + ] + }, + {}, + attrs, + 'x' + ) + ).toEqual([ + [0, 3], + [8, 11] + ]); + expect(coerce({ x: [[10, 5, 10], [6], [1, 2, 3]] }, {}, attrs, 'x')).toEqual([ + [1, 5], + [6, 10] + ]); }); - it('supports unbounded 2D freeLength arrays', function() { + it('supports unbounded 2D freeLength arrays', function () { var attrs = { x: { valType: 'info_array', freeLength: true, dimensions: 2, - items: {valType: 'integer', min: 0, dflt: 1} + items: { valType: 'integer', min: 0, dflt: 1 } } }; expect(coerce({}, {}, attrs, 'x')).toBeUndefined(); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([]); - expect(coerce({x: [[], [0], [-1, 2], [5, 'a', 4, 6.6]]}, {}, attrs, 'x')) - .toEqual([[], [0], [1, 2], [5, 1, 4, 1]]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([]); + expect(coerce({ x: [[], [0], [-1, 2], [5, 'a', 4, 6.6]] }, {}, attrs, 'x')).toEqual([ + [], + [0], + [1, 2], + [5, 1, 4, 1] + ]); }); - it('supports dimensions=\'1-2\' with 1D items array', function() { + it("supports dimensions='1-2' with 1D items array", function () { var attrs = { x: { valType: 'info_array', freeLength: true, // in this case only the outer length of 2D is free dimensions: '1-2', items: [ - {valType: 'integer', min: 0, max: 5, dflt: 1}, - {valType: 'integer', min: 10, max: 15, dflt: 11} + { valType: 'integer', min: 0, max: 5, dflt: 1 }, + { valType: 'integer', min: 10, max: 15, dflt: 11 } ] } }; expect(coerce({}, {}, attrs, 'x')).toBeUndefined(); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([1, 11]); - expect(coerce({x: [4, 4, 4]}, {}, attrs, 'x')).toEqual([4, 11]); - expect(coerce({x: [[]]}, {}, attrs, 'x')).toEqual([[1, 11]]); - expect(coerce({x: [[12, 12, 12]]}, {}, attrs, 'x')).toEqual([[1, 12]]); - expect(coerce({x: [[], 4, true]}, {}, attrs, 'x')).toEqual([[1, 11], [1, 11], [1, 11]]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([1, 11]); + expect(coerce({ x: [4, 4, 4] }, {}, attrs, 'x')).toEqual([4, 11]); + expect(coerce({ x: [[]] }, {}, attrs, 'x')).toEqual([[1, 11]]); + expect(coerce({ x: [[12, 12, 12]] }, {}, attrs, 'x')).toEqual([[1, 12]]); + expect(coerce({ x: [[], 4, true] }, {}, attrs, 'x')).toEqual([ + [1, 11], + [1, 11], + [1, 11] + ]); }); - it('supports dimensions=\'1-2\' with single item', function() { + it("supports dimensions='1-2' with single item", function () { var attrs = { x: { valType: 'info_array', freeLength: true, dimensions: '1-2', - items: {valType: 'integer', min: 0, max: 5, dflt: 1} + items: { valType: 'integer', min: 0, max: 5, dflt: 1 } } }; expect(coerce({}, {}, attrs, 'x')).toBeUndefined(); - expect(coerce({x: []}, {}, attrs, 'x')).toEqual([]); - expect(coerce({x: [-3, 3, 6, 'a']}, {}, attrs, 'x')).toEqual([1, 3, 1, 1]); - expect(coerce({x: [[]]}, {}, attrs, 'x')).toEqual([[]]); - expect(coerce({x: [[-1, 0, 10]]}, {}, attrs, 'x')).toEqual([[1, 0, 1]]); - expect(coerce({x: [[], 4, [3], [-1, 10]]}, {}, attrs, 'x')).toEqual([[], [], [3], [1, 1]]); + expect(coerce({ x: [] }, {}, attrs, 'x')).toEqual([]); + expect(coerce({ x: [-3, 3, 6, 'a'] }, {}, attrs, 'x')).toEqual([1, 3, 1, 1]); + expect(coerce({ x: [[]] }, {}, attrs, 'x')).toEqual([[]]); + expect(coerce({ x: [[-1, 0, 10]] }, {}, attrs, 'x')).toEqual([[1, 0, 1]]); + expect(coerce({ x: [[], 4, [3], [-1, 10]] }, {}, attrs, 'x')).toEqual([[], [], [3], [1, 1]]); }); }); - describe('subplotid valtype', function() { + describe('subplotid valtype', function () { var dflt = 'slice'; var idAttrs = { pizza: { @@ -1016,10 +1067,9 @@ describe('Test lib.js:', function() { var goodVals = ['slice', 'slice2', 'slice1492']; - goodVals.forEach(function(goodVal) { - it('should allow "' + goodVal + '"', function() { - expect(coerce({pizza: goodVal}, {}, idAttrs, 'pizza')) - .toEqual(goodVal); + goodVals.forEach(function (goodVal) { + it('should allow "' + goodVal + '"', function () { + expect(coerce({ pizza: goodVal }, {}, idAttrs, 'pizza')).toEqual(goodVal); }); }); @@ -1037,16 +1087,15 @@ describe('Test lib.js:', function() { 'slice01' ]; - badVals.forEach(function(badVal) { - it('should not allow "' + badVal + '"', function() { - expect(coerce({pizza: badVal}, {}, idAttrs, 'pizza')) - .toEqual(dflt); + badVals.forEach(function (badVal) { + it('should not allow "' + badVal + '"', function () { + expect(coerce({ pizza: badVal }, {}, idAttrs, 'pizza')).toEqual(dflt); }); }); }); }); - describe('coerceFont', function() { + describe('coerceFont', function () { var fontAttrs = Plots.fontAttrs({}); var extendFlat = Lib.extendFlat; var coerceFont = Lib.coerceFont; @@ -1060,20 +1109,20 @@ describe('Test lib.js:', function() { variant: 'small-caps', textcase: 'word caps', lineposition: 'under', - shadow: 'auto', + shadow: 'auto' }; var attributes = { fontWithDefault: { - family: extendFlat({}, fontAttrs.family, {dflt: defaultFont.family}), - size: extendFlat({}, fontAttrs.size, {dflt: defaultFont.size}), - color: extendFlat({}, fontAttrs.color, {dflt: defaultFont.color}), - weight: extendFlat({}, fontAttrs.weight, {dflt: defaultFont.weight}), - style: extendFlat({}, fontAttrs.style, {dflt: defaultFont.style}), - variant: extendFlat({}, fontAttrs.variant, {dflt: defaultFont.variant}), - textcase: extendFlat({}, fontAttrs.textcase, {dflt: defaultFont.textcase}), - lineposition: extendFlat({}, fontAttrs.lineposition, {dflt: defaultFont.lineposition}), - shadow: extendFlat({}, fontAttrs.shadow, {dflt: defaultFont.shadow}) + family: extendFlat({}, fontAttrs.family, { dflt: defaultFont.family }), + size: extendFlat({}, fontAttrs.size, { dflt: defaultFont.size }), + color: extendFlat({}, fontAttrs.color, { dflt: defaultFont.color }), + weight: extendFlat({}, fontAttrs.weight, { dflt: defaultFont.weight }), + style: extendFlat({}, fontAttrs.style, { dflt: defaultFont.style }), + variant: extendFlat({}, fontAttrs.variant, { dflt: defaultFont.variant }), + textcase: extendFlat({}, fontAttrs.textcase, { dflt: defaultFont.textcase }), + lineposition: extendFlat({}, fontAttrs.lineposition, { dflt: defaultFont.lineposition }), + shadow: extendFlat({}, fontAttrs.shadow, { dflt: defaultFont.shadow }) }, fontNoDefault: fontAttrs }; @@ -1084,21 +1133,18 @@ describe('Test lib.js:', function() { return Lib.coerce(containerIn, {}, attributes, attr, dflt); } - it('should insert the full default if no or empty input', function() { + it('should insert the full default if no or empty input', function () { containerIn = undefined; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual(defaultFont); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual(defaultFont); containerIn = {}; - expect(coerceFont(coerce, 'fontNoDefault', defaultFont)) - .toEqual(defaultFont); + expect(coerceFont(coerce, 'fontNoDefault', defaultFont)).toEqual(defaultFont); - containerIn = {fontWithDefault: {}}; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual(defaultFont); + containerIn = { fontWithDefault: {} }; + expect(coerceFont(coerce, 'fontWithDefault')).toEqual(defaultFont); }); - it('should fill in defaults for bad inputs', function() { + it('should fill in defaults for bad inputs', function () { containerIn = { fontWithDefault: { family: '', @@ -1109,14 +1155,13 @@ describe('Test lib.js:', function() { variant: false, textcase: true, lineposition: false, - shadow: false, + shadow: false } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual(defaultFont); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual(defaultFont); }); - it('should pass through individual valid pieces', function() { + it('should pass through individual valid pieces', function () { var goodFamily = 'A fish'; // for now any non-blank string is OK var badFamily = 42; var goodSize = 123.456; @@ -1150,18 +1195,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: goodFamily, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: goodFamily, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1176,18 +1220,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: goodSize, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: goodSize, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1202,18 +1245,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: goodColor, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: goodColor, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1228,18 +1270,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: goodWeight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: goodWeight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1254,18 +1295,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: goodStyle, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: goodStyle, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1280,18 +1320,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: goodVariant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: goodVariant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1306,18 +1345,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: goodTextcase, - lineposition: defaultFont.lineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: goodTextcase, + lineposition: defaultFont.lineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1332,18 +1370,17 @@ describe('Test lib.js:', function() { shadow: badShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: goodLineposition, - shadow: defaultFont.shadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: goodLineposition, + shadow: defaultFont.shadow + }); containerIn = { fontWithDefault: { @@ -1358,23 +1395,22 @@ describe('Test lib.js:', function() { shadow: goodShadow } }; - expect(coerceFont(coerce, 'fontWithDefault')) - .toEqual({ - family: defaultFont.family, - size: defaultFont.size, - color: defaultFont.color, - weight: defaultFont.weight, - style: defaultFont.style, - variant: defaultFont.variant, - textcase: defaultFont.textcase, - lineposition: defaultFont.lineposition, - shadow: goodShadow - }); + expect(coerceFont(coerce, 'fontWithDefault')).toEqual({ + family: defaultFont.family, + size: defaultFont.size, + color: defaultFont.color, + weight: defaultFont.weight, + style: defaultFont.style, + variant: defaultFont.variant, + textcase: defaultFont.textcase, + lineposition: defaultFont.lineposition, + shadow: goodShadow + }); }); }); - describe('init2dArray', function() { - it('should initialize a 2d array with the correct dimenstions', function() { + describe('init2dArray', function () { + it('should initialize a 2d array with the correct dimenstions', function () { var array = Lib.init2dArray(4, 5); expect(array.length).toEqual(4); expect(array[0].length).toEqual(5); @@ -1382,20 +1418,20 @@ describe('Test lib.js:', function() { }); }); - describe('validate', function() { + describe('validate', function () { function assert(shouldPass, shouldFail, valObject) { - shouldPass.forEach(function(v) { + shouldPass.forEach(function (v) { var res = Lib.validate(v, valObject); expect(res).toBe(true, JSON.stringify(v) + ' should pass'); }); - shouldFail.forEach(function(v) { + shouldFail.forEach(function (v) { var res = Lib.validate(v, valObject); expect(res).toBe(false, JSON.stringify(v) + ' should fail'); }); } - it('should work for valType \'data_array\' where', function() { + it("should work for valType 'data_array' where", function () { var shouldPass = [[20], []]; var shouldFail = ['a', {}, 20, undefined, null]; @@ -1409,7 +1445,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'enumerated\' where', function() { + it("should work for valType 'enumerated' where", function () { assert(['a', 'b'], ['c', 1, null, undefined, ''], { valType: 'enumerated', values: ['a', 'b'], @@ -1437,7 +1473,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'boolean\' where', function() { + it("should work for valType 'boolean' where", function () { var shouldPass = [true, false]; var shouldFail = ['a', 1, {}, [], null, undefined, '']; @@ -1452,7 +1488,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'number\' where', function() { + it("should work for valType 'number' where", function () { var shouldPass = [20, '20', 1e6]; var shouldFail = ['a', [], {}, null, undefined, '']; @@ -1479,7 +1515,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'integer\' where', function() { + it("should work for valType 'integer' where", function () { assert([1, 2, '3', '4'], ['a', 1.321321, {}, [], null, 2 / 3, undefined, null], { valType: 'integer', dflt: 1 @@ -1492,7 +1528,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'string\' where', function() { + it("should work for valType 'string' where", function () { var date = new Date(2016, 1, 1); assert(['3', '4', 'a', 3, 1.2113, ''], [undefined, {}, [], null, date, false], { @@ -1520,7 +1556,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'color\' where', function() { + it("should work for valType 'color' where", function () { var shouldPass = ['red', '#d3d3d3', 'rgba(0,255,255,0.1)']; var shouldFail = [1, {}, [], 'rgq(233,122,332,1)', null, undefined]; @@ -1529,7 +1565,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'colorlist\' where', function() { + it("should work for valType 'colorlist' where", function () { var shouldPass = [['red'], ['#ffffff'], ['rgba(0,0,0,1)'], ['red', 'green', 'blue']]; var shouldFail = [1, null, undefined, {}, [], 'red', ['red', null]]; @@ -1538,11 +1574,17 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'colorscale\' where', function() { - var good = [ [0, 'red'], [1, 'blue'] ]; - var bad = [ [0.1, 'red'], [1, 'blue'] ]; - var bad2 = [ [0], [1] ]; - var bad3 = [ ['red'], ['blue']]; + it("should work for valType 'colorscale' where", function () { + var good = [ + [0, 'red'], + [1, 'blue'] + ]; + var bad = [ + [0.1, 'red'], + [1, 'blue'] + ]; + var bad2 = [[0], [1]]; + var bad3 = [['red'], ['blue']]; var bad4 = ['red', 'blue']; var shouldPass = ['Viridis', 'Greens', good]; @@ -1553,7 +1595,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'angle\' where', function() { + it("should work for valType 'angle' where", function () { var shouldPass = ['auto', '120', 270]; var shouldFail = [{}, [], 'red', null, undefined, '']; @@ -1563,7 +1605,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'subplotid\' where', function() { + it("should work for valType 'subplotid' where", function () { var shouldPass = ['sp', 'sp4', 'sp10']; var shouldFail = [{}, [], 'sp1', 'sp0', 'spee1', null, undefined, true]; @@ -1573,7 +1615,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'flaglist\' where', function() { + it("should work for valType 'flaglist' where", function () { var shouldPass = ['a', 'b', 'a+b', 'b+a', 'c']; var shouldFail = [{}, [], 'red', null, undefined, '', 'a + b']; @@ -1584,7 +1626,7 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'any\' where', function() { + it("should work for valType 'any' where", function () { var shouldPass = ['', '120', null, false, {}, []]; var shouldFail = [undefined]; @@ -1593,25 +1635,29 @@ describe('Test lib.js:', function() { }); }); - it('should work for valType \'info_array\' where', function() { - var shouldPass = [[1, 2], [-20, '20']]; - var shouldFail = [ - {}, [], [10], [null, 10], ['aads', null], - 'red', null, undefined, '', - [1, 10, null] + it("should work for valType 'info_array' where", function () { + var shouldPass = [ + [1, 2], + [-20, '20'] ]; + var shouldFail = [{}, [], [10], [null, 10], ['aads', null], 'red', null, undefined, '', [1, 10, null]]; assert(shouldPass, shouldFail, { valType: 'info_array', - items: [{ - valType: 'number', dflt: -20 - }, { - valType: 'number', dflt: 20 - }] + items: [ + { + valType: 'number', + dflt: -20 + }, + { + valType: 'number', + dflt: 20 + } + ] }); }); - it('should work for valType \'info_array\' (freeLength case)', function() { + it("should work for valType 'info_array' (freeLength case)", function () { var shouldPass = [ ['marker.color', 'red'], [{ 'marker.color': 'red' }, [1, 2]] @@ -1624,31 +1670,35 @@ describe('Test lib.js:', function() { assert(shouldPass, shouldFail, { valType: 'info_array', freeLength: true, - items: [{ - valType: 'any' - }, { - valType: 'any' - }, { - valType: 'number' - }] + items: [ + { + valType: 'any' + }, + { + valType: 'any' + }, + { + valType: 'number' + } + ] }); }); }); - describe('setCursor', function() { - beforeEach(function() { + describe('setCursor', function () { + beforeEach(function () { this.el3 = d3Select(createGraphDiv()); }); afterEach(destroyGraphDiv); - it('should assign cursor- class', function() { + it('should assign cursor- class', function () { setCursor(this.el3, 'one'); expect(this.el3.attr('class')).toEqual('cursor-one'); }); - it('should assign cursor- class while present non-cursor- classes', function() { + it('should assign cursor- class while present non-cursor- classes', function () { this.el3.classed('one', true); this.el3.classed('two', true); this.el3.classed('three', true); @@ -1657,14 +1707,14 @@ describe('Test lib.js:', function() { expect(this.el3.attr('class')).toEqual('one two three cursor-one'); }); - it('should update class from one cursor- class to another', function() { + it('should update class from one cursor- class to another', function () { this.el3.classed('cursor-one', true); setCursor(this.el3, 'two'); expect(this.el3.attr('class')).toEqual('cursor-two'); }); - it('should update multiple cursor- classes', function() { + it('should update multiple cursor- classes', function () { this.el3.classed('cursor-one', true); this.el3.classed('cursor-two', true); this.el3.classed('cursor-three', true); @@ -1673,7 +1723,7 @@ describe('Test lib.js:', function() { expect(this.el3.attr('class')).toEqual('cursor-four'); }); - it('should remove cursor- if no new class is given', function() { + it('should remove cursor- if no new class is given', function () { this.el3.classed('cursor-one', true); this.el3.classed('cursor-two', true); this.el3.classed('cursor-three', true); @@ -1683,18 +1733,15 @@ describe('Test lib.js:', function() { }); }); - describe('overrideCursor', function() { - beforeEach(function() { + describe('overrideCursor', function () { + beforeEach(function () { this.el3 = d3Select(createGraphDiv()); }); afterEach(destroyGraphDiv); - it('should apply the new cursor(s) and revert to the original when removed', function() { - this.el3 - .classed('cursor-before', true) - .classed('not-a-cursor', true) - .classed('another', true); + it('should apply the new cursor(s) and revert to the original when removed', function () { + this.el3.classed('cursor-before', true).classed('not-a-cursor', true).classed('another', true); overrideCursor(this.el3, 'after'); expect(this.el3.attr('class')).toBe('not-a-cursor another cursor-after'); @@ -1706,10 +1753,8 @@ describe('Test lib.js:', function() { expect(this.el3.attr('class')).toBe('not-a-cursor another cursor-before'); }); - it('should apply the new cursor(s) and revert to the none when removed', function() { - this.el3 - .classed('not-a-cursor', true) - .classed('another', true); + it('should apply the new cursor(s) and revert to the none when removed', function () { + this.el3.classed('not-a-cursor', true).classed('another', true); overrideCursor(this.el3, 'after'); expect(this.el3.attr('class')).toBe('not-a-cursor another cursor-after'); @@ -1721,10 +1766,8 @@ describe('Test lib.js:', function() { expect(this.el3.attr('class')).toBe('not-a-cursor another'); }); - it('should do nothing if no existing or new override is present', function() { - this.el3 - .classed('cursor-before', true) - .classed('not-a-cursor', true); + it('should do nothing if no existing or new override is present', function () { + this.el3.classed('cursor-before', true).classed('not-a-cursor', true); overrideCursor(this.el3); @@ -1732,20 +1775,20 @@ describe('Test lib.js:', function() { }); }); - describe('pushUnique', function() { - beforeEach(function() { + describe('pushUnique', function () { + beforeEach(function () { this.obj = { a: 'A' }; this.array = ['a', 'b', 'c', this.obj]; }); - it('should fill new items in array', function() { + it('should fill new items in array', function () { var out = Lib.pushUnique(this.array, 'd'); expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }, 'd']); expect(this.array).toBe(out); }); - it('should ignore falsy items except 0', function() { + it('should ignore falsy items except 0', function () { Lib.pushUnique(this.array, false); expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }]); @@ -1762,7 +1805,7 @@ describe('Test lib.js:', function() { expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }, 0]); }); - it('should ignore item already in array', function() { + it('should ignore item already in array', function () { Lib.pushUnique(this.array, 'a'); expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }]); @@ -1770,7 +1813,7 @@ describe('Test lib.js:', function() { expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }]); }); - it('should recognize matching RegExps', function() { + it('should recognize matching RegExps', function () { expect(this.array).toEqual(['a', 'b', 'c', { a: 'A' }]); var r1 = /a/; @@ -1783,85 +1826,78 @@ describe('Test lib.js:', function() { }); }); - describe('filterUnique', function() { - it('should return array containing unique values', function() { - expect( - Lib.filterUnique(['a', 'a', 'b', 'b']) - ) - .toEqual(['a', 'b']); + describe('filterUnique', function () { + it('should return array containing unique values', function () { + expect(Lib.filterUnique(['a', 'a', 'b', 'b'])).toEqual(['a', 'b']); - expect( - Lib.filterUnique(['1', ['1'], 1]) - ) - .toEqual(['1']); + expect(Lib.filterUnique(['1', ['1'], 1])).toEqual(['1']); - expect( - Lib.filterUnique([1, '1', [1]]) - ) - .toEqual([1]); + expect(Lib.filterUnique([1, '1', [1]])).toEqual([1]); - expect( - Lib.filterUnique([ { a: 1 }, { b: 2 }]) - ) - .toEqual([{ a: 1 }]); + expect(Lib.filterUnique([{ a: 1 }, { b: 2 }])).toEqual([{ a: 1 }]); - expect( - Lib.filterUnique([null, undefined, null, null, undefined]) - ) - .toEqual([null, undefined]); + expect(Lib.filterUnique([null, undefined, null, null, undefined])).toEqual([null, undefined]); }); }); - describe('numSeparate', function() { - it('should work on numbers and strings', function() { + describe('numSeparate', function () { + it('should work on numbers and strings', function () { expect(Lib.numSeparate(12345.67, '.,')).toBe('12,345.67'); expect(Lib.numSeparate('12345.67', '.,')).toBe('12,345.67'); }); - it('should ignore years', function() { + it('should ignore years', function () { expect(Lib.numSeparate(2016, '.,')).toBe('2016'); }); - it('should work even for 4-digit integer if third argument is true', function() { + it('should work even for 4-digit integer if third argument is true', function () { expect(Lib.numSeparate(3000, '.,', true)).toBe('3,000'); }); - it('should work for multiple thousands', function() { + it('should work for multiple thousands', function () { expect(Lib.numSeparate(1000000000, '.,')).toBe('1,000,000,000'); }); - it('should work when there\'s only one separator', function() { + it("should work when there's only one separator", function () { expect(Lib.numSeparate(12.34, '|')).toBe('12|34'); expect(Lib.numSeparate(1234.56, '|')).toBe('1234|56'); }); - it('should throw an error when no separator is provided', function() { - expect(function() { + it('should throw an error when no separator is provided', function () { + expect(function () { Lib.numSeparate(1234); }).toThrowError('Separator string required for formatting!'); - expect(function() { + expect(function () { Lib.numSeparate(1234, ''); }).toThrowError('Separator string required for formatting!'); }); }); - describe('cleanNumber', function() { - it('should return finite numbers untouched', function() { + describe('cleanNumber', function () { + it('should return finite numbers untouched', function () { var vals = [ - 0, 1, 2, 1234.567, -1, -100, -999.999, - Number.MAX_VALUE, Number.MIN_VALUE, - -Number.MAX_VALUE, -Number.MIN_VALUE + 0, + 1, + 2, + 1234.567, + -1, + -100, + -999.999, + Number.MAX_VALUE, + Number.MIN_VALUE, + -Number.MAX_VALUE, + -Number.MIN_VALUE ]; vals.push(Number.EPSILON, -Number.EPSILON); - vals.forEach(function(v) { + vals.forEach(function (v) { expect(Lib.cleanNumber(v)).toBe(v); }); }); - it('should accept number strings with arbitrary cruft on the outside', function() { + it('should accept number strings with arbitrary cruft on the outside', function () { [ ['0', 0], ['1', 1], @@ -1869,46 +1905,61 @@ describe('Test lib.js:', function() { ['-100.001', -100.001], [' $4.325 #%\t', 4.325], [' " #1" ', 1], - [' \'\n \r -9.2e7 \t\' ', -9.2e7], + [" '\n \r -9.2e7 \t' ", -9.2e7], ['1,690,000', 1690000], ['1 690 000', 1690000], ['2 2', 22], ['$5,162,000.00', 5162000], - [' $1,410,000.00 ', 1410000], - ].forEach(function(v) { + [' $1,410,000.00 ', 1410000] + ].forEach(function (v) { expect(Lib.cleanNumber(v[0])).toBe(v[1], v[0]); }); }); - it('should not accept other objects or cruft in the middle', function() { + it('should not accept other objects or cruft in the middle', function () { [ - NaN, Infinity, -Infinity, null, undefined, new Date(), '', - ' ', '\t', '2\t2', '2%2', '2$2', {1: 2}, [1], ['1'], {}, [] - ].forEach(function(v) { + NaN, + Infinity, + -Infinity, + null, + undefined, + new Date(), + '', + ' ', + '\t', + '2\t2', + '2%2', + '2$2', + { 1: 2 }, + [1], + ['1'], + {}, + [] + ].forEach(function (v) { expect(Lib.cleanNumber(v)).toBeUndefined(v); }); }); }); - describe('isPlotDiv', function() { - it('should work on plain objects', function() { + describe('isPlotDiv', function () { + it('should work on plain objects', function () { expect(Lib.isPlotDiv({})).toBe(false); }); }); - describe('isD3Selection', function() { + describe('isD3Selection', function () { var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); - afterEach(function() { + afterEach(function () { destroyGraphDiv(); Plotly.setPlotConfig({ queueLength: 0 }); }); - it('recognizes real and duck typed selections', function() { + it('recognizes real and duck typed selections', function () { var yesSelections = [ d3Select(gd), // this is what got us into trouble actually - d3 selections can @@ -1917,36 +1968,29 @@ describe('Test lib.js:', function() { d3Select(1) ]; - yesSelections.forEach(function(v) { + yesSelections.forEach(function (v) { expect(Lib.isD3Selection(v)).toBe(true, v); }); }); - it('rejects non-selections', function() { - var notSelections = [ - 1, - 'path', - [1, 2], - [[1, 2]], - {classed: 1}, - gd - ]; + it('rejects non-selections', function () { + var notSelections = [1, 'path', [1, 2], [[1, 2]], { classed: 1 }, gd]; - notSelections.forEach(function(v) { + notSelections.forEach(function (v) { expect(Lib.isD3Selection(v)).toBe(false, v); }); }); }); - describe('loggers', function() { + describe('loggers', function () { var stashConsole; var stashLogLevel; var stashOnGraphLogLevel; function consoleFn(name, messages) { - var out = function() { + var out = function () { var args = []; - for(var i = 0; i < arguments.length; i++) args.push(arguments[i]); + for (var i = 0; i < arguments.length; i++) args.push(arguments[i]); messages.push([name, args]); }; @@ -1966,35 +2010,35 @@ describe('Test lib.js:', function() { return out; } - beforeEach(function() { + beforeEach(function () { stashConsole = window.console; stashLogLevel = config.logging; stashOnGraphLogLevel = config.notifyOnLogging; }); - afterEach(function() { + afterEach(function () { window.console = stashConsole; config.logging = stashLogLevel; config.notifyOnLogging = stashOnGraphLogLevel; }); - it('emits one console message', function() { - var c = window.console = mockConsole(); + it('emits one console message', function () { + var c = (window.console = mockConsole()); config.logging = 2; Lib.log('tick', 'tock', 'tick', 'tock', 1); - Lib.warn('I\'m', 'a', 'little', 'cuckoo', 'clock', [1, 2]); - Lib.error('cuckoo!', 'cuckoo!!!', {a: 1, b: 2}); + Lib.warn("I'm", 'a', 'little', 'cuckoo', 'clock', [1, 2]); + Lib.error('cuckoo!', 'cuckoo!!!', { a: 1, b: 2 }); expect(c.MESSAGES).toEqual([ ['trace', ['LOG:', 'tick', 'tock', 'tick', 'tock', 1]], - ['trace', ['WARN:', 'I\'m', 'a', 'little', 'cuckoo', 'clock', [1, 2]]], - ['error', ['ERROR:', 'cuckoo!', 'cuckoo!!!', {a: 1, b: 2}]] + ['trace', ['WARN:', "I'm", 'a', 'little', 'cuckoo', 'clock', [1, 2]]], + ['error', ['ERROR:', 'cuckoo!', 'cuckoo!!!', { a: 1, b: 2 }]] ]); }); - it('omits .log at log level 1', function() { - var c = window.console = mockConsole(); + it('omits .log at log level 1', function () { + var c = (window.console = mockConsole()); config.logging = 1; Lib.log(1); @@ -2007,8 +2051,8 @@ describe('Test lib.js:', function() { ]); }); - it('logs nothing at log level 0', function() { - var c = window.console = mockConsole(); + it('logs nothing at log level 0', function () { + var c = (window.console = mockConsole()); config.logging = 0; Lib.log(1); @@ -2018,11 +2062,11 @@ describe('Test lib.js:', function() { expect(c.MESSAGES).toEqual([]); }); - describe('should log message in notifier div in accordance notifyOnLogging config option', function() { + describe('should log message in notifier div in accordance notifyOnLogging config option', function () { var query = '.notifier-note'; - beforeEach(function(done) { - d3SelectAll(query).each(function() { + beforeEach(function (done) { + d3SelectAll(query).each(function () { d3Select(this).select('button').node().click(); }); setTimeout(done, 1000); @@ -2040,32 +2084,32 @@ describe('Test lib.js:', function() { expect(notes.size()).toBe(exp.length, '# of notifier notes'); var actual = []; - notes.each(function() { + notes.each(function () { actual.push(d3Select(this).select('p').text()); }); expect(actual).toEqual(exp); } - it('with level 2', function() { + it('with level 2', function () { config.notifyOnLogging = 2; _run(['log', 'warn', 'error!']); }); - it('with level 1', function() { + it('with level 1', function () { config.notifyOnLogging = 1; _run(['warn', 'error!']); }); - it('with level 0', function() { + it('with level 0', function () { config.notifyOnLogging = 0; _run([]); }); }); }); - describe('keyedContainer', function() { - describe('with no existing container', function() { - it('creates a named container only when setting a value', function() { + describe('keyedContainer', function () { + describe('with no existing container', function () { + it('creates a named container only when setting a value', function () { var container = {}; var kCont = Lib.keyedContainer(container, 'styles'); @@ -2077,15 +2121,15 @@ describe('Test lib.js:', function() { kCont.set('name1', 'value1'); expect(container).toEqual({ - styles: [{name: 'name1', value: 'value1'}] + styles: [{ name: 'name1', value: 'value1' }] }); expect(kCont.get('name1')).toBe('value1'); expect(kCont.get('name2')).toBeUndefined(); }); }); - describe('with no path', function() { - it('adds elements just like when there is a path', function() { + describe('with no path', function () { + it('adds elements just like when there is a path', function () { var arr = []; var kCont = Lib.keyedContainer(arr); @@ -2096,12 +2140,12 @@ describe('Test lib.js:', function() { expect(arr).toEqual([]); kCont.set('name1', 'value1'); - expect(arr).toEqual([{name: 'name1', value: 'value1'}]); + expect(arr).toEqual([{ name: 'name1', value: 'value1' }]); expect(kCont.get('name1')).toBe('value1'); expect(kCont.get('name2')).toBeUndefined(); }); - it('does not barf if the array is missing', function() { + it('does not barf if the array is missing', function () { var kCont = Lib.keyedContainer(); kCont.set('name1', null); kCont.set('name1', 'value1'); @@ -2109,43 +2153,47 @@ describe('Test lib.js:', function() { }); }); - describe('with a filled container', function() { + describe('with a filled container', function () { var container, carr; - beforeEach(function() { + beforeEach(function () { container = { styles: [ - {name: 'name1', value: 'value1'}, - {name: 'name2', value: 'value2'} + { name: 'name1', value: 'value1' }, + { name: 'name2', value: 'value2' } ] }; carr = Lib.keyedContainer(container, 'styles'); }); - describe('modifying the object', function() { - it('adds and updates items', function() { + describe('modifying the object', function () { + it('adds and updates items', function () { carr.set('foo', 'bar'); carr.set('name1', 'value3'); - expect(container).toEqual({styles: [ - {name: 'name1', value: 'value3'}, - {name: 'name2', value: 'value2'}, - {name: 'foo', value: 'bar'} - ]}); + expect(container).toEqual({ + styles: [ + { name: 'name1', value: 'value3' }, + { name: 'name2', value: 'value2' }, + { name: 'foo', value: 'bar' } + ] + }); }); - it('removes items', function() { + it('removes items', function () { carr.set('foo', 'bar'); carr.remove('name1'); - expect(container).toEqual({styles: [ - {name: 'name2', value: 'value2'}, - {name: 'foo', value: 'bar'} - ]}); + expect(container).toEqual({ + styles: [ + { name: 'name2', value: 'value2' }, + { name: 'foo', value: 'bar' } + ] + }); }); - it('gets items', function() { + it('gets items', function () { expect(carr.get('foo')).toBe(undefined); expect(carr.get('name1')).toEqual('value1'); @@ -2158,18 +2206,20 @@ describe('Test lib.js:', function() { expect(carr.get('name3')).toEqual('value2'); }); - it('renames items', function() { + it('renames items', function () { carr.rename('name2', 'name3'); - expect(container).toEqual({styles: [ - {name: 'name1', value: 'value1'}, - {name: 'name3', value: 'value2'} - ]}); + expect(container).toEqual({ + styles: [ + { name: 'name1', value: 'value1' }, + { name: 'name3', value: 'value2' } + ] + }); }); }); - describe('constructing updates', function() { - it('constructs updates for addition and modification', function() { + describe('constructing updates', function () { + it('constructs updates for addition and modification', function () { carr.set('foo', 'bar'); carr.set('name1', 'value3'); @@ -2180,7 +2230,7 @@ describe('Test lib.js:', function() { }); }); - it('constructs updates for removal', function() { + it('constructs updates for removal', function () { carr.set('foo', 'bar'); carr.remove('name1'); @@ -2193,7 +2243,7 @@ describe('Test lib.js:', function() { }); }); - it('constructs updates for renaming', function() { + it('constructs updates for renaming', function () { carr.rename('name2', 'name3'); expect(carr.constructUpdate()).toEqual({ @@ -2203,12 +2253,14 @@ describe('Test lib.js:', function() { }); }); - describe('with custom named properties', function() { - it('performs all of the operations', function() { - var container = {styles: [ - {foo: 'name1', bar: 'value1'}, - {foo: 'name2', bar: 'value2'} - ]}; + describe('with custom named properties', function () { + it('performs all of the operations', function () { + var container = { + styles: [ + { foo: 'name1', bar: 'value1' }, + { foo: 'name2', bar: 'value2' } + ] + }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar'); @@ -2216,11 +2268,13 @@ describe('Test lib.js:', function() { carr.set('name3', 'value3'); - expect(container).toEqual({styles: [ - {foo: 'name1', bar: 'value1'}, - {foo: 'name2', bar: 'value2'}, - {foo: 'name3', bar: 'value3'} - ]}); + expect(container).toEqual({ + styles: [ + { foo: 'name1', bar: 'value1' }, + { foo: 'name2', bar: 'value2' }, + { foo: 'name3', bar: 'value3' } + ] + }); expect(carr.constructUpdate()).toEqual({ 'styles[2].foo': 'name3', @@ -2231,10 +2285,12 @@ describe('Test lib.js:', function() { carr.remove('name2'); - expect(container).toEqual({styles: [ - {foo: 'name1', bar: 'value1'}, - {foo: 'name3', bar: 'value3'} - ]}); + expect(container).toEqual({ + styles: [ + { foo: 'name1', bar: 'value1' }, + { foo: 'name3', bar: 'value3' } + ] + }); expect(carr.constructUpdate()).toEqual({ 'styles[1].foo': 'name3', @@ -2246,10 +2302,12 @@ describe('Test lib.js:', function() { carr.rename('name1', 'name2'); - expect(container).toEqual({styles: [ - {foo: 'name2', bar: 'value1'}, - {foo: 'name3', bar: 'value3'} - ]}); + expect(container).toEqual({ + styles: [ + { foo: 'name2', bar: 'value1' }, + { foo: 'name3', bar: 'value3' } + ] + }); expect(carr.constructUpdate()).toEqual({ 'styles[0].foo': 'name2', @@ -2262,10 +2320,12 @@ describe('Test lib.js:', function() { carr.set('name2', 'value2'); - expect(container).toEqual({styles: [ - {foo: 'name2', bar: 'value2'}, - {foo: 'name3', bar: 'value3'} - ]}); + expect(container).toEqual({ + styles: [ + { foo: 'name2', bar: 'value2' }, + { foo: 'name3', bar: 'value3' } + ] + }); expect(carr.constructUpdate()).toEqual({ 'styles[0].foo': 'name2', @@ -2277,42 +2337,40 @@ describe('Test lib.js:', function() { }); }); - describe('with nested valueName', function() { - it('gets and sets values', function() { - var container = {styles: []}; + describe('with nested valueName', function () { + it('gets and sets values', function () { + var container = { styles: [] }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); carr.set('name1', 'value1'); - expect(container).toEqual({styles: [ - {foo: 'name1', bar: {value: 'value1'}} - ]}); + expect(container).toEqual({ styles: [{ foo: 'name1', bar: { value: 'value1' } }] }); expect(carr.get('name1')).toEqual('value1'); }); - it('renames values', function() { - var container = {styles: []}; + it('renames values', function () { + var container = { styles: [] }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); carr.set('name1', 'value1'); carr.rename('name1', 'name2'); - expect(container).toEqual({styles: [ - {foo: 'name2', bar: {value: 'value1'}} - ]}); + expect(container).toEqual({ styles: [{ foo: 'name2', bar: { value: 'value1' } }] }); expect(carr.get('name2')).toEqual('value1'); expect(carr.get('name1')).toBeUndefined(); }); - it('constructs updates', function() { - var container = {styles: [ - {foo: 'name1', bar: {value: 'value1'}}, - {foo: 'name2', bar: {value: 'value2'}} - ]}; + it('constructs updates', function () { + var container = { + styles: [ + { foo: 'name1', bar: { value: 'value1' } }, + { foo: 'name2', bar: { value: 'value2' } } + ] + }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); @@ -2320,42 +2378,44 @@ describe('Test lib.js:', function() { carr.remove('name2'); carr.rename('name1', 'name4'); - expect(container).toEqual({styles: [ - {foo: 'name4', bar: {value: 'value1'}}, - {foo: 'name2', bar: {}}, - {foo: 'name3', bar: {value: 'value3'}} - ]}); + expect(container).toEqual({ + styles: [ + { foo: 'name4', bar: { value: 'value1' } }, + { foo: 'name2', bar: {} }, + { foo: 'name3', bar: { value: 'value3' } } + ] + }); expect(carr.constructUpdate()).toEqual({ 'styles[0].foo': 'name4', 'styles[1].bar.value': null, 'styles[2].foo': 'name3', - 'styles[2].bar.value': 'value3', + 'styles[2].bar.value': 'value3' }); }); - it('unsets but does not remove items with extra top-level data', function() { - var container = {styles: [ - {foo: 'name', bar: {value: 'value'}, extra: 'data'} - ]}; + it('unsets but does not remove items with extra top-level data', function () { + var container = { styles: [{ foo: 'name', bar: { value: 'value' }, extra: 'data' }] }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); carr.remove('name'); - expect(container.styles).toEqual([{foo: 'name', bar: {}, extra: 'data'}]); + expect(container.styles).toEqual([{ foo: 'name', bar: {}, extra: 'data' }]); expect(carr.constructUpdate()).toEqual({ - 'styles[0].bar.value': null, + 'styles[0].bar.value': null }); }); - it('unsets but does not remove items with extra value data', function() { - var container = {styles: [ - {foo: 'name1', bar: {value: 'value1', extra: 'data'}}, - {foo: 'name2', bar: {value: 'value2'}}, - {foo: 'name3', bar: {value: 'value3', extra: 'data'}}, - ]}; + it('unsets but does not remove items with extra value data', function () { + var container = { + styles: [ + { foo: 'name1', bar: { value: 'value1', extra: 'data' } }, + { foo: 'name2', bar: { value: 'value2' } }, + { foo: 'name3', bar: { value: 'value3', extra: 'data' } } + ] + }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); @@ -2364,9 +2424,9 @@ describe('Test lib.js:', function() { carr.remove('name1'); expect(container.styles).toEqual([ - {foo: 'name1', bar: {extra: 'data'}}, - {foo: 'name2', bar: {value: 'value2'}}, - {foo: 'name3', bar: {value: 'value3', extra: 'data'}}, + { foo: 'name1', bar: { extra: 'data' } }, + { foo: 'name2', bar: { value: 'value2' } }, + { foo: 'name3', bar: { value: 'value3', extra: 'data' } } ]); expect(carr.constructUpdate()).toEqual({ @@ -2377,9 +2437,9 @@ describe('Test lib.js:', function() { carr.remove('name2'); expect(container.styles).toEqual([ - {foo: 'name1', bar: {extra: 'data'}}, - {foo: 'name2', bar: {}}, - {foo: 'name3', bar: {value: 'value3', extra: 'data'}}, + { foo: 'name1', bar: { extra: 'data' } }, + { foo: 'name2', bar: {} }, + { foo: 'name3', bar: { value: 'value3', extra: 'data' } } ]); expect(carr.constructUpdate()).toEqual({ @@ -2388,11 +2448,13 @@ describe('Test lib.js:', function() { }); }); - it('does not compress nested attributes *sigh*', function() { - var container = {styles: [ - {foo: 'name1', bar: {value: 'value1'}}, - {foo: 'name2', bar: {value: 'value2', extra: 'data2'}}, - ]}; + it('does not compress nested attributes *sigh*', function () { + var container = { + styles: [ + { foo: 'name1', bar: { value: 'value1' } }, + { foo: 'name2', bar: { value: 'value2', extra: 'data2' } } + ] + }; var carr = Lib.keyedContainer(container, 'styles', 'foo', 'bar.value'); @@ -2401,8 +2463,8 @@ describe('Test lib.js:', function() { carr.remove('name1'); expect(container.styles).toEqual([ - {foo: 'name1', bar: {}}, - {foo: 'name2', bar: {value: 'value2', extra: 'data2'}}, + { foo: 'name1', bar: {} }, + { foo: 'name2', bar: { value: 'value2', extra: 'data2' } } ]); expect(carr.constructUpdate()).toEqual({ @@ -2412,95 +2474,109 @@ describe('Test lib.js:', function() { }); }); - describe('templateString', function() { - it('evaluates attributes', function() { - expect(Lib.templateString('foo %{bar}', {bar: 'baz'})).toEqual('foo baz'); + describe('templateString', function () { + it('evaluates attributes', function () { + expect(Lib.templateString('foo %{bar}', { bar: 'baz' })).toEqual('foo baz'); }); - it('evaluates nested properties', function() { - expect(Lib.templateString('foo %{bar.baz}', {bar: {baz: 'asdf'}})).toEqual('foo asdf'); + it('evaluates nested properties', function () { + expect(Lib.templateString('foo %{bar.baz}', { bar: { baz: 'asdf' } })).toEqual('foo asdf'); }); - it('evaluates array nested properties', function() { - expect(Lib.templateString('foo %{bar[0].baz}', {bar: [{baz: 'asdf'}]})).toEqual('foo asdf'); + it('evaluates array nested properties', function () { + expect(Lib.templateString('foo %{bar[0].baz}', { bar: [{ baz: 'asdf' }] })).toEqual('foo asdf'); }); - it('subtitutes multiple matches', function() { - expect(Lib.templateString('foo %{group} %{trace}', {group: 'asdf', trace: 'jkl;'})).toEqual('foo asdf jkl;'); + it('subtitutes multiple matches', function () { + expect(Lib.templateString('foo %{group} %{trace}', { group: 'asdf', trace: 'jkl;' })).toEqual( + 'foo asdf jkl;' + ); }); - it('replaces missing matches with empty string', function() { + it('replaces missing matches with empty string', function () { expect(Lib.templateString('foo %{group} %{trace}', {})).toEqual('foo '); }); - it('replaces empty key with empty string', function() { + it('replaces empty key with empty string', function () { expect(Lib.templateString('foo %{} %{}', {})).toEqual('foo '); }); - it('should work with the number *0*', function() { - expect(Lib.templateString('%{group}', {group: 0})).toEqual('0'); + it('should work with the number *0*', function () { + expect(Lib.templateString('%{group}', { group: 0 })).toEqual('0'); }); - it('should work with the number *0* (nested case)', function() { - expect(Lib.templateString('%{x.y}', {x: {y: 0}})).toEqual('0'); + it('should work with the number *0* (nested case)', function () { + expect(Lib.templateString('%{x.y}', { x: { y: 0 } })).toEqual('0'); }); - it('preserves null and NaN', function() { - expect(Lib.templateString( - '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', - {a: null, b: NaN, c: {d: null, e: NaN}, f: [null, NaN]} - )) - .toEqual('null NaN null NaN null NaN'); + it('preserves null and NaN', function () { + expect( + Lib.templateString('%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', { + a: null, + b: NaN, + c: { d: null, e: NaN }, + f: [null, NaN] + }) + ).toEqual('null NaN null NaN null NaN'); }); }); - describe('hovertemplateString', function() { + describe('hovertemplateString', function () { var locale = false; - it('evaluates attributes', function() { - expect(Lib.hovertemplateString('foo %{bar}', {}, locale, {bar: 'baz'})).toEqual('foo baz'); + it('evaluates attributes', function () { + expect(Lib.hovertemplateString('foo %{bar}', {}, locale, { bar: 'baz' })).toEqual('foo baz'); }); - it('evaluates attributes with a dot in their name', function() { - expect(Lib.hovertemplateString('%{marker.size}', {}, locale, {'marker.size': 12}, {marker: {size: 14}})).toEqual('12'); + it('evaluates attributes with a dot in their name', function () { + expect( + Lib.hovertemplateString('%{marker.size}', {}, locale, { 'marker.size': 12 }, { marker: { size: 14 } }) + ).toEqual('12'); }); - it('evaluates nested properties', function() { - expect(Lib.hovertemplateString('foo %{bar.baz}', {}, locale, {bar: {baz: 'asdf'}})).toEqual('foo asdf'); + it('evaluates nested properties', function () { + expect(Lib.hovertemplateString('foo %{bar.baz}', {}, locale, { bar: { baz: 'asdf' } })).toEqual('foo asdf'); }); - it('evaluates array nested properties', function() { - expect(Lib.hovertemplateString('foo %{bar[0].baz}', {}, locale, {bar: [{baz: 'asdf'}]})).toEqual('foo asdf'); + it('evaluates array nested properties', function () { + expect(Lib.hovertemplateString('foo %{bar[0].baz}', {}, locale, { bar: [{ baz: 'asdf' }] })).toEqual( + 'foo asdf' + ); }); - it('should work with the number *0*', function() { - expect(Lib.hovertemplateString('%{group}', {}, locale, {group: 0})).toEqual('0'); + it('should work with the number *0*', function () { + expect(Lib.hovertemplateString('%{group}', {}, locale, { group: 0 })).toEqual('0'); }); - it('should work with the number *0* (nested case)', function() { - expect(Lib.hovertemplateString('%{x.y}', {}, locale, {x: {y: 0}})).toEqual('0'); + it('should work with the number *0* (nested case)', function () { + expect(Lib.hovertemplateString('%{x.y}', {}, locale, { x: { y: 0 } })).toEqual('0'); }); - it('preserves null and NaN', function() { - expect(Lib.hovertemplateString( - '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', - {}, - locale, - {a: null, b: NaN, c: {d: null, e: NaN}, f: [null, NaN]} - )) - .toEqual('null NaN null NaN null NaN'); + it('preserves null and NaN', function () { + expect( + Lib.hovertemplateString('%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', {}, locale, { + a: null, + b: NaN, + c: { d: null, e: NaN }, + f: [null, NaN] + }) + ).toEqual('null NaN null NaN null NaN'); }); - it('subtitutes multiple matches', function() { - expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, {group: 'asdf', trace: 'jkl;'})).toEqual('foo asdf jkl;'); + it('subtitutes multiple matches', function () { + expect( + Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, { group: 'asdf', trace: 'jkl;' }) + ).toEqual('foo asdf jkl;'); }); - it('replaces missing matches with template string', function() { - expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, {group: 1})).toEqual('foo 1 %{trace}'); + it('replaces missing matches with template string', function () { + expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, { group: 1 })).toEqual( + 'foo 1 %{trace}' + ); }); - it('uses the value from the first object with the specified key', function() { - var obj1 = {a: 'first'}; - var obj2 = {a: 'second', foo: {bar: 'bar'}}; + it('uses the value from the first object with the specified key', function () { + var obj1 = { a: 'first' }; + var obj2 = { a: 'second', foo: { bar: 'bar' } }; // Simple key expect(Lib.hovertemplateString('foo %{a}', {}, locale, obj1, obj2)).toEqual('foo first'); @@ -2510,111 +2586,126 @@ describe('Test lib.js:', function() { expect(Lib.hovertemplateString('foo %{foo.bar}', {}, locale, obj1, obj2)).toEqual('foo bar'); // Nested keys with 0 - expect(Lib.hovertemplateString('y: %{y}', {}, locale, {y: 0}, {y: 1})).toEqual('y: 0'); + expect(Lib.hovertemplateString('y: %{y}', {}, locale, { y: 0 }, { y: 1 })).toEqual('y: 0'); }); - it('formats numbers using d3-format mini-language when `:`', function() { - expect(Lib.hovertemplateString('a: %{a:.0%}', {}, locale, {a: 0.123})).toEqual('a: 12%'); - expect(Lib.hovertemplateString('a: %{a:0.2%}', {}, locale, {a: 0.123})).toEqual('a: 12.30%'); - expect(Lib.hovertemplateString('b: %{b:2.2f}', {}, locale, {b: 43})).toEqual('b: 43.00'); + it('formats numbers using d3-format mini-language when `:`', function () { + expect(Lib.hovertemplateString('a: %{a:.0%}', {}, locale, { a: 0.123 })).toEqual('a: 12%'); + expect(Lib.hovertemplateString('a: %{a:0.2%}', {}, locale, { a: 0.123 })).toEqual('a: 12.30%'); + expect(Lib.hovertemplateString('b: %{b:2.2f}', {}, locale, { b: 43 })).toEqual('b: 43.00'); }); - it('formats date using d3-time-format mini-language `|`', function() { - expect(Lib.hovertemplateString('a: %{a|%A}', {}, locale, {a: '2019-05-22'})).toEqual('a: Wednesday'); - expect(Lib.hovertemplateString('%{x|%b %-d, %Y}', {}, locale, {x: '2019-01-01'})).toEqual('Jan 1, 2019'); + it('formats date using d3-time-format mini-language `|`', function () { + expect(Lib.hovertemplateString('a: %{a|%A}', {}, locale, { a: '2019-05-22' })).toEqual('a: Wednesday'); + expect(Lib.hovertemplateString('%{x|%b %-d, %Y}', {}, locale, { x: '2019-01-01' })).toEqual('Jan 1, 2019'); }); - it('looks for default label if no format is provided', function() { - expect(Lib.hovertemplateString('y: %{y}', {yLabel: '0.1'}, locale, {y: 0.123})).toEqual('y: 0.1'); + it('looks for default label if no format is provided', function () { + expect(Lib.hovertemplateString('y: %{y}', { yLabel: '0.1' }, locale, { y: 0.123 })).toEqual('y: 0.1'); }); - it('warns user up to 10 times if a variable cannot be found', function() { + it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); Lib.hovertemplateString('%{idontexist}', {}); expect(Lib.warn.calls.count()).toBe(1); - for(var i = 0; i < 15; i++) { + for (var i = 0; i < 15; i++) { Lib.hovertemplateString('%{idontexist}', {}); } expect(Lib.warn.calls.count()).toBe(10); }); - it('does not error out when arguments are undefined', function() { - expect(function() { + it('does not error out when arguments are undefined', function () { + expect(function () { Lib.hovertemplateString('y: %{y}', undefined, locale, undefined); }).not.toThrow(); }); }); - describe('texttemplateString', function() { + describe('texttemplateString', function () { var locale = false; - it('evaluates attributes', function() { - expect(Lib.texttemplateString('foo %{bar}', {}, locale, {bar: 'baz'})).toEqual('foo baz'); + it('evaluates attributes', function () { + expect(Lib.texttemplateString('foo %{bar}', {}, locale, { bar: 'baz' })).toEqual('foo baz'); }); - it('looks for default label if no format is provided', function() { - expect(Lib.texttemplateString('y: %{y}', {yLabel: '0.1'}, locale, {y: 0.123})).toEqual('y: 0.1'); + it('looks for default label if no format is provided', function () { + expect(Lib.texttemplateString('y: %{y}', { yLabel: '0.1' }, locale, { y: 0.123 })).toEqual('y: 0.1'); }); - it('preserves null and NaN', function() { - expect(Lib.texttemplateString( - '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', - {}, - locale, - {a: null, b: NaN, c: {d: null, e: NaN}, f: [null, NaN]} - )) - .toEqual('null NaN null NaN null NaN'); + it('preserves null and NaN', function () { + expect( + Lib.texttemplateString('%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', {}, locale, { + a: null, + b: NaN, + c: { d: null, e: NaN }, + f: [null, NaN] + }) + ).toEqual('null NaN null NaN null NaN'); }); - it('warns user up to 10 times if a variable cannot be found', function() { + it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); Lib.texttemplateString('%{idontexist}', {}); expect(Lib.warn.calls.count()).toBe(1); - for(var i = 0; i < 15; i++) { + for (var i = 0; i < 15; i++) { Lib.texttemplateString('%{idontexist}', {}); } expect(Lib.warn.calls.count()).toBe(11); }); }); - describe('relativeAttr()', function() { - it('replaces the last part always', function() { + describe('relativeAttr()', function () { + it('replaces the last part always', function () { expect(Lib.relativeAttr('annotations[3].x', 'y')).toBe('annotations[3].y'); expect(Lib.relativeAttr('x', 'z')).toBe('z'); expect(Lib.relativeAttr('marker.line.width', 'colorbar.x')).toBe('marker.line.colorbar.x'); }); - it('ascends with ^', function() { + it('ascends with ^', function () { expect(Lib.relativeAttr('annotations[3].x', '^[2].z')).toBe('annotations[2].z'); expect(Lib.relativeAttr('annotations[3].x', '^^margin')).toBe('margin'); expect(Lib.relativeAttr('annotations[3].x', '^^margin.r')).toBe('margin.r'); expect(Lib.relativeAttr('marker.line.width', '^colorbar.x')).toBe('marker.colorbar.x'); }); - it('fails on ascending too far', function() { - expect(function() { return Lib.relativeAttr('x', '^y'); }).toThrow(); - expect(function() { return Lib.relativeAttr('marker.line.width', '^^^colorbar.x'); }).toThrow(); - }); - - it('fails with malformed baseAttr', function() { - expect(function() { return Lib.relativeAttr('x[]', 'z'); }).toThrow(); - expect(function() { return Lib.relativeAttr('x.a]', 'z'); }).toThrow(); - expect(function() { return Lib.relativeAttr('x[a]', 'z'); }).toThrow(); - expect(function() { return Lib.relativeAttr('x[3].', 'z'); }).toThrow(); - expect(function() { return Lib.relativeAttr('x.y.', 'z'); }).toThrow(); + it('fails on ascending too far', function () { + expect(function () { + return Lib.relativeAttr('x', '^y'); + }).toThrow(); + expect(function () { + return Lib.relativeAttr('marker.line.width', '^^^colorbar.x'); + }).toThrow(); + }); + + it('fails with malformed baseAttr', function () { + expect(function () { + return Lib.relativeAttr('x[]', 'z'); + }).toThrow(); + expect(function () { + return Lib.relativeAttr('x.a]', 'z'); + }).toThrow(); + expect(function () { + return Lib.relativeAttr('x[a]', 'z'); + }).toThrow(); + expect(function () { + return Lib.relativeAttr('x[3].', 'z'); + }).toThrow(); + expect(function () { + return Lib.relativeAttr('x.y.', 'z'); + }).toThrow(); }); }); - describe('subplotSort', function() { - it('puts xy subplots in the right order', function() { + describe('subplotSort', function () { + it('puts xy subplots in the right order', function () { var a = ['x10y', 'x10y20', 'x10y12', 'x10y2', 'xy', 'x2y12', 'xy2', 'xy15']; a.sort(Lib.subplotSort); expect(a).toEqual(['xy', 'xy2', 'xy15', 'x2y12', 'x10y', 'x10y2', 'x10y12', 'x10y20']); }); - it('puts simple subplots in the right order', function() { - ['scene', 'geo', 'ternary', 'mapbox', 'map'].forEach(function(v) { + it('puts simple subplots in the right order', function () { + ['scene', 'geo', 'ternary', 'mapbox', 'map'].forEach(function (v) { var a = [v + '100', v + '43', v, v + '10', v + '2']; a.sort(Lib.subplotSort); expect(a).toEqual([v, v + '2', v + '10', v + '43', v + '100']); @@ -2622,9 +2713,9 @@ describe('Test lib.js:', function() { }); }); - describe('sort', function() { + describe('sort', function () { var callCount; - beforeEach(function() { + beforeEach(function () { callCount = 0; }); @@ -2640,7 +2731,7 @@ describe('Test lib.js:', function() { function ascending(n) { var out = new Array(n); - for(var i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { out[i] = i; } assertAscending(out); @@ -2649,7 +2740,7 @@ describe('Test lib.js:', function() { function descending(n) { var out = new Array(n); - for(var i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { out[i] = n - 1 - i; } assertDescending(out); @@ -2659,15 +2750,15 @@ describe('Test lib.js:', function() { function rand(n) { Lib.seedPseudoRandom(); var out = new Array(n); - for(var i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { out[i] = Lib.pseudoRandom(); } return out; } function assertAscending(array) { - for(var i = 1; i < array.length; i++) { - if(array[i] < array[i - 1]) { + for (var i = 1; i < array.length; i++) { + if (array[i] < array[i - 1]) { // we already know this expect will fail, // just want to format the message nicely and then // quit so we don't get a million messages @@ -2678,8 +2769,8 @@ describe('Test lib.js:', function() { } function assertDescending(array) { - for(var i = 1; i < array.length; i++) { - if(array[i] < array[i - 1]) { + for (var i = 1; i < array.length; i++) { + if (array[i] < array[i - 1]) { expect(array[i]).not.toBeGreaterThan(array[i - 1]); break; } @@ -2692,31 +2783,31 @@ describe('Test lib.js:', function() { return array; } - it('sorts ascending arrays ascending in N-1 calls', function() { + it('sorts ascending arrays ascending in N-1 calls', function () { var arrayIn = _sort(ascending(100000), sortCounter); expect(callCount).toBe(99999); assertAscending(arrayIn); }); - it('sorts descending arrays ascending in N-1 calls', function() { + it('sorts descending arrays ascending in N-1 calls', function () { var arrayIn = _sort(descending(100000), sortCounter); expect(callCount).toBe(99999); assertAscending(arrayIn); }); - it('sorts ascending arrays descending in N-1 calls', function() { + it('sorts ascending arrays descending in N-1 calls', function () { var arrayIn = _sort(ascending(100000), sortCounterReversed); expect(callCount).toBe(99999); assertDescending(arrayIn); }); - it('sorts descending arrays descending in N-1 calls', function() { + it('sorts descending arrays descending in N-1 calls', function () { var arrayIn = _sort(descending(100000), sortCounterReversed); expect(callCount).toBe(99999); assertDescending(arrayIn); }); - it('sorts random arrays ascending in a few more calls than bare sort', function() { + it('sorts random arrays ascending in a few more calls than bare sort', function () { var arrayIn = _sort(rand(100000), sortCounter); assertAscending(arrayIn); @@ -2732,7 +2823,7 @@ describe('Test lib.js:', function() { expect(ourCallCount - callCount).toBe(2); }); - it('sorts random arrays descending in a few more calls than bare sort', function() { + it('sorts random arrays descending in a few more calls than bare sort', function () { var arrayIn = _sort(rand(100000), sortCounterReversed); assertDescending(arrayIn); @@ -2743,7 +2834,7 @@ describe('Test lib.js:', function() { expect(ourCallCount - callCount).toBe(2); }); - it('supports short arrays', function() { + it('supports short arrays', function () { expect(_sort([], sortCounter)).toEqual([]); expect(_sort([1], sortCounter)).toEqual([1]); expect(callCount).toBe(0); @@ -2757,30 +2848,28 @@ describe('Test lib.js:', function() { return [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 7, 8, 9]; } - it('still short-circuits in order with duplicates', function() { - expect(_sort(dupes(), sortCounter)) - .toEqual(dupes()); + it('still short-circuits in order with duplicates', function () { + expect(_sort(dupes(), sortCounter)).toEqual(dupes()); expect(callCount).toEqual(18); }); - it('still short-circuits reversed with duplicates', function() { - expect(_sort(dupes(), sortCounterReversed)) - .toEqual(dupes().reverse()); + it('still short-circuits reversed with duplicates', function () { + expect(_sort(dupes(), sortCounterReversed)).toEqual(dupes().reverse()); expect(callCount).toEqual(18); }); }); - describe('relinkPrivateKeys', function() { - it('ignores customdata and ids', function() { + describe('relinkPrivateKeys', function () { + it('ignores customdata and ids', function () { var fromContainer = { - customdata: [{_x: 1, _y: 2, a: 3}], - ids: [{_i: 4, j: 5}] + customdata: [{ _x: 1, _y: 2, a: 3 }], + ids: [{ _i: 4, j: 5 }] }; var toContainer = { - customdata: [{a: 6}], - ids: [{j: 7}] + customdata: [{ a: 6 }], + ids: [{ j: 7 }] }; Lib.relinkPrivateKeys(toContainer, fromContainer); @@ -2790,12 +2879,17 @@ describe('Test lib.js:', function() { expect(toContainer.ids[0]._i).toBeUndefined(); }); - it('ignores any values that are ===', function() { + it('ignores any values that are ===', function () { var accesses = 0; var obj = { - get _x() { accesses++; return 1; }, - set _x(v) { accesses++; } + get _x() { + accesses++; + return 1; + }, + set _x(v) { + accesses++; + } }; var array = [obj]; var array2 = [obj]; @@ -2820,11 +2914,15 @@ describe('Test lib.js:', function() { expect(accesses).toBe(2); }); - it('reinserts other private keys if they\'re not already there', function() { - var obj1 = {a: 10, _a: 11}; - var obj2 = {a: 12, _a: 13}; - function f1() { return 1; } - function f2() { return 2; } + it("reinserts other private keys if they're not already there", function () { + var obj1 = { a: 10, _a: 11 }; + var obj2 = { a: 12, _a: 13 }; + function f1() { + return 1; + } + function f2() { + return 2; + } var fromContainer = { a: 1, @@ -2834,10 +2932,10 @@ describe('Test lib.js:', function() { _d: obj1, f: f1, // functions are private even without _ g: f1, - array: [{a: 3, _a: 4, _b: 5, f: f1, g: f1}], - o: {a: 6, _a: 7, _b: 8}, - array2: [{a: 9, _a: 10}], - o2: {a: 11, _a: 12} + array: [{ a: 3, _a: 4, _b: 5, f: f1, g: f1 }], + o: { a: 6, _a: 7, _b: 8 }, + array2: [{ a: 9, _a: 10 }], + o2: { a: 11, _a: 12 } }; fromContainer._circular = fromContainer; fromContainer._circular2 = fromContainer; @@ -2846,8 +2944,8 @@ describe('Test lib.js:', function() { _a: 22, _c: obj2, f: f2, - array: [{a: 23, _a: 24, f: f2}], - o: {a: 26, _a: 27}, + array: [{ a: 23, _a: 24, f: f2 }], + o: { a: 26, _a: 27 }, x: [28], _x: 29 }; @@ -2865,8 +2963,8 @@ describe('Test lib.js:', function() { _d: obj1, f: f2, g: f1, - array: [{a: 23, _a: 24, _b: 5, f: f2, g: f1}], - o: {a: 26, _a: 27, _b: 8}, + array: [{ a: 23, _a: 24, _b: 5, f: f2, g: f1 }], + o: { a: 26, _a: 27, _b: 8 }, x: [28], _x: 29 }; @@ -2875,24 +2973,24 @@ describe('Test lib.js:', function() { }); }); - describe('concat', function() { + describe('concat', function () { var concat = Lib.concat; - beforeEach(function() { + beforeEach(function () { spyOn(Array.prototype, 'concat').and.callThrough(); }); - it('works with multiple Arrays', function() { - var res = concat([1], [[2], 3], [{a: 4}, 5, 6]); + it('works with multiple Arrays', function () { + var res = concat([1], [[2], 3], [{ a: 4 }, 5, 6]); expect(Array.prototype.concat.calls.count()).toBe(1); // note: can't `concat` in the `expect` if we want to count native // `Array.concat calls`, because `toEqual` calls `Array.concat` // profusely itself. - expect(res).toEqual([1, [2], 3, {a: 4}, 5, 6]); + expect(res).toEqual([1, [2], 3, { a: 4 }, 5, 6]); }); - it('works with some empty arrays', function() { + it('works with some empty arrays', function () { var a1 = [1]; var res = concat(a1, [], [2, 3]); expect(Array.prototype.concat.calls.count()).toBe(1); @@ -2922,8 +3020,8 @@ describe('Test lib.js:', function() { expect(f1).toEqual(new Float32Array([1, 2])); }); - it('works with all empty arrays', function() { - [[], [[]], [[], []], [[], [], [], []]].forEach(function(empties) { + it('works with all empty arrays', function () { + [[], [[]], [[], []], [[], [], [], []]].forEach(function (empties) { Array.prototype.concat.calls.reset(); var res = concat.apply(null, empties); expect(Array.prototype.concat.calls.count()).toBe(0); @@ -2931,12 +3029,12 @@ describe('Test lib.js:', function() { }); }); - it('converts mismatched types to Array', function() { + it('converts mismatched types to Array', function () { [ [[1, 2], new Float64Array([3, 4])], [new Float64Array([1, 2]), [3, 4]], [new Float64Array([1, 2]), new Float32Array([3, 4])] - ].forEach(function(mismatch) { + ].forEach(function (mismatch) { Array.prototype.concat.calls.reset(); var res = concat.apply(null, mismatch); // no concat - all entries moved over individually @@ -2945,8 +3043,8 @@ describe('Test lib.js:', function() { }); }); - it('concatenates matching TypedArrays preserving type', function() { - [Float32Array, Float64Array, Int16Array, Int32Array].forEach(function(Type, i) { + it('concatenates matching TypedArrays preserving type', function () { + [Float32Array, Float64Array, Int16Array, Int32Array].forEach(function (Type, i) { var v = i * 10; Array.prototype.concat.calls.reset(); var res = concat([], new Type([v]), new Type([v + 1, v]), new Type([v + 2, v, v])); @@ -2957,168 +3055,171 @@ describe('Test lib.js:', function() { }); }); - describe("User agent", () => { + describe('User agent', () => { const userAgentStrings = { - iOSSafari: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1", - iOSChrome: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1", - macChrome: "Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", - macSafari: "Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15", - macWKWebView: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)", - winFirefox: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0" - } + iOSSafari: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1', + iOSChrome: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1', + macChrome: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', + macSafari: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15', + macWKWebView: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)', + winFirefox: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0' + }; describe('isIOS', () => { - [userAgentStrings.iOSChrome, userAgentStrings.iOSSafari].forEach(uaString => { + [userAgentStrings.iOSChrome, userAgentStrings.iOSSafari].forEach((uaString) => { it('matches an iOS user agent string', () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(uaString) - expect(Lib.isIOS()).toBe(true) - }) - }) - + spyOnProperty(navigator, 'userAgent').and.returnValue(uaString); + expect(Lib.isIOS()).toBe(true); + }); + }); + it("doesn't match a non-iOS user agent string", () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari) - expect(Lib.isIOS()).toBe(false) - }) - }) - + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari); + expect(Lib.isIOS()).toBe(false); + }); + }); + describe('isSafari', () => { it('matches a Safari user agent string', () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari) - expect(Lib.isSafari()).toBe(true) - }) - + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari); + expect(Lib.isSafari()).toBe(true); + }); + it("doesn't match a non-Safari user agent string", () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macChrome) - expect(Lib.isSafari()).toBe(false) - }) - }) + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macChrome); + expect(Lib.isSafari()).toBe(false); + }); + }); describe('isMacWKWebView', () => { it('matches a Safari user agent string', () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macWKWebView) - expect(Lib.isMacWKWebView()).toBe(true) - }) - + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macWKWebView); + expect(Lib.isMacWKWebView()).toBe(true); + }); + it("doesn't match a non-Safari user agent string", () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari) - expect(Lib.isMacWKWebView()).toBe(false) - }) - }) + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari); + expect(Lib.isMacWKWebView()).toBe(false); + }); + }); describe('getFirefoxVersion', () => { it('gets the Firefox version from the user agent string', () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.winFirefox) - expect(Lib.getFirefoxVersion()).toBe(140) - }) - - it("returns null for a non-Firefox user agent string", () => { - spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari) - expect(Lib.getFirefoxVersion()).toBe(null) - }) - }) - }) + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.winFirefox); + expect(Lib.getFirefoxVersion()).toBe(140); + }); + + it('returns null for a non-Firefox user agent string', () => { + spyOnProperty(navigator, 'userAgent').and.returnValue(userAgentStrings.macSafari); + expect(Lib.getFirefoxVersion()).toBe(null); + }); + }); + }); }); -describe('Queue', function() { +describe('Queue', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); - afterEach(function() { + afterEach(function () { destroyGraphDiv(); Plotly.setPlotConfig({ queueLength: 0 }); }); - it('should not fill in undoQueue by default', function(done) { - Plotly.newPlot(gd, [{ - y: [2, 1, 2] - }]).then(function() { - expect(gd.undoQueue).toBeUndefined(); - - return Plotly.restyle(gd, 'marker.color', 'red'); - }).then(function() { - expect(gd.undoQueue.index).toEqual(0); - expect(gd.undoQueue.queue).toEqual([]); - - return Plotly.relayout(gd, 'title', 'A title'); - }).then(function() { - expect(gd.undoQueue.index).toEqual(0); - expect(gd.undoQueue.queue).toEqual([]); - }) - .then(done, done.fail); + it('should not fill in undoQueue by default', function (done) { + Plotly.newPlot(gd, [ + { + y: [2, 1, 2] + } + ]) + .then(function () { + expect(gd.undoQueue).toBeUndefined(); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }) + .then(function () { + expect(gd.undoQueue.index).toEqual(0); + expect(gd.undoQueue.queue).toEqual([]); + + return Plotly.relayout(gd, 'title', 'A title'); + }) + .then(function () { + expect(gd.undoQueue.index).toEqual(0); + expect(gd.undoQueue.queue).toEqual([]); + }) + .then(done, done.fail); }); - it('should fill in undoQueue up to value found in *queueLength* config', function(done) { + it('should fill in undoQueue up to value found in *queueLength* config', function (done) { Plotly.setPlotConfig({ queueLength: 2 }); - Plotly.newPlot(gd, [{ - y: [2, 1, 2] - }]) - .then(function() { - expect(gd.undoQueue).toBeUndefined(); - - return Plotly.restyle(gd, 'marker.color', 'red'); - }) - .then(function() { - expect(gd.undoQueue.index).toEqual(1); - expect(gd.undoQueue.queue[0].undo.args[0][1]['marker.color']).toEqual([null]); - expect(gd.undoQueue.queue[0].redo.args[0][1]['marker.color']).toEqual('red'); - - return Plotly.relayout(gd, 'title.text', 'A title'); - }) - .then(function() { - expect(gd.undoQueue.index).toEqual(2); - expect(gd.undoQueue.queue[1].undo.args[0][1]['title.text']).toEqual(null); - expect(gd.undoQueue.queue[1].redo.args[0][1]['title.text']).toEqual('A title'); - - return Plotly.restyle(gd, 'mode', 'markers'); - }) - .then(function() { - expect(gd.undoQueue.index).toEqual(2); - expect(gd.undoQueue.queue[2]).toBeUndefined(); - - expect(gd.undoQueue.queue[1].undo.args[0][1].mode).toEqual([null]); - expect(gd.undoQueue.queue[1].redo.args[0][1].mode).toEqual('markers'); - - expect(gd.undoQueue.queue[0].undo.args[0][1]['title.text']).toEqual(null); - expect(gd.undoQueue.queue[0].redo.args[0][1]['title.text']).toEqual('A title'); - - return Plotly.restyle(gd, 'transforms[0]', { type: 'filter' }); - }) - .then(function() { - expect(gd.undoQueue.queue[1].undo.args[0][1]) - .toEqual({ 'transforms[0]': null }); - expect(gd.undoQueue.queue[1].redo.args[0][1]) - .toEqual({ 'transforms[0]': { type: 'filter' } }); - - return Plotly.relayout(gd, 'updatemenus[0]', { buttons: [] }); - }) - .then(function() { - expect(gd.undoQueue.queue[1].undo.args[0][1]) - .toEqual({ 'updatemenus[0]': null }); - expect(gd.undoQueue.queue[1].redo.args[0][1]) - .toEqual({ 'updatemenus[0]': { buttons: [] } }); - - return Plotly.relayout(gd, 'updatemenus[0]', null); - }) - .then(function() { - expect(gd.undoQueue.queue[1].undo.args[0][1]) - .toEqual({ 'updatemenus[0]': { buttons: [] } }); - expect(gd.undoQueue.queue[1].redo.args[0][1]) - .toEqual({ 'updatemenus[0]': null }); - - return Plotly.restyle(gd, 'transforms[0]', null); - }) - .then(function() { - expect(gd.undoQueue.queue[1].undo.args[0][1]) - .toEqual({ 'transforms[0]': [ { type: 'filter' } ]}); - expect(gd.undoQueue.queue[1].redo.args[0][1]) - .toEqual({ 'transforms[0]': null }); - }) - .then(done, done.fail); + Plotly.newPlot(gd, [ + { + y: [2, 1, 2] + } + ]) + .then(function () { + expect(gd.undoQueue).toBeUndefined(); + + return Plotly.restyle(gd, 'marker.color', 'red'); + }) + .then(function () { + expect(gd.undoQueue.index).toEqual(1); + expect(gd.undoQueue.queue[0].undo.args[0][1]['marker.color']).toEqual([null]); + expect(gd.undoQueue.queue[0].redo.args[0][1]['marker.color']).toEqual('red'); + + return Plotly.relayout(gd, 'title.text', 'A title'); + }) + .then(function () { + expect(gd.undoQueue.index).toEqual(2); + expect(gd.undoQueue.queue[1].undo.args[0][1]['title.text']).toEqual(null); + expect(gd.undoQueue.queue[1].redo.args[0][1]['title.text']).toEqual('A title'); + + return Plotly.restyle(gd, 'mode', 'markers'); + }) + .then(function () { + expect(gd.undoQueue.index).toEqual(2); + expect(gd.undoQueue.queue[2]).toBeUndefined(); + + expect(gd.undoQueue.queue[1].undo.args[0][1].mode).toEqual([null]); + expect(gd.undoQueue.queue[1].redo.args[0][1].mode).toEqual('markers'); + + expect(gd.undoQueue.queue[0].undo.args[0][1]['title.text']).toEqual(null); + expect(gd.undoQueue.queue[0].redo.args[0][1]['title.text']).toEqual('A title'); + + return Plotly.restyle(gd, 'transforms[0]', { type: 'filter' }); + }) + .then(function () { + expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'transforms[0]': null }); + expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'transforms[0]': { type: 'filter' } }); + + return Plotly.relayout(gd, 'updatemenus[0]', { buttons: [] }); + }) + .then(function () { + expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'updatemenus[0]': null }); + expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'updatemenus[0]': { buttons: [] } }); + + return Plotly.relayout(gd, 'updatemenus[0]', null); + }) + .then(function () { + expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'updatemenus[0]': { buttons: [] } }); + expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'updatemenus[0]': null }); + + return Plotly.restyle(gd, 'transforms[0]', null); + }) + .then(function () { + expect(gd.undoQueue.queue[1].undo.args[0][1]).toEqual({ 'transforms[0]': [{ type: 'filter' }] }); + expect(gd.undoQueue.queue[1].redo.args[0][1]).toEqual({ 'transforms[0]': null }); + }) + .then(done, done.fail); }); }); diff --git a/test/jasmine/tests/sunburst_test.js b/test/jasmine/tests/sunburst_test.js index 719d9f61bae..a87ef2288d6 100644 --- a/test/jasmine/tests/sunburst_test.js +++ b/test/jasmine/tests/sunburst_test.js @@ -13,7 +13,6 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); var delay = require('../assets/delay'); - var customAssertions = require('../assets/custom_assertions'); var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle; var assertHoverLabelContent = customAssertions.assertHoverLabelContent; @@ -22,15 +21,15 @@ var checkTextTemplate = require('../assets/check_texttemplate'); var SLICES_TEXT_SELECTOR = '.sunburstlayer text.slicetext'; function _mouseEvent(type, gd, v) { - return function() { - if(Array.isArray(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}); + mouseEvent(type, 0, 0, { element: el }); } }; } @@ -47,7 +46,7 @@ function click(gd, v) { return _mouseEvent('click', gd, v); } -describe('Test sunburst defaults:', function() { +describe('Test sunburst defaults:', function () { var gd; var fullData; @@ -55,8 +54,8 @@ describe('Test sunburst defaults:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'sunburst'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'sunburst' }, o || {}); }); gd.layout = layout || {}; @@ -64,23 +63,19 @@ describe('Test sunburst defaults:', function() { fullData = gd._fullData; } - it('should set *visible:false* when *labels* or *parents* is missing', function() { - _supply([ - {labels: [1], parents: ['']}, - {labels: [1]}, - {parents: ['']} - ]); + 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() { + 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]} + { labels: [1], parents: [''] }, + { labels: [1], parents: [''], values: [] }, + { labels: [1], parents: [''], values: [1] } ]); expect(fullData[0].count).toBe('leaves'); @@ -88,107 +83,106 @@ describe('Test sunburst defaults:', function() { expect(fullData[2].count).toBe(undefined, 'has values'); }); - it('should not coerce *branchvalues* when *values* is not set', function() { + it('should not coerce *branchvalues* when *values* is not set', function () { _supply([ - {labels: [1], parents: [''], values: [1]}, - {labels: [1], parents: ['']}, + { 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' - }); + 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() { + 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: ['']} + { 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() { + it('should default *leaf.opacity* depending on a *colorscale* being present or not', function () { _supply([ - {labels: [1], parents: ['']}, - {labels: [1], parents: [''], marker: {colorscale: 'Blues'}} + { 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() { + it('should include "text" flag in *textinfo* when *text* is set', function () { _supply([ - {labels: [1], parents: [''], text: ['A']}, - {labels: [1], parents: ['']} + { 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 *sunburstcolorway*', function() { - _supply([ - {labels: [1], parents: ['']} - ], { + it('should use *layout.colorway* as dflt for *sunburstcolorway*', function () { + _supply([{ labels: [1], parents: [''] }], { colorway: ['red', 'blue', 'green'] }); - expect(gd._fullLayout.sunburstcolorway) - .toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); + expect(gd._fullLayout.sunburstcolorway).toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); - _supply([ - {labels: [1], parents: ['']} - ], { + _supply([{ labels: [1], parents: [''] }], { colorway: ['red', 'blue', 'green'], sunburstcolorway: ['cyan', 'yellow', 'black'] }); - expect(gd._fullLayout.sunburstcolorway) - .toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); + expect(gd._fullLayout.sunburstcolorway).toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); }); - it('should not default *marker.colorscale* when *marker.colors* is not present', function() { - _supply([ - {labels: [1], parents: ['']} - ]); + 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() { + it('should default *marker.colorscale* to *Reds* when *marker.colors* is present', function () { _supply([ - {labels: [1], parents: [''], marker: { - colors: [0] - }} + { + 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)' ] + [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 sunburst calc:', function() { +describe('Test sunburst calc:', function () { var gd; - beforeEach(function() { + beforeEach(function () { spyOn(Lib, 'warn'); }); @@ -196,8 +190,8 @@ describe('Test sunburst calc:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'sunburst'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'sunburst' }, o || {}); }); gd.layout = layout || {}; @@ -206,22 +200,24 @@ describe('Test sunburst calc:', function() { } function extract(k) { - var out = gd.calcdata.map(function(cd) { - return cd.map(function(pt) { return pt[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) { + 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() { + it('should generate *id* when it can', function () { _calc({ labels: ['Root', 'A', 'B', 'b'], parents: ['', 'Root', 'Root', 'B'] @@ -231,9 +227,9 @@ describe('Test sunburst calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should generate "implied root" when it can', function() { + it('should generate "implied root" when it can', function () { _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['Root', 'Root', 'B'] }); @@ -243,23 +239,25 @@ describe('Test sunburst calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when there are multiple implied roots', function() { + it('should warn when there are multiple implied roots', function () { _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['Root1', 'Root22', 'B'] }); expect(Lib.warn).toHaveBeenCalledTimes(1); - expect(Lib.warn).toHaveBeenCalledWith('Multiple implied roots, cannot build sunburst hierarchy of trace 0. These roots include: Root1, Root22'); + expect(Lib.warn).toHaveBeenCalledWith( + 'Multiple implied roots, cannot build sunburst 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() { + it('should generate "root of roots" when it can', function () { + spyOn(Lib, 'randstr').and.callFake(function () { return 'dummy'; }); _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['', '', 'B'] }); @@ -268,16 +266,16 @@ describe('Test sunburst calc:', function() { expect(extract('label')).toEqual(['', 'A', 'B', 'b']); }); - it('should compute hierarchy values', function() { + 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'} + { 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([ @@ -290,7 +288,7 @@ describe('Test sunburst calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when values under *branchvalues:total* do not add up and not show trace', function() { + 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'], @@ -301,11 +299,15 @@ describe('Test sunburst calc:', function() { 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'); + 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() { + it('should warn labels/parents lead to ambiguous hierarchy', function () { _calc({ labels: ['Root', 'A', 'A', 'B'], parents: ['', 'Root', 'Root', 'A'] @@ -315,7 +317,7 @@ describe('Test sunburst calc:', function() { expect(Lib.warn).toHaveBeenCalledWith('Failed to build sunburst hierarchy of trace 0. Error: ambiguous: A'); }); - it('should warn ids/parents lead to ambiguous hierarchy', function() { + 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'], @@ -326,7 +328,7 @@ describe('Test sunburst calc:', function() { expect(Lib.warn).toHaveBeenCalledWith('Failed to build sunburst hierarchy of trace 0. Error: ambiguous: b'); }); - it('should accept numbers (even `0`) are ids/parents items', function() { + 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], @@ -337,7 +339,7 @@ describe('Test sunburst calc:', function() { expect(extract('pid')).toEqual(['', '0', '0', '2', '2', '0', '0', '6', '0']); }); - it('should accept mix typed are ids/parents items', function() { + 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], @@ -348,18 +350,21 @@ describe('Test sunburst calc:', function() { 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' - }]); + 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'], @@ -371,20 +376,25 @@ describe('Test sunburst calc:', function() { ]); }); - it('should compute correct sector *value* for generated "root of roots"', function() { - spyOn(Lib, 'randstr').and.callFake(function() { return 'dummy'; }); + 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' - }]); + _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'], @@ -396,7 +406,7 @@ describe('Test sunburst calc:', function() { ]); }); - it('should use *marker.colors*', function() { + 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'], @@ -416,7 +426,7 @@ describe('Test sunburst calc:', function() { expect(cd[8].color).toEqual('rgba(255, 255, 255, 1)'); }); - it('should use *marker.colors* numbers with default colorscale', function() { + 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'], @@ -436,7 +446,7 @@ describe('Test sunburst calc:', function() { expect(cd[8].color).toEqual('rgb(178, 10, 28)'); }); - it('should use *marker.colors* numbers with desired colorscale', function() { + 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'], @@ -456,7 +466,7 @@ describe('Test sunburst calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use *marker.colors* numbers not values with colorscale', function() { + 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' }, @@ -477,7 +487,7 @@ describe('Test sunburst calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use values with colorscale when *marker.colors* in empty', function() { + 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' }, @@ -499,7 +509,7 @@ describe('Test sunburst calc:', function() { }); }); -describe('Test sunburst hover:', function() { +describe('Test sunburst hover:', function () { var gd; var labels0 = ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura']; @@ -511,31 +521,36 @@ describe('Test sunburst hover:', function() { function run(spec) { gd = createGraphDiv(); - var data = (spec.traces || [{}]).map(function(t) { + var data = (spec.traces || [{}]).map(function (t) { t.type = 'sunburst'; - if(!t.labels) t.labels = labels0.slice(); - if(!t.parents) t.parents = parents0.slice(); + 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 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(function () { + gd.once('plotly_hover', function (d) { + ptData = d.points[0]; + }); }) .then(hover(gd, spec.pos)) - .then(function() { + .then(function () { assertHoverLabelContent(exp.label); - for(var k in exp.ptData) { + for (var k in exp.ptData) { expect(ptData[k]).toBe(exp.ptData[k], 'pt event data key ' + k); } @@ -545,173 +560,190 @@ describe('Test sunburst hover:', function() { expect(typeof ptData.bbox.y0).toEqual('number'); expect(typeof ptData.bbox.y1).toEqual('number'); - if(exp.style) { + 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: 'base', + pos: 2, + exp: { + label: { + nums: 'Seth' + }, + ptData: { + curveNumber: 0, + pointNumber: 2, + label: 'Seth', + 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 scalar hovertext', + traces: [{ hovertext: 'A' }], + pos: 3, + exp: { + label: { + nums: 'Cain\nA' + }, + ptData: { + curveNumber: 0, + pointNumber: 1, + label: 'Cain', + 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 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 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 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 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 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 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' + }, + { + 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 } } - }], - 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: '' + }, + { + 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) { + ].forEach(function (spec) { + it('should generate correct hover labels and event data - ' + spec.desc, function (done) { run(spec).then(done, done.fail); }); }); }); -describe('Test sunburst hover lifecycle:', function() { +describe('Test sunburst hover lifecycle:', function () { var gd; var hoverData; var unhoverData; var hoverCnt; var unhoverCnt; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); @@ -721,62 +753,62 @@ describe('Test sunburst hover lifecycle:', function() { hoverCnt = 0; unhoverCnt = 0; - return function() { - gd.on('plotly_hover', function(d) { + return function () { + gd.on('plotly_hover', function (d) { hoverData = d; hoverCnt++; }); - gd.on('plotly_unhover', function(d) { + gd.on('plotly_unhover', function (d) { unhoverData = d; unhoverCnt++; }); }; } - it('should fire the correct events', function(done) { + it('should fire the correct events', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(hover(gd, 1)) - .then(function() { - if(hoverCnt === 1) { - expect(hoverData.event).toBeDefined(); - expect(hoverData.points[0].label).toBe('Eve'); - } else { - fail('did not trigger correct # of plotly_hover events'); - } + .then(setupListeners()) + .then(hover(gd, 1)) + .then(function () { + if (hoverCnt === 1) { + expect(hoverData.event).toBeDefined(); + expect(hoverData.points[0].label).toBe('Eve'); + } else { + fail('did not trigger correct # of plotly_hover events'); + } - if(unhoverCnt) { - fail('should not have triggered plotly_unhover'); - } - }) - .then(unhover(gd, 1)) - .then(hover(gd, 2)) - .then(function() { - if(hoverCnt === 2) { - expect(hoverData.event).toBeDefined(); - expect(hoverData.points[0].label).toBe('Seth'); - } else { - fail('did not trigger correct # of plotly_hover events'); - } + if (unhoverCnt) { + fail('should not have triggered plotly_unhover'); + } + }) + .then(unhover(gd, 1)) + .then(hover(gd, 2)) + .then(function () { + if (hoverCnt === 2) { + expect(hoverData.event).toBeDefined(); + expect(hoverData.points[0].label).toBe('Seth'); + } else { + fail('did not trigger correct # of plotly_hover events'); + } - if(unhoverCnt === 1) { - expect(unhoverData.event).toBeDefined(); - expect(unhoverData.points[0].label).toBe('Eve'); - } else { - fail('did not trigger correct # of plotly_unhover events'); - } - }) - .then(done, done.fail); + if (unhoverCnt === 1) { + expect(unhoverData.event).toBeDefined(); + expect(unhoverData.points[0].label).toBe('Eve'); + } else { + fail('did not trigger correct # of plotly_unhover events'); + } + }) + .then(done, done.fail); }); }); -describe('Test sunburst clicks:', function() { +describe('Test sunburst clicks:', function () { var gd; var trackers; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); trackers = {}; }); @@ -793,274 +825,280 @@ describe('Test sunburst clicks:', function() { // use `.unshift` that way to latest event data object // will be in entry [0], which is easier to pick out - return function() { - gd.on('plotly_sunburstclick', function(d) { + return function () { + gd.on('plotly_sunburstclick', function (d) { trackers.sunburstclick.unshift(d); - if(opts.turnOffAnimation) return false; + if (opts.turnOffAnimation) return false; }); - gd.on('plotly_click', function(d) { + gd.on('plotly_click', function (d) { trackers.click.unshift(d); }); - gd.on('plotly_animating', function() { + gd.on('plotly_animating', function () { // N.B. does not emit event data trackers.animating.unshift(true); }); }; } - it('should trigger animation when clicking on branches', function(done) { + it('should trigger animation when clicking on branches', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 2)) - .then(function() { - if(trackers.sunburstclick.length === 1) { - expect(trackers.sunburstclick[0].event).toBeDefined(); - expect(trackers.sunburstclick[0].points[0].label).toBe('Seth'); - expect(trackers.sunburstclick[0].nextLevel).toBe('Seth'); - } else { - fail('incorrect plotly_sunburstclick triggering'); - } + .then(setupListeners()) + .then(click(gd, 2)) + .then(function () { + if (trackers.sunburstclick.length === 1) { + expect(trackers.sunburstclick[0].event).toBeDefined(); + expect(trackers.sunburstclick[0].points[0].label).toBe('Seth'); + expect(trackers.sunburstclick[0].nextLevel).toBe('Seth'); + } else { + fail('incorrect plotly_sunburstclick triggering'); + } - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(); - expect(trackers.click[0].points[0].label).toBe('Seth'); - expect(trackers.click[0].nextLevel).not.toBeDefined(); - } else { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(); + expect(trackers.click[0].points[0].label).toBe('Seth'); + expect(trackers.click[0].nextLevel).not.toBeDefined(); + } else { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); - it('should trigger plotly_click event when clicking on root node', function(done) { + it('should trigger plotly_click event when clicking on root node', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 1)) - .then(function() { - if(trackers.sunburstclick.length === 1) { - expect(trackers.sunburstclick[0].event).toBeDefined(); - expect(trackers.sunburstclick[0].points[0].label).toBe('Eve'); - } else { - fail('incorrect plotly_sunburstclick triggering'); - } + .then(setupListeners()) + .then(click(gd, 1)) + .then(function () { + if (trackers.sunburstclick.length === 1) { + expect(trackers.sunburstclick[0].event).toBeDefined(); + expect(trackers.sunburstclick[0].points[0].label).toBe('Eve'); + } else { + fail('incorrect plotly_sunburstclick triggering'); + } - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(); - expect(trackers.click[0].points[0].label).toBe('Eve'); - } else { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(); + expect(trackers.click[0].points[0].label).toBe('Eve'); + } else { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 0) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 0) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); - it('should trigger plotly_click event when clicking on leaf node', function(done) { + it('should trigger plotly_click event when clicking on leaf node', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 8)) - .then(function() { - if(trackers.sunburstclick.length === 1) { - expect(trackers.sunburstclick[0].event).toBeDefined(); - expect(trackers.sunburstclick[0].points[0].label).toBe('Noam'); - } else { - fail('incorrect plotly_sunburstclick triggering'); - } + .then(setupListeners()) + .then(click(gd, 8)) + .then(function () { + if (trackers.sunburstclick.length === 1) { + expect(trackers.sunburstclick[0].event).toBeDefined(); + expect(trackers.sunburstclick[0].points[0].label).toBe('Noam'); + } else { + fail('incorrect plotly_sunburstclick triggering'); + } - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(); - expect(trackers.click[0].points[0].label).toBe('Noam'); - } else { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(); + expect(trackers.click[0].points[0].label).toBe('Noam'); + } else { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 0) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 0) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); - it('should not trigger animation when graph is transitioning', function(done) { + it('should not trigger animation when graph is transitioning', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 2)) - .then(function() { - var msg = 'after 1st click'; - - if(trackers.sunburstclick.length === 1) { - expect(trackers.sunburstclick[0].event).toBeDefined(msg); - expect(trackers.sunburstclick[0].points[0].label).toBe('Seth', msg); - expect(trackers.sunburstclick[0].nextLevel).toBe('Seth', msg); - } else { - fail('incorrect plotly_sunburstclick triggering - ' + msg); - } - - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(msg); - expect(trackers.click[0].points[0].label).toBe('Seth', msg); - expect(trackers.click[0].nextLevel).not.toBeDefined(msg); - } else { - fail('incorrect plotly_click triggering - ' + msg); - } - - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering - ' + msg); - } - }) - .then(click(gd, 4)) - .then(function() { - var msg = 'after 2nd click'; + .then(setupListeners()) + .then(click(gd, 2)) + .then(function () { + var msg = 'after 1st click'; + + if (trackers.sunburstclick.length === 1) { + expect(trackers.sunburstclick[0].event).toBeDefined(msg); + expect(trackers.sunburstclick[0].points[0].label).toBe('Seth', msg); + expect(trackers.sunburstclick[0].nextLevel).toBe('Seth', msg); + } else { + fail('incorrect plotly_sunburstclick triggering - ' + msg); + } - // should trigger plotly_sunburstclick and plotly_click twice, - // but not plotly_animating + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(msg); + expect(trackers.click[0].points[0].label).toBe('Seth', msg); + expect(trackers.click[0].nextLevel).not.toBeDefined(msg); + } else { + fail('incorrect plotly_click triggering - ' + msg); + } - if(trackers.sunburstclick.length === 2) { - expect(trackers.sunburstclick[0].event).toBeDefined(msg); - expect(trackers.sunburstclick[0].points[0].label).toBe('Awan', msg); - expect(trackers.sunburstclick[0].nextLevel).toBe('Awan', msg); - } else { - fail('incorrect plotly_sunburstclick triggering - ' + msg); - } + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering - ' + msg); + } + }) + .then(click(gd, 4)) + .then(function () { + var msg = 'after 2nd click'; + + // should trigger plotly_sunburstclick and plotly_click twice, + // but not plotly_animating + + if (trackers.sunburstclick.length === 2) { + expect(trackers.sunburstclick[0].event).toBeDefined(msg); + expect(trackers.sunburstclick[0].points[0].label).toBe('Awan', msg); + expect(trackers.sunburstclick[0].nextLevel).toBe('Awan', msg); + } else { + fail('incorrect plotly_sunburstclick triggering - ' + msg); + } - if(trackers.click.length === 2) { - expect(trackers.click[0].event).toBeDefined(msg); - expect(trackers.click[0].points[0].label).toBe('Awan', msg); - expect(trackers.click[0].nextLevel).not.toBeDefined(msg); - } else { - fail('incorrect plotly_click triggering - ' + msg); - } + if (trackers.click.length === 2) { + expect(trackers.click[0].event).toBeDefined(msg); + expect(trackers.click[0].points[0].label).toBe('Awan', msg); + expect(trackers.click[0].nextLevel).not.toBeDefined(msg); + } else { + fail('incorrect plotly_click triggering - ' + msg); + } - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering - ' + msg); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering - ' + msg); + } + }) + .then(done, done.fail); }); - it('should be able to override default click behavior using plotly_sunburstclick handler ()', function(done) { + it('should be able to override default click behavior using plotly_sunburstclick handler ()', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners({turnOffAnimation: true})) - .then(click(gd, 2)) - .then(function() { - if(trackers.sunburstclick.length === 1) { - expect(trackers.sunburstclick[0].event).toBeDefined(); - expect(trackers.sunburstclick[0].points[0].label).toBe('Seth'); - } else { - fail('incorrect plotly_sunburstclick triggering'); - } + .then(setupListeners({ turnOffAnimation: true })) + .then(click(gd, 2)) + .then(function () { + if (trackers.sunburstclick.length === 1) { + expect(trackers.sunburstclick[0].event).toBeDefined(); + expect(trackers.sunburstclick[0].points[0].label).toBe('Seth'); + } else { + fail('incorrect plotly_sunburstclick triggering'); + } - if(trackers.click.length !== 0) { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length !== 0) { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 0) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 0) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); }); -describe('Test sunburst restyle:', function() { +describe('Test sunburst restyle:', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); function _restyle(updateObj) { - return function() { return Plotly.restyle(gd, updateObj); }; + return function () { + return Plotly.restyle(gd, updateObj); + }; } - it('should be able to toggle visibility', function(done) { + it('should be able to toggle visibility', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_first.json')); function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.sunburstlayer'); 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); + .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) { + it('should be able to restyle *maxdepth* and *level* w/o recomputing the hierarchy', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/sunburst_coffee.json')); function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.sunburstlayer'); expect(layer.selectAll('.slice').size()).toBe(exp, msg); // editType:plot - if(msg !== 'base') { + if (msg !== 'base') { expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); } }; } Plotly.newPlot(gd, mock) - .then(_assert('base', 96)) - .then(function() { - spyOn(Plots, 'doCalcdata').and.callThrough(); - }) - .then(_restyle({maxdepth: 3})) - .then(_assert('with maxdepth:3', 26)) - .then(_restyle({level: 'Aromas'})) - .then(_assert('with non-root level', 13)) - .then(_restyle({maxdepth: null, level: null})) - .then(_assert('back to first view', 96)) - .then(done, done.fail); + .then(_assert('base', 96)) + .then(function () { + spyOn(Plots, 'doCalcdata').and.callThrough(); + }) + .then(_restyle({ maxdepth: 3 })) + .then(_assert('with maxdepth:3', 26)) + .then(_restyle({ level: 'Aromas' })) + .then(_assert('with non-root level', 13)) + .then(_restyle({ maxdepth: null, level: null })) + .then(_assert('back to first view', 96)) + .then(done, done.fail); }); - it('should be able to restyle *leaf.opacity*', function(done) { + it('should be able to restyle *leaf.opacity*', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'] - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + } + ] }; function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.sunburstlayer'); var opacities = []; - layer.selectAll('path.surface').each(function() { + layer.selectAll('path.surface').each(function () { opacities.push(this.style.opacity); }); expect(opacities).toEqual(exp, msg); // editType:style - if(msg !== 'base') { + if (msg !== 'base') { expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); expect(gd._fullData[0]._module.plot).toHaveBeenCalledTimes(0); } @@ -1068,42 +1106,44 @@ describe('Test sunburst restyle:', function() { } 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); + .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); }); - it('should be able to restyle *textinfo* with various *insidetextorientation*', function(done) { + it('should be able to restyle *textinfo* with various *insidetextorientation*', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - text: ['node0', 'node1', 'node2', 'node3'], - values: [0, 1, 2, 3] - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + text: ['node0', 'node1', 'node2', 'node3'], + values: [0, 1, 2, 3] + } + ] }; function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.sunburstlayer'); var tx = []; - layer.selectAll('text.slicetext').each(function() { + layer.selectAll('text.slicetext').each(function () { var lines = d3Select(this).selectAll('tspan'); - if(lines.size()) { + if (lines.size()) { var t = []; - lines.each(function() { + lines.each(function () { t.push(this.innerHTML); }); tx.push(t.join('\n')); @@ -1115,63 +1155,63 @@ describe('Test sunburst restyle:', function() { expect(tx).toEqual(exp, msg); // editType:plot - if(msg !== 'base') { + if (msg !== 'base') { expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); } }; } Plotly.newPlot(gd, mock) - .then(_assert('base', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - .then(function() { - spyOn(Plots, 'doCalcdata').and.callThrough(); - }) - .then(_restyle({textinfo: 'label'})) - .then(_assert('just label', ['Root', 'B', 'A', 'b'])) - .then(_restyle({textinfo: 'value'})) - .then(_assert('show input values', ['0', '2', '1', '3'])) - .then(_restyle({textinfo: 'none'})) - .then(_assert('no textinfo', ['', '', '', ''])) - .then(_restyle({textinfo: 'label+text+value'})) - .then(_assert('show everything', ['Root\n0\nnode0', 'B\n2\nnode2', 'A\n1\nnode1', 'b\n3\nnode3'])) - .then(_restyle({textinfo: null})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - // now change insidetextorientation to 'horizontal' - .then(_restyle({insidetextorientation: 'horizontal'})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - .then(_restyle({textinfo: 'none'})) - .then(_assert('no textinfo', ['', '', '', ''])) - .then(_restyle({textinfo: null})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - // now change insidetextorientation to 'tangential' - .then(_restyle({insidetextorientation: 'tangential'})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - .then(_restyle({textinfo: 'none'})) - .then(_assert('no textinfo', ['', '', '', ''])) - .then(_restyle({textinfo: null})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - // now change insidetextorientation to 'radial' - .then(_restyle({insidetextorientation: 'radial'})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - .then(_restyle({textinfo: 'none'})) - .then(_assert('no textinfo', ['', '', '', ''])) - .then(_restyle({textinfo: null})) - .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) - .then(done, done.fail); + .then(_assert('base', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + .then(function () { + spyOn(Plots, 'doCalcdata').and.callThrough(); + }) + .then(_restyle({ textinfo: 'label' })) + .then(_assert('just label', ['Root', 'B', 'A', 'b'])) + .then(_restyle({ textinfo: 'value' })) + .then(_assert('show input values', ['0', '2', '1', '3'])) + .then(_restyle({ textinfo: 'none' })) + .then(_assert('no textinfo', ['', '', '', ''])) + .then(_restyle({ textinfo: 'label+text+value' })) + .then(_assert('show everything', ['Root\n0\nnode0', 'B\n2\nnode2', 'A\n1\nnode1', 'b\n3\nnode3'])) + .then(_restyle({ textinfo: null })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + // now change insidetextorientation to 'horizontal' + .then(_restyle({ insidetextorientation: 'horizontal' })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + .then(_restyle({ textinfo: 'none' })) + .then(_assert('no textinfo', ['', '', '', ''])) + .then(_restyle({ textinfo: null })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + // now change insidetextorientation to 'tangential' + .then(_restyle({ insidetextorientation: 'tangential' })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + .then(_restyle({ textinfo: 'none' })) + .then(_assert('no textinfo', ['', '', '', ''])) + .then(_restyle({ textinfo: null })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + // now change insidetextorientation to 'radial' + .then(_restyle({ insidetextorientation: 'radial' })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + .then(_restyle({ textinfo: 'none' })) + .then(_assert('no textinfo', ['', '', '', ''])) + .then(_restyle({ textinfo: null })) + .then(_assert('back to dflt', ['Root\nnode0', 'B\nnode2', 'A\nnode1', 'b\nnode3'])) + .then(done, done.fail); }); }); -describe('Test sunburst tweening:', function() { +describe('Test sunburst tweening:', function () { var gd; var pathTweenFnLookup; var textTweenFnLookup; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); // hacky way to track tween functions - spyOn(d3Transition.prototype, 'attrTween').and.callFake(function(attrName, fn) { - var lookup = {d: pathTweenFnLookup, transform: textTweenFnLookup}[attrName]; + spyOn(d3Transition.prototype, 'attrTween').and.callFake(function (attrName, fn) { + var lookup = { d: pathTweenFnLookup, transform: textTweenFnLookup }[attrName]; var pt = this[0][0].__data__; var id = pt.data.data.id; @@ -1200,7 +1240,7 @@ describe('Test sunburst tweening:', function() { } function _assert(msg, attrName, id, exp, tolerance) { - var lookup = {d: pathTweenFnLookup, transform: textTweenFnLookup}[attrName]; + var lookup = { d: pathTweenFnLookup, transform: textTweenFnLookup }[attrName]; var fn = lookup[id]; // normalize time in [0, 1] where we'll assert the tweening fn output, // asserting at the mid point *should be* representative enough @@ -1208,149 +1248,209 @@ describe('Test sunburst tweening:', function() { var actual = trim(fn(t)); var msg2 = msg + ' | node ' + id + ' @t=' + t; - if(attrName === 'transform') { - var fake = {attr: function() { return actual; }}; + if (attrName === 'transform') { + var fake = { + attr: function () { + return actual; + } + }; var xy = Drawing.getTranslate(fake); expect([xy.x, xy.y]).toBeWithinArray(exp, tolerance || 2, msg2); } else { - // we could maybe to bring in: - // https://github.com/hughsk/svg-path-parser - // to make these assertions more readable + // we could maybe to bring in: + // https://github.com/hughsk/svg-path-parser + // to make these assertions more readable expect(actual).toBe(trim(exp), msg2); } } - it('should tween sector exit/update (case: click on branch, no maxdepth)', function(done) { + it('should tween sector exit/update (case: click on branch, no maxdepth)', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'] - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 3)) - .then(function() { - _assert('exit entry radially inward', 'd', 'Root', - 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + - 'M372.5,235 A22.5,22.50,1,1327.5,235 A22.5,22.50,1,1372.5,235Z' - ); - _assert('exit A clockwise', 'd', 'A', - 'M395,235 L440,235 A90,900,0,0350,145 L350,190 A45,450,0,1395,235Z' - ); - _assert('update B to new position', 'd', 'B', - 'M350,212.5 L350,156.25 A78.75,78.750,1,0428.75,235.00000000000003' + - 'L372.5,235 A22.5,22.50,1,1350,212.5Z' - ); - _assert('update b to new position', 'd', 'b', - 'M350,156.25 L350,100 A135,1350,1,0485,235.00000000000003 L428.75,235.00000000000003' + - 'A78.75,78.750,1,1350,156.25Z' - ); - _assert('move B text to new position', 'transform', 'B', [313.45, 275.54]); - _assert('move b text to new position', 'transform', 'b', [274.42, 314.57]); - }) - .then(done, done.fail); + .then(_run(gd, 3)) + .then(function () { + _assert( + 'exit entry radially inward', + 'd', + 'Root', + 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + + 'M372.5,235 A22.5,22.50,1,1327.5,235 A22.5,22.50,1,1372.5,235Z' + ); + _assert( + 'exit A clockwise', + 'd', + 'A', + 'M395,235 L440,235 A90,900,0,0350,145 L350,190 A45,450,0,1395,235Z' + ); + _assert( + 'update B to new position', + 'd', + 'B', + 'M350,212.5 L350,156.25 A78.75,78.750,1,0428.75,235.00000000000003' + + 'L372.5,235 A22.5,22.50,1,1350,212.5Z' + ); + _assert( + 'update b to new position', + 'd', + 'b', + 'M350,156.25 L350,100 A135,1350,1,0485,235.00000000000003 L428.75,235.00000000000003' + + 'A78.75,78.750,1,1350,156.25Z' + ); + _assert('move B text to new position', 'transform', 'B', [313.45, 275.54]); + _assert('move b text to new position', 'transform', 'b', [274.42, 314.57]); + }) + .then(done, done.fail); }); - it('should tween sector enter/update (case: click on entry, no maxdepth)', function(done) { + it('should tween sector enter/update (case: click on entry, no maxdepth)', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - level: 'B' - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + level: 'B' + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 1)) - .then(function() { - _assert('enter new entry radially outward', 'd', 'Root', - 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + - 'M372.5,235 A22.5,22.50,1,1327.5,235 A22.5,22.50,1,1372.5,235Z' - ); - _assert('enter A counterclockwise', 'd', 'A', - 'M395,235 L440,235 A90,900,0,0350,145 L350,190 A45,450,0,1395,235Z' - ); - _assert('update B to new position', 'd', 'B', - 'M350,212.5 L350,156.25 A78.75,78.750,1,0428.75,235.00000000000003' + - 'L372.5,235 A22.5,22.50,1,1350,212.5Z' - ); - _assert('update b to new position', 'd', 'b', - 'M350,156.25 L350,100 A135,1350,1,0485,235.00000000000003 L428.75,235.00000000000003' + - 'A78.75,78.750,1,1350,156.25Z' - ); - _assert('move B text to new position', 'transform', 'B', [316.85, 272.14]); - _assert('move b text to new position', 'transform', 'b', [274.42, 314.57]); - }) - .then(done, done.fail); + .then(_run(gd, 1)) + .then(function () { + _assert( + 'enter new entry radially outward', + 'd', + 'Root', + 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + + 'M372.5,235 A22.5,22.50,1,1327.5,235 A22.5,22.50,1,1372.5,235Z' + ); + _assert( + 'enter A counterclockwise', + 'd', + 'A', + 'M395,235 L440,235 A90,900,0,0350,145 L350,190 A45,450,0,1395,235Z' + ); + _assert( + 'update B to new position', + 'd', + 'B', + 'M350,212.5 L350,156.25 A78.75,78.750,1,0428.75,235.00000000000003' + + 'L372.5,235 A22.5,22.50,1,1350,212.5Z' + ); + _assert( + 'update b to new position', + 'd', + 'b', + 'M350,156.25 L350,100 A135,1350,1,0485,235.00000000000003 L428.75,235.00000000000003' + + 'A78.75,78.750,1,1350,156.25Z' + ); + _assert('move B text to new position', 'transform', 'B', [316.85, 272.14]); + _assert('move b text to new position', 'transform', 'b', [274.42, 314.57]); + }) + .then(done, done.fail); }); - it('should tween sector enter/update/exit (case: click on entry, maxdepth=2)', function(done) { + it('should tween sector enter/update/exit (case: click on entry, maxdepth=2)', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - maxdepth: 2 - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + maxdepth: 2 + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 3)) - .then(function() { - _assert('exit entry radially inward', 'd', 'Root', - 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + - 'M383.75,235 A33.75,33.750,1,1316.25,235A33.75,33.750,1,1383.75,235Z' - ); - _assert('exit A clockwise', 'd', 'A', - 'M417.5,235 L485,235 A135,1350,0,0350,100 L350,167.5 A67.5,67.50,0,1417.5,235Z' - ); - _assert('update B to new position', 'd', 'B', - 'M350,201.25 L350,133.75 A101.25,101.250,1,0451.25,235.00000000000003' + - 'L383.75,235 A33.75,33.750,1,1350,201.25Z' - ); - _assert('enter b for parent position', 'd', 'b', - 'M350,133.75 L350,100 A135,1350,0,0350,370 L350,336.25 A101.25,101.250,0,1350,133.75Z' - ); - _assert('move B text to new position', 'transform', 'B', [303.01, 285.98]); - _assert('enter b text to new position', 'transform', 'b', [248.75, 239]); - }) - .then(done, done.fail); + .then(_run(gd, 3)) + .then(function () { + _assert( + 'exit entry radially inward', + 'd', + 'Root', + 'M350,235 A0,00,1,0350,235 A0,00,1,0350,235Z' + + 'M383.75,235 A33.75,33.750,1,1316.25,235A33.75,33.750,1,1383.75,235Z' + ); + _assert( + 'exit A clockwise', + 'd', + 'A', + 'M417.5,235 L485,235 A135,1350,0,0350,100 L350,167.5 A67.5,67.50,0,1417.5,235Z' + ); + _assert( + 'update B to new position', + 'd', + 'B', + 'M350,201.25 L350,133.75 A101.25,101.250,1,0451.25,235.00000000000003' + + 'L383.75,235 A33.75,33.750,1,1350,201.25Z' + ); + _assert( + 'enter b for parent position', + 'd', + 'b', + 'M350,133.75 L350,100 A135,1350,0,0350,370 L350,336.25 A101.25,101.250,0,1350,133.75Z' + ); + _assert('move B text to new position', 'transform', 'B', [303.01, 285.98]); + _assert('enter b text to new position', 'transform', 'b', [248.75, 239]); + }) + .then(done, done.fail); }); - it('should tween sector enter/update/exit (case: click on entry, maxdepth=2, level=B)', function(done) { + it('should tween sector enter/update/exit (case: click on entry, maxdepth=2, level=B)', function (done) { var mock = { - data: [{ - type: 'sunburst', - labels: ['Root', 'A', 'B', 'b', 'bb'], - parents: ['', 'Root', 'Root', 'B', 'b'], - maxdepth: 2, - level: 'B' - }] + data: [ + { + type: 'sunburst', + labels: ['Root', 'A', 'B', 'b', 'bb'], + parents: ['', 'Root', 'Root', 'B', 'b'], + maxdepth: 2, + level: 'B' + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 1)) - .then(function() { - _assert('exit b radially outward and to parent sector angle', 'd', 'b', - 'M350,133.75L350,100A135,1350,1,0485,235.00000000000003' + - 'L451.25,235.00000000000003A101.25,101.250,1,1350,133.75Z' - ); - _assert('enter new entry radially outward', 'd', 'Root', - 'M350,235A0,00,1,0350,235A0,00,1,0350,235Z' + - 'M383.75,235A33.75,33.750,1,1316.25,235A33.75,33.750,1,1383.75,235Z' - ); - _assert('enter A counterclockwise', 'd', 'A', - 'M417.5,235L485,235A135,1350,0,0350,100L350,167.5A67.5,67.50,0,1417.5,235Z' - ); - _assert('update B to new position', 'd', 'B', - 'M350,201.25L350,133.75A101.25,101.250,1,0451.25,235.00000000000003' + - 'L383.75,235A33.75,33.750,1,1350,201.25Z' - ); - }) - .then(done, done.fail); + .then(_run(gd, 1)) + .then(function () { + _assert( + 'exit b radially outward and to parent sector angle', + 'd', + 'b', + 'M350,133.75L350,100A135,1350,1,0485,235.00000000000003' + + 'L451.25,235.00000000000003A101.25,101.250,1,1350,133.75Z' + ); + _assert( + 'enter new entry radially outward', + 'd', + 'Root', + 'M350,235A0,00,1,0350,235A0,00,1,0350,235Z' + + 'M383.75,235A33.75,33.750,1,1316.25,235A33.75,33.750,1,1383.75,235Z' + ); + _assert( + 'enter A counterclockwise', + 'd', + 'A', + 'M417.5,235L485,235A135,1350,0,0350,100L350,167.5A67.5,67.50,0,1417.5,235Z' + ); + _assert( + 'update B to new position', + 'd', + 'B', + 'M350,201.25L350,133.75A101.25,101.250,1,0451.25,235.00000000000003' + + 'L383.75,235A33.75,33.750,1,1350,201.25Z' + ); + }) + .then(done, done.fail); }); /* @@ -1382,180 +1482,581 @@ describe('Test sunburst tweening:', function() { }); */ - it('should update text position during transition using *auto* insidetextorientation', function(done) { + it('should update text position during transition using *auto* insidetextorientation', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'auto' - }] - }) - .then(_run(gd, 4)) - .then(function() { - _assert('move J text to new position', 'transform', 'J', [309.3085305481173, 202.66937078300114]); - _assert('move O text to new position', 'transform', 'O', [337.158534264498, 162.57550532369754], 5); - _assert('move U text to new position', 'transform', 'U', [416.1153793700712, 163.4078137147134]); - _assert('move V text to new position', 'transform', 'V', [471.63745793297295, 218.00377184475153]); - _assert('move W text to new position', 'transform', 'W', [455.10235209157037, 177.717459723826], 5); - _assert('move X text to new position', 'transform', 'X', [431.0320488371527, 145.88885474402548]); - _assert('move Y text to new position', 'transform', 'Y', [395.12660928295867, 124.11350635624726]); - _assert('move Z text to new position', 'transform', 'Z', [354.1550374068844, 115.63596810986363]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'auto' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 4)) + .then(function () { + _assert('move J text to new position', 'transform', 'J', [309.3085305481173, 202.66937078300114]); + _assert('move O text to new position', 'transform', 'O', [337.158534264498, 162.57550532369754], 5); + _assert('move U text to new position', 'transform', 'U', [416.1153793700712, 163.4078137147134]); + _assert('move V text to new position', 'transform', 'V', [471.63745793297295, 218.00377184475153]); + _assert('move W text to new position', 'transform', 'W', [455.10235209157037, 177.717459723826], 5); + _assert('move X text to new position', 'transform', 'X', [431.0320488371527, 145.88885474402548]); + _assert('move Y text to new position', 'transform', 'Y', [395.12660928295867, 124.11350635624726]); + _assert('move Z text to new position', 'transform', 'Z', [354.1550374068844, 115.63596810986363]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *horizontal* insidetextorientation', function(done) { + it('should update text position during transition using *horizontal* insidetextorientation', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'horizontal' - }] - }) - .then(_run(gd, 4)) - .then(function() { - _assert('move J text to new position', 'transform', 'J', [350, 185.9244172266002]); - _assert('move O text to new position', 'transform', 'O', [350.1640625, 162.2952497427013]); - _assert('move U text to new position', 'transform', 'U', [416.1153793700712, 163.4078137147134]); - _assert('move V text to new position', 'transform', 'V', [471.63745793297295, 218.00377184475153]); - _assert('move W text to new position', 'transform', 'W', [457.21539566810236, 178.44157384259557]); - _assert('move X text to new position', 'transform', 'X', [431.0320488371527, 145.88885474402548]); - _assert('move Y text to new position', 'transform', 'Y', [395.12660928295867, 124.11350635624726]); - _assert('move Z text to new position', 'transform', 'Z', [354.1550374068844, 115.63596810986363]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'horizontal' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 4)) + .then(function () { + _assert('move J text to new position', 'transform', 'J', [350, 185.9244172266002]); + _assert('move O text to new position', 'transform', 'O', [350.1640625, 162.2952497427013]); + _assert('move U text to new position', 'transform', 'U', [416.1153793700712, 163.4078137147134]); + _assert('move V text to new position', 'transform', 'V', [471.63745793297295, 218.00377184475153]); + _assert('move W text to new position', 'transform', 'W', [457.21539566810236, 178.44157384259557]); + _assert('move X text to new position', 'transform', 'X', [431.0320488371527, 145.88885474402548]); + _assert('move Y text to new position', 'transform', 'Y', [395.12660928295867, 124.11350635624726]); + _assert('move Z text to new position', 'transform', 'Z', [354.1550374068844, 115.63596810986363]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *tangential* insidetextorientation', function(done) { + it('should update text position during transition using *tangential* insidetextorientation', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'tangential' - }] - }) - .then(_run(gd, 4)) - .then(function() { - _assert('move J text to new position', 'transform', 'J', [350, 185.9244172266002]); - _assert('move O text to new position', 'transform', 'O', [350.1640625, 162.3617907020963]); - _assert('move U text to new position', 'transform', 'U', [387.0665312800944, 146.39132446549587]); - _assert('move V text to new position', 'transform', 'V', [467.5637172232141, 214.71357776223093]); - _assert('move W text to new position', 'transform', 'W', [453.6883022471187, 176.23118240799604]); - _assert('move X text to new position', 'transform', 'X', [428.32070483274055, 145.007590714263]); - _assert('move Y text to new position', 'transform', 'Y', [393.6173101979463, 123.958130483835]); - _assert('move Z text to new position', 'transform', 'Z', [359.52567880729003, 116.05583257124167]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'tangential' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 4)) + .then(function () { + _assert('move J text to new position', 'transform', 'J', [350, 185.9244172266002]); + _assert('move O text to new position', 'transform', 'O', [350.1640625, 162.3617907020963]); + _assert('move U text to new position', 'transform', 'U', [387.0665312800944, 146.39132446549587]); + _assert('move V text to new position', 'transform', 'V', [467.5637172232141, 214.71357776223093]); + _assert('move W text to new position', 'transform', 'W', [453.6883022471187, 176.23118240799604]); + _assert('move X text to new position', 'transform', 'X', [428.32070483274055, 145.007590714263]); + _assert('move Y text to new position', 'transform', 'Y', [393.6173101979463, 123.958130483835]); + _assert('move Z text to new position', 'transform', 'Z', [359.52567880729003, 116.05583257124167]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *radial* insidetextorientation', function(done) { + it('should update text position during transition using *radial* insidetextorientation', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'radial' - }] - }) - .then(_run(gd, 4)) - .then(function() { - _assert('move J text to new position', 'transform', 'J', [298.18238454231454, 239]); - _assert('move O text to new position', 'transform', 'O', [299.00421744782363, 183.7721980352468]); - _assert('move U text to new position', 'transform', 'U', [418.6530444037927, 162.19895218157865]); - _assert('move V text to new position', 'transform', 'V', [471.8671910181962, 218.0219264868202]); - _assert('move W text to new position', 'transform', 'W', [459.0093083790858, 178.21113754411613]); - _assert('move X text to new position', 'transform', 'X', [433.74669513154777, 144.8536840385141]); - _assert('move Y text to new position', 'transform', 'Y', [398.67767996405655, 121.9940236084775]); - _assert('move Z text to new position', 'transform', 'Z', [354.00770212095256, 116.19286557341015]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'radial' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 4)) + .then(function () { + _assert('move J text to new position', 'transform', 'J', [298.18238454231454, 239]); + _assert('move O text to new position', 'transform', 'O', [299.00421744782363, 183.7721980352468]); + _assert('move U text to new position', 'transform', 'U', [418.6530444037927, 162.19895218157865]); + _assert('move V text to new position', 'transform', 'V', [471.8671910181962, 218.0219264868202]); + _assert('move W text to new position', 'transform', 'W', [459.0093083790858, 178.21113754411613]); + _assert('move X text to new position', 'transform', 'X', [433.74669513154777, 144.8536840385141]); + _assert('move Y text to new position', 'transform', 'Y', [398.67767996405655, 121.9940236084775]); + _assert('move Z text to new position', 'transform', 'Z', [354.00770212095256, 116.19286557341015]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *radial* insidetextorientation with level', function(done) { + it('should update text position during transition using *radial* insidetextorientation with level', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'radial', - level: 'O', - }] - }) - .then(_run(gd, 2)) - .then(function() { - _assert('move U text to new position', 'transform', 'U', [317.71031126211744, 202.23522389350774]); - _assert('move V text to new position', 'transform', 'V', [444.88381073744586, 191.14358863479603]); - _assert('move W text to new position', 'transform', 'W', [365.5485731154604, 134.6827081871288]); - _assert('move X text to new position', 'transform', 'X', [277.7815763779703, 162.7705278345142]); - _assert('move Y text to new position', 'transform', 'Y', [247.47466543373307, 255.288278237516]); - _assert('move Z text to new position', 'transform', 'Z', [300.75324430542196, 332.0135787956955]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'radial', + level: 'O' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 2)) + .then(function () { + _assert('move U text to new position', 'transform', 'U', [317.71031126211744, 202.23522389350774]); + _assert('move V text to new position', 'transform', 'V', [444.88381073744586, 191.14358863479603]); + _assert('move W text to new position', 'transform', 'W', [365.5485731154604, 134.6827081871288]); + _assert('move X text to new position', 'transform', 'X', [277.7815763779703, 162.7705278345142]); + _assert('move Y text to new position', 'transform', 'Y', [247.47466543373307, 255.288278237516]); + _assert('move Z text to new position', 'transform', 'Z', [300.75324430542196, 332.0135787956955]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *tangential* insidetextorientation with level', function(done) { + it('should update text position during transition using *tangential* insidetextorientation with level', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'tangential', - level: 'O', - }] - }) - .then(_run(gd, 2)) - .then(function() { - _assert('move U text to new position', 'transform', 'U', [313.79288001914836, 202.45694251914836]); - _assert('move V text to new position', 'transform', 'V', [441.011030377721, 188.63633201157208]); - _assert('move W text to new position', 'transform', 'W', [382.1346244328249, 135.0126788235936], 5); - _assert('move X text to new position', 'transform', 'X', [277.7815763779703, 162.7705278345142]); - _assert('move Y text to new position', 'transform', 'Y', [249.73412124927503, 271.78420776316403]); - _assert('move Z text to new position', 'transform', 'Z', [305.39156336654094, 331.3597434293286]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'tangential', + level: 'O' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 2)) + .then(function () { + _assert('move U text to new position', 'transform', 'U', [313.79288001914836, 202.45694251914836]); + _assert('move V text to new position', 'transform', 'V', [441.011030377721, 188.63633201157208]); + _assert('move W text to new position', 'transform', 'W', [382.1346244328249, 135.0126788235936], 5); + _assert('move X text to new position', 'transform', 'X', [277.7815763779703, 162.7705278345142]); + _assert('move Y text to new position', 'transform', 'Y', [249.73412124927503, 271.78420776316403]); + _assert('move Z text to new position', 'transform', 'Z', [305.39156336654094, 331.3597434293286]); + }) + .then(done, done.fail); }); - it('should update text position during transition using *horizontal* insidetextorientation with level', function(done) { + it('should update text position during transition using *horizontal* insidetextorientation with level', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - textinfo: 'label', - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], - parents: ['', 'A', 'A', 'C', 'C', 'C', 'F', 'F', 'F', 'F', 'J', 'J', 'J', 'J', 'J', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'U', 'U'], - insidetextorientation: 'horizontal', - level: 'O', - }] - }) - .then(_run(gd, 2)) - .then(function() { - _assert('move U text to new position', 'transform', 'U', [313.79288001914836, 202.45694251914836]); - _assert('move V text to new position', 'transform', 'V', [445.2341347726318, 190.47976534033592]); - _assert('move W text to new position', 'transform', 'W', [366.3829959511747, 133.44080859889465]); - _assert('move X text to new position', 'transform', 'X', [274.43577526068776, 163.42796276068773]); - _assert('move Y text to new position', 'transform', 'Y', [244.44862109889465, 255.71893345117468]); - _assert('move Z text to new position', 'transform', 'Z', [301.6438278403359, 334.2263222726318]); + data: [ + { + type: 'sunburst', + textinfo: 'label', + labels: [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ], + parents: [ + '', + 'A', + 'A', + 'C', + 'C', + 'C', + 'F', + 'F', + 'F', + 'F', + 'J', + 'J', + 'J', + 'J', + 'J', + 'O', + 'O', + 'O', + 'O', + 'O', + 'O', + 'U', + 'U', + 'U', + 'U', + 'U', + 'U' + ], + insidetextorientation: 'horizontal', + level: 'O' + } + ] }) - .then(done, done.fail); + .then(_run(gd, 2)) + .then(function () { + _assert('move U text to new position', 'transform', 'U', [313.79288001914836, 202.45694251914836]); + _assert('move V text to new position', 'transform', 'V', [445.2341347726318, 190.47976534033592]); + _assert('move W text to new position', 'transform', 'W', [366.3829959511747, 133.44080859889465]); + _assert('move X text to new position', 'transform', 'X', [274.43577526068776, 163.42796276068773]); + _assert('move Y text to new position', 'transform', 'Y', [244.44862109889465, 255.71893345117468]); + _assert('move Z text to new position', 'transform', 'Z', [301.6438278403359, 334.2263222726318]); + }) + .then(done, done.fail); }); }); -describe('Test sunburst interactions edge cases', function() { +describe('Test sunburst interactions edge cases', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); - it('should keep tracking hover labels and hover events after *calc* edits', function(done) { + it('should keep tracking hover labels and hover events after *calc* edits', function (done) { var mock = Lib.extendFlat({}, require('../../image/mocks/sunburst_first.json')); var hoverCnt = 0; var unhoverCnt = 0; @@ -1574,334 +2075,677 @@ describe('Test sunburst interactions edge cases', function() { } Plotly.newPlot(gd, mock) - .then(function() { - gd.on('plotly_hover', function() { - hoverCnt++; - // N.B. trigger a 'plot' edit - Plotly.restyle(gd, 'textinfo', 'none'); - }); - gd.on('plotly_unhover', function() { - unhoverCnt++; - // N.B. trigger a 'plot' edit - Plotly.restyle(gd, 'textinfo', null); - }); - }) - .then(hover(gd, 1)) - .then(function() { - _assert('after hovering on first sector', { - hoverCnt: 1, - unhoverCnt: 0, - hoverLabel: 1 - }); - }) - .then(unhover(gd, 1)) - .then(function() { - _assert('after un-hovering from first sector', { - hoverCnt: 0, - unhoverCnt: 1, - hoverLabel: 0 - }); - }) - .then(hover(gd, 2)) - .then(function() { - _assert('after hovering onto second sector', { - hoverCnt: 1, - unhoverCnt: 0, - hoverLabel: 1 - }); - }) - .then(done, done.fail); + .then(function () { + gd.on('plotly_hover', function () { + hoverCnt++; + // N.B. trigger a 'plot' edit + Plotly.restyle(gd, 'textinfo', 'none'); + }); + gd.on('plotly_unhover', function () { + unhoverCnt++; + // N.B. trigger a 'plot' edit + Plotly.restyle(gd, 'textinfo', null); + }); + }) + .then(hover(gd, 1)) + .then(function () { + _assert('after hovering on first sector', { + hoverCnt: 1, + unhoverCnt: 0, + hoverLabel: 1 + }); + }) + .then(unhover(gd, 1)) + .then(function () { + _assert('after un-hovering from first sector', { + hoverCnt: 0, + unhoverCnt: 1, + hoverLabel: 0 + }); + }) + .then(hover(gd, 2)) + .then(function () { + _assert('after hovering onto second sector', { + hoverCnt: 1, + unhoverCnt: 0, + hoverLabel: 1 + }); + }) + .then(done, done.fail); }); - it('should show falsy zero text', function(done) { + it('should show falsy zero text', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - parents: ['', 'A', 'B', 'C', 'D', 'E', 'F'], - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], - values: [7, 6, 5, 4, 3, 2, 1], - text: [null, '', '0', 0, 1, true, false], - textinfo: 'label+text+value' - }], + data: [ + { + type: 'sunburst', + parents: ['', 'A', 'B', 'C', 'D', 'E', 'F'], + labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], + values: [7, 6, 5, 4, 3, 2, 1], + text: [null, '', '0', 0, 1, true, false], + textinfo: 'label+text+value' + } + ], layout: { width: 400, height: 400 } }) - .then(hover(gd, 4)) - .then(function() { - assertHoverLabelContent({ nums: 'D\n4\n0' }); - }) - .then(done, done.fail); + .then(hover(gd, 4)) + .then(function () { + assertHoverLabelContent({ nums: 'D\n4\n0' }); + }) + .then(done, done.fail); }); - it('should transition sunburst traces only', function(done) { + it('should transition sunburst traces only', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/display-text_zero-number.json')); mock.data[0].visible = false; function _assert(msg, exp) { var gd3 = d3Select(gd); - expect(gd3.select('.cartesianlayer').selectAll('.trace').size()) - .toBe(exp.cartesianTraceCnt, '# of cartesian traces'); - expect(gd3.select('.pielayer').selectAll('.trace').size()) - .toBe(exp.pieTraceCnt, '# of pie traces'); - expect(gd3.select('.sunburstlayer').selectAll('.trace').size()) - .toBe(exp.sunburstTraceCnt, '# of sunburst traces'); + expect(gd3.select('.cartesianlayer').selectAll('.trace').size()).toBe( + exp.cartesianTraceCnt, + '# of cartesian traces' + ); + expect(gd3.select('.pielayer').selectAll('.trace').size()).toBe(exp.pieTraceCnt, '# of pie traces'); + expect(gd3.select('.sunburstlayer').selectAll('.trace').size()).toBe( + exp.sunburstTraceCnt, + '# of sunburst traces' + ); } Plotly.newPlot(gd, mock) - .then(function() { - _assert('base', { - cartesianTraceCnt: 2, - pieTraceCnt: 0, - sunburstTraceCnt: 1 - }); - }) - .then(click(gd, 2)) - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(function() { - _assert('after sunburst click', { - cartesianTraceCnt: 2, - pieTraceCnt: 0, - sunburstTraceCnt: 1 - }); - }) - .then(done, done.fail); + .then(function () { + _assert('base', { + cartesianTraceCnt: 2, + pieTraceCnt: 0, + sunburstTraceCnt: 1 + }); + }) + .then(click(gd, 2)) + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then(function () { + _assert('after sunburst click', { + cartesianTraceCnt: 2, + pieTraceCnt: 0, + sunburstTraceCnt: 1 + }); + }) + .then(done, done.fail); }); - it('should be able to transition sunburst traces via `Plotly.react`', function(done) { + it('should be able to transition sunburst traces via `Plotly.react`', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/display-text_zero-number.json')); - mock.layout.transition = {duration: 200}; + mock.layout.transition = { duration: 200 }; spyOn(Plots, 'transitionFromReact').and.callThrough(); Plotly.newPlot(gd, mock) - .then(function() { - gd.data[1].level = 'B'; - return Plotly.react(gd, gd.data, gd.layout); - }) - .then(delay(202)) - .then(function() { - expect(Plots.transitionFromReact).toHaveBeenCalledTimes(1); - }) - .then(done, done.fail); + .then(function () { + gd.data[1].level = 'B'; + return Plotly.react(gd, gd.data, gd.layout); + }) + .then(delay(202)) + .then(function () { + expect(Plots.transitionFromReact).toHaveBeenCalledTimes(1); + }) + .then(done, done.fail); }); }); -describe('Test sunburst texttemplate without `values` should work at root level:', function() { - checkTextTemplate([{ - type: 'sunburst', - 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']], +describe('Test sunburst texttemplate without `values` should work at root level:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + 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}', - 'value: %{value}', + [ + '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}', - '%{percentParent} of %{parent}', - 'color: %{color}' + [ + '%{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: 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' + [ + '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 sunburst texttemplate with *total* `values` should work at root level:', function() { - checkTextTemplate([{ - type: 'sunburst', - 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']], +describe('Test sunburst texttemplate with *total* `values` should work at root level:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + 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}', - 'text: %{text}', + [ + '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}', - '%{percentParent} of %{parent}', - 'color: %{color}' + [ + '%{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: Eve', - 'text: fourteen', - 'value: 12', - '9% of Eve', - '15% of Eve', - '3% of Eve', - '6% of Eve', - '17% of Awan', - 'color: #9467bd' + [ + '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('Test sunburst texttemplate with *remainder* `values` should work at root level:', function() { - checkTextTemplate([{ - type: 'sunburst', - branchvalues: 'remainder', - 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', '20% of Eve', '12% of Eve', '6% of Eve', '5% of Eve', '3% of Eve', '8% of Eve', '2% of Eve', '1% of Eve']], - ['%{percentEntry} of %{entry}', ['100% of Eve', '20% of Eve', '12% of Eve', '6% of Eve', '5% of Eve', '3% of Eve', '8% of Eve', '2% of Eve', '1% of Eve']], - ['%{percentParent} of %{parent}', ['%{percentParent} of %{parent}', '20% of Eve', '12% of Eve', '6% of Eve', '5% of Eve', '3% of Eve', '42% of Seth', '8% of Seth', '14% of Awan']], +describe('Test sunburst texttemplate with *remainder* `values` should work at root level:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + branchvalues: 'remainder', + 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}', - 'text: %{text}', + [ + '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', + '20% of Eve', + '12% of Eve', + '6% of Eve', + '5% of Eve', + '3% of Eve', + '8% of Eve', + '2% of Eve', + '1% of Eve' + ] + ], + [ '%{percentEntry} of %{entry}', + [ + '100% of Eve', + '20% of Eve', + '12% of Eve', + '6% of Eve', + '5% of Eve', + '3% of Eve', + '8% of Eve', + '2% of Eve', + '1% of Eve' + ] + ], + [ '%{percentParent} of %{parent}', - '%{percentParent} of %{parent}', - '%{percentParent} of %{parent}', - 'color: %{color}' + [ + '%{percentParent} of %{parent}', + '20% of Eve', + '12% of Eve', + '6% of Eve', + '5% of Eve', + '3% of Eve', + '42% of Seth', + '8% of Seth', + '14% of Awan' + ] ], [ - 'label: Eve', - 'text: fourteen', - 'value: 12', - '6% of Eve', - '5% of Eve', - '8% of Eve', - '2% of Eve', - '14% of Awan', - 'color: #9467bd' + [ + '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', + '6% of Eve', + '5% of Eve', + '8% of Eve', + '2% of Eve', + '14% of Awan', + 'color: #9467bd' + ] ] ] - ]); + ); }); -describe('Test sunburst texttemplate without `values` should work when *level* is set:', function() { - checkTextTemplate([{ - type: 'sunburst', - level: 'Seth', - 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: #1f77b4', 'color: #1f77b4', 'color: #1f77b4']], - ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], - ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], - ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], - ['%{percentRoot} of %{root}', ['33% of Eve', '17% of Eve', '17% of Eve']], - ['%{percentEntry} of %{entry}', ['100% of Seth', '50% of Seth', '50% of Seth']], - ['%{percentParent} of %{parent}', ['33% of Eve', '50% of Seth', '50% of Seth']], - ], /* skipEtra */ true); +describe('Test sunburst texttemplate without `values` should work when *level* is set:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + level: 'Seth', + 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: #1f77b4', 'color: #1f77b4', 'color: #1f77b4']], + ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], + ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], + ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], + ['%{percentRoot} of %{root}', ['33% of Eve', '17% of Eve', '17% of Eve']], + ['%{percentEntry} of %{entry}', ['100% of Seth', '50% of Seth', '50% of Seth']], + ['%{percentParent} of %{parent}', ['33% of Eve', '50% of Seth', '50% of Seth']] + ], + /* skipEtra */ true + ); }); -describe('Test sunburst texttemplate with *total* `values` should work when *level* is set:', function() { - checkTextTemplate([{ - type: 'sunburst', - level: 'Seth', - 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: #ff7f0e', 'color: #ff7f0e', 'color: #ff7f0e']], - ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], - ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], - ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], - ['%{percentRoot} of %{root}', ['18% of Eve', '15% of Eve', '3% of Eve']], - ['%{percentEntry} of %{entry}', ['100% of Seth', '83% of Seth', '17% of Seth']], - ['%{percentParent} of %{parent}', ['18% of Eve', '83% of Seth', '17% of Seth']], - ], /* skipEtra */ true); +describe('Test sunburst texttemplate with *total* `values` should work when *level* is set:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + level: 'Seth', + 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: #ff7f0e', 'color: #ff7f0e', 'color: #ff7f0e']], + ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], + ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], + ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], + ['%{percentRoot} of %{root}', ['18% of Eve', '15% of Eve', '3% of Eve']], + ['%{percentEntry} of %{entry}', ['100% of Seth', '83% of Seth', '17% of Seth']], + ['%{percentParent} of %{parent}', ['18% of Eve', '83% of Seth', '17% of Seth']] + ], + /* skipEtra */ true + ); }); -describe('Test sunburst texttemplate with *remainder* `values` should work when *level* is set:', function() { - checkTextTemplate([{ - type: 'sunburst', - level: 'Seth', - branchvalues: 'remainder', - 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: #1f77b4', 'color: #1f77b4', 'color: #1f77b4']], - ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], - ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], - ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], - ['%{percentRoot} of %{root}', ['20% of Eve', '8% of Eve', '2% of Eve']], - ['%{percentEntry} of %{entry}', ['100% of Seth', '42% of Seth', '8% of Seth']], - ['%{percentParent} of %{parent}', ['20% of Eve', '42% of Seth', '8% of Seth']], - ], /* skipEtra */ true); +describe('Test sunburst texttemplate with *remainder* `values` should work when *level* is set:', function () { + checkTextTemplate( + [ + { + type: 'sunburst', + level: 'Seth', + branchvalues: 'remainder', + 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: #1f77b4', 'color: #1f77b4', 'color: #1f77b4']], + ['label: %{label}', ['label: Seth', 'label: Enos', 'label: Noam']], + ['text: %{text}', ['text: twelve', 'text: ten', 'text: two']], + ['path: %{currentPath}', ['path: Eve/', 'path: Eve/Seth', 'path: Eve/Seth/']], + ['%{percentRoot} of %{root}', ['20% of Eve', '8% of Eve', '2% of Eve']], + ['%{percentEntry} of %{entry}', ['100% of Seth', '42% of Seth', '8% of Seth']], + ['%{percentParent} of %{parent}', ['20% of Eve', '42% of Seth', '8% of Seth']] + ], + /* skipEtra */ true + ); }); -describe('sunburst inside text orientation', function() { +describe('sunburst inside text orientation', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); function assertTextRotations(msg, opts) { - return function() { + return function () { var selection = d3SelectAll(SLICES_TEXT_SELECTOR); var size = selection.size(); - ['rotations'].forEach(function(e) { + ['rotations'].forEach(function (e) { expect(size).toBe(opts[e].length, 'length for ' + e + ' does not match with the number of elements'); }); - for(var i = 0; i < selection[0].length; i++) { + for (var i = 0; i < selection[0].length; i++) { var transform = selection[0][i].getAttribute('transform'); var pos0 = transform.indexOf('rotate('); var rotate = 0; - if(pos0 !== -1) { + if (pos0 !== -1) { pos0 += 'rotate('.length; var pos1 = transform.indexOf(')', pos0); - rotate = +(transform.substring(pos0, pos1)); + rotate = +transform.substring(pos0, pos1); } expect(opts.rotations[i]).toBeCloseTo(rotate, -1, 'rotation for element ' + i, msg); @@ -1909,26 +2753,23 @@ describe('sunburst inside text orientation', function() { }; } - it('should be able to react to new insidetextorientation option', function(done) { + it('should be able to react to new insidetextorientation option', function (done) { var fig = { - data: [{ - type: 'sunburst', - parents: ['', '', '', ''], - labels: [64, 32, 16, 8], - values: [64, 32, 16, 8], - sort: false, - - text: [ - 'very long label', - 'label', - 'long label', - '+' - ], - - textinfo: 'text', - textposition: 'inside', - showlegend: false - }], + data: [ + { + type: 'sunburst', + parents: ['', '', '', ''], + labels: [64, 32, 16, 8], + values: [64, 32, 16, 8], + sort: false, + + text: ['very long label', 'label', 'long label', '+'], + + textinfo: 'text', + textposition: 'inside', + showlegend: false + } + ], layout: { width: 300, height: 300 @@ -1936,73 +2777,83 @@ describe('sunburst inside text orientation', function() { }; Plotly.newPlot(gd, fig) - .then(assertTextRotations('using default "auto"', { - rotations: [-0.6, 0, 48, 0] - })) - .then(function() { - fig.data[0].insidetextorientation = 'horizontal'; - return Plotly.react(gd, fig); - }) - .then(assertTextRotations('using "horizontal"', { - rotations: [0, 0, 0, 0] - })) - .then(function() { - fig.data[0].insidetextorientation = 'radial'; - return Plotly.react(gd, fig); - }) - .then(assertTextRotations('using "radial"', { - rotations: [84, -60, 48, 12] - })) - .then(function() { - fig.data[0].insidetextorientation = 'tangential'; - return Plotly.react(gd, fig); - }) - .then(assertTextRotations('using "tangential"', { - rotations: [0, 30, -42, -78] - })) - .then(function() { - fig.data[0].insidetextorientation = 'auto'; - return Plotly.react(gd, fig); - }) - .then(assertTextRotations('back to "auto"', { - rotations: [-0.6, 0, 48, 0] - })) - .then(done, done.fail); + .then( + assertTextRotations('using default "auto"', { + rotations: [-0.6, 0, 48, 0] + }) + ) + .then(function () { + fig.data[0].insidetextorientation = 'horizontal'; + return Plotly.react(gd, fig); + }) + .then( + assertTextRotations('using "horizontal"', { + rotations: [0, 0, 0, 0] + }) + ) + .then(function () { + fig.data[0].insidetextorientation = 'radial'; + return Plotly.react(gd, fig); + }) + .then( + assertTextRotations('using "radial"', { + rotations: [84, -60, 48, 12] + }) + ) + .then(function () { + fig.data[0].insidetextorientation = 'tangential'; + return Plotly.react(gd, fig); + }) + .then( + assertTextRotations('using "tangential"', { + rotations: [0, 30, -42, -78] + }) + ) + .then(function () { + fig.data[0].insidetextorientation = 'auto'; + return Plotly.react(gd, fig); + }) + .then( + assertTextRotations('back to "auto"', { + rotations: [-0.6, 0, 48, 0] + }) + ) + .then(done, done.fail); }); }); -describe('sunburst uniformtext', function() { +describe('sunburst uniformtext', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); function assertTextSizes(msg, opts) { - return function() { + return function () { var selection = d3SelectAll(SLICES_TEXT_SELECTOR); var size = selection.size(); - ['fontsizes', 'scales'].forEach(function(e) { + ['fontsizes', 'scales'].forEach(function (e) { expect(size).toBe(opts[e].length, 'length for ' + e + ' does not match with the number of elements'); }); - selection.each(function(d, i) { + selection.each(function (d, i) { var fontSize = this.style.fontSize; expect(fontSize).toBe(opts.fontsizes[i] + 'px', 'fontSize for element ' + i, msg); }); - for(var i = 0; i < selection[0].length; i++) { + for (var i = 0; i < selection[0].length; i++) { var transform = selection[0][i].getAttribute('transform'); var pos0 = transform.indexOf('scale('); var scale = 1; - if(pos0 !== -1) { + if (pos0 !== -1) { pos0 += 'scale('.length; var pos1 = transform.indexOf(')', pos0); - scale = +(transform.substring(pos0, pos1)); + scale = +transform.substring(pos0, pos1); } expect(opts.scales[i]).toBeCloseTo(scale, 1, 'scale for element ' + i, msg); @@ -2010,29 +2861,20 @@ describe('sunburst uniformtext', function() { }; } - it('should be able to react with new uniform text options', function(done) { + it('should be able to react with new uniform text options', function (done) { var fig = { - data: [{ - type: 'sunburst', - parents: ['', '', '', '', '', '', '', '', '', ''], - labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - values: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - - text: [ - 0, - '
', - null, - '', - ' ', - '.', - '+', - '=', - '$', - 'very long lablel' - ], + data: [ + { + type: 'sunburst', + parents: ['', '', '', '', '', '', '', '', '', ''], + labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + values: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + + text: [0, '
', null, '', ' ', '.', '+', '=', '$', 'very long lablel'], - textinfo: 'text' - }], + textinfo: 'text' + } + ], layout: { width: 300, height: 300 @@ -2040,96 +2882,112 @@ describe('sunburst uniformtext', function() { }; Plotly.newPlot(gd, fig) - .then(assertTextSizes('without uniformtext', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.52], - })) - .then(function() { - fig.layout.uniformtext = {mode: 'hide'}; // default with minsize=0 - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using mode: "hide"', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52], - })) - .then(function() { - fig.layout.uniformtext.minsize = 9; // set a minsize less than trace font size - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using minsize: 9', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - })) - .then(function() { - fig.layout.uniformtext.minsize = 32; // set a minsize greater than trace font size - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using minsize: 32', { - fontsizes: [32, 32, 32, 32, 32, 32, 32, 32, 32, 32], - scales: [0, 1, 1, 1, 1, 1, 0, 0, 0, 0], - })) - .then(function() { - fig.layout.uniformtext.minsize = 16; // set a minsize greater than trace font size - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using minsize: 16', { - fontsizes: [16, 16, 16, 16, 16, 16, 16, 16, 16, 16], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - })) - .then(function() { - fig.layout.uniformtext.mode = 'show'; - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using mode: "show"', { - fontsizes: [16, 16, 16, 16, 16, 16, 16, 16, 16, 16], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - })) - .then(function() { - fig.layout.uniformtext = undefined; // back to default - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('clear uniformtext', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.52], - })) - .then(done, done.fail); + .then( + assertTextSizes('without uniformtext', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.52] + }) + ) + .then(function () { + fig.layout.uniformtext = { mode: 'hide' }; // default with minsize=0 + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using mode: "hide"', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52, 0.52] + }) + ) + .then(function () { + fig.layout.uniformtext.minsize = 9; // set a minsize less than trace font size + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using minsize: 9', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0] + }) + ) + .then(function () { + fig.layout.uniformtext.minsize = 32; // set a minsize greater than trace font size + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using minsize: 32', { + fontsizes: [32, 32, 32, 32, 32, 32, 32, 32, 32, 32], + scales: [0, 1, 1, 1, 1, 1, 0, 0, 0, 0] + }) + ) + .then(function () { + fig.layout.uniformtext.minsize = 16; // set a minsize greater than trace font size + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using minsize: 16', { + fontsizes: [16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0] + }) + ) + .then(function () { + fig.layout.uniformtext.mode = 'show'; + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using mode: "show"', { + fontsizes: [16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + }) + ) + .then(function () { + fig.layout.uniformtext = undefined; // back to default + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('clear uniformtext', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0.52] + }) + ) + .then(done, done.fail); }); - it('should uniform text scales after transition', function(done) { + it('should uniform text scales after transition', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'sunburst', - parents: [ - '', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform' - ], - labels: [ - 'Oscar', - 'Papa', - 'Quebec', - 'Romeo and Juliet', - 'Sierra', - 'Tango', - 'Uniform', - 'ViKtor Korchnoi - Anatoly Karpov', - 'Whiskey', - 'X ray', - 'Yankee', - 'Zulu' - ], - textinfo: 'label' - }], + data: [ + { + type: 'sunburst', + parents: [ + '', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform' + ], + labels: [ + 'Oscar', + 'Papa', + 'Quebec', + 'Romeo and Juliet', + 'Sierra', + 'Tango', + 'Uniform', + 'ViKtor Korchnoi - Anatoly Karpov', + 'Whiskey', + 'X ray', + 'Yankee', + 'Zulu' + ], + textinfo: 'label' + } + ], layout: { width: 500, height: 500, @@ -2139,22 +2997,28 @@ describe('sunburst uniformtext', function() { } } }) - .then(assertTextSizes('before click', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], - })) - .then(click(gd, 2)) // click on Uniform - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(assertTextSizes('after click child', { - fontsizes: [12, 12, 12, 12, 12, 12], - scales: [1, 0, 1, 1, 1, 1], - })) - .then(click(gd, 1)) // click on Oscar - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(assertTextSizes('after click parent', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], - })) - .then(done, done.fail); + .then( + assertTextSizes('before click', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1] + }) + ) + .then(click(gd, 2)) // click on Uniform + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then( + assertTextSizes('after click child', { + fontsizes: [12, 12, 12, 12, 12, 12], + scales: [1, 0, 1, 1, 1, 1] + }) + ) + .then(click(gd, 1)) // click on Oscar + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then( + assertTextSizes('after click parent', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1] + }) + ) + .then(done, done.fail); }); }); diff --git a/test/jasmine/tests/treemap_test.js b/test/jasmine/tests/treemap_test.js index d70ad287b31..953a7d60ebb 100644 --- a/test/jasmine/tests/treemap_test.js +++ b/test/jasmine/tests/treemap_test.js @@ -13,7 +13,6 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var mouseEvent = require('../assets/mouse_event'); var delay = require('../assets/delay'); - var customAssertions = require('../assets/custom_assertions'); var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle; var assertHoverLabelContent = customAssertions.assertHoverLabelContent; @@ -23,15 +22,15 @@ var SLICES_SELECTOR = '.treemaplayer path.surface'; var SLICES_TEXT_SELECTOR = '.treemaplayer text.slicetext'; function _mouseEvent(type, gd, v) { - return function() { - if(Array.isArray(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}); + mouseEvent(type, 0, 0, { element: el }); } }; } @@ -48,7 +47,7 @@ function click(gd, v) { return _mouseEvent('click', gd, v); } -describe('Test treemap defaults:', function() { +describe('Test treemap defaults:', function () { var gd; var fullData; @@ -56,8 +55,8 @@ describe('Test treemap defaults:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'treemap'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'treemap' }, o || {}); }); gd.layout = layout || {}; @@ -65,23 +64,19 @@ describe('Test treemap defaults:', function() { fullData = gd._fullData; } - it('should set *visible:false* when *labels* or *parents* is missing', function() { - _supply([ - {labels: [1], parents: ['']}, - {labels: [1]}, - {parents: ['']} - ]); + 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() { + 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]} + { labels: [1], parents: [''] }, + { labels: [1], parents: [''], values: [] }, + { labels: [1], parents: [''], values: [1] } ]); expect(fullData[0].count).toBe('leaves'); @@ -89,69 +84,70 @@ describe('Test treemap defaults:', function() { expect(fullData[2].count).toBe(undefined, 'has values'); }); - it('should not coerce *branchvalues* when *values* is not set', function() { + it('should not coerce *branchvalues* when *values* is not set', function () { _supply([ - {labels: [1], parents: [''], values: [1]}, - {labels: [1], parents: ['']}, + { 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' - }); + 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() { + 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: ['']} + { 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 *marker.depthfade* depending on *marker.colors* is present or not', function() { + it('should default *marker.depthfade* depending on *marker.colors* is present or not', function () { _supply([ - {labels: ['A', 'B', 'a'], parents: ['', '', 'A']}, - {labels: ['A', 'B', 'a'], parents: ['', '', 'A'], marker: {colors: ['red', 'green', 'blue']}} + { labels: ['A', 'B', 'a'], parents: ['', '', 'A'] }, + { labels: ['A', 'B', 'a'], parents: ['', '', 'A'], marker: { colors: ['red', 'green', 'blue'] } } ]); expect(fullData[0].marker.depthfade).toBe(true); expect(fullData[1].marker.depthfade).toBe(false); }); - it('should not coerce *marker.depthfade* when a *colorscale* is present', function() { - _supply([ - {labels: [1], parents: [''], marker: {colorscale: 'Blues'}} - ]); + it('should not coerce *marker.depthfade* when a *colorscale* is present', function () { + _supply([{ labels: [1], parents: [''], marker: { colorscale: 'Blues' } }]); expect(fullData[0].marker.depthfade).toBe(undefined); }); - it('should use *textfont.size* to adjust top, bottom , left and right *marker.pad* defaults', function() { + it('should use *textfont.size* to adjust top, bottom , left and right *marker.pad* defaults', function () { _supply([ - {labels: [1], parents: ['']}, - {labels: [1], parents: [''], textfont: {size: 24}}, - {labels: [1], parents: [''], textposition: 'bottom left'}, - {labels: [1], parents: [''], textposition: 'bottom center'}, - {labels: [1], parents: [''], textposition: 'bottom right'}, - {labels: [1], parents: [''], textposition: 'middle left'}, - {labels: [1], parents: [''], textposition: 'middle center'}, - {labels: [1], parents: [''], textposition: 'middle right'}, - {labels: [1], parents: [''], textposition: 'top left'}, - {labels: [1], parents: [''], textposition: 'tpo center'}, - {labels: [1], parents: [''], textposition: 'top right'} + { labels: [1], parents: [''] }, + { labels: [1], parents: [''], textfont: { size: 24 } }, + { labels: [1], parents: [''], textposition: 'bottom left' }, + { labels: [1], parents: [''], textposition: 'bottom center' }, + { labels: [1], parents: [''], textposition: 'bottom right' }, + { labels: [1], parents: [''], textposition: 'middle left' }, + { labels: [1], parents: [''], textposition: 'middle center' }, + { labels: [1], parents: [''], textposition: 'middle right' }, + { labels: [1], parents: [''], textposition: 'top left' }, + { labels: [1], parents: [''], textposition: 'tpo center' }, + { labels: [1], parents: [''], textposition: 'top right' } ]); expect(fullData[0].textfont.size).toBe(12); @@ -167,57 +163,83 @@ describe('Test treemap defaults:', function() { expect(fullData[1].marker.pad.b).toBe(12, 'half of increased textfont.size'); var i; - for(i = 0 + 2; i < 3 + 2; i++) { - expect(fullData[i].marker.pad.t).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.l).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.r).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.b).toBe(24, 'twice of default textfont.size', 'with textposition:' + fullData[i].textposition); + for (i = 0 + 2; i < 3 + 2; i++) { + expect(fullData[i].marker.pad.t).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.l).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.r).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.b).toBe( + 24, + 'twice of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); } - for(i = 0 + 5; i < 6 + 5; i++) { - expect(fullData[i].marker.pad.t).toBe(24, 'twice of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.l).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.r).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); - expect(fullData[i].marker.pad.b).toBe(6, 'half of default textfont.size', 'with textposition:' + fullData[i].textposition); + for (i = 0 + 5; i < 6 + 5; i++) { + expect(fullData[i].marker.pad.t).toBe( + 24, + 'twice of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.l).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.r).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); + expect(fullData[i].marker.pad.b).toBe( + 6, + 'half of default textfont.size', + 'with textposition:' + fullData[i].textposition + ); } }); - it('should not include "text" flag in *textinfo* when *text* is set', function() { + it('should not include "text" flag in *textinfo* when *text* is set', function () { _supply([ - {labels: [1], parents: [''], text: ['A']}, - {labels: [1], parents: ['']} + { 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 *treemapcolorway*', function() { - _supply([ - {labels: [1], parents: ['']} - ], { + it('should use *layout.colorway* as dflt for *treemapcolorway*', function () { + _supply([{ labels: [1], parents: [''] }], { colorway: ['red', 'blue', 'green'] }); - expect(gd._fullLayout.treemapcolorway) - .toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); + expect(gd._fullLayout.treemapcolorway).toEqual(['red', 'blue', 'green'], 'dflt to layout colorway'); - _supply([ - {labels: [1], parents: ['']} - ], { + _supply([{ labels: [1], parents: [''] }], { colorway: ['red', 'blue', 'green'], treemapcolorway: ['cyan', 'yellow', 'black'] }); - expect(gd._fullLayout.treemapcolorway) - .toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); + expect(gd._fullLayout.treemapcolorway).toEqual(['cyan', 'yellow', 'black'], 'user-defined value'); }); - it('should only coerce *squarifyratio* when *tiling.packing* is *squarify*', function() { + it('should only coerce *squarifyratio* when *tiling.packing* is *squarify*', function () { _supply([ - {labels: [1], parents: ['']}, - {labels: [1], parents: [''], tiling: {packing: 'binary'}}, - {labels: [1], parents: [''], tiling: {packing: 'slice'}}, - {labels: [1], parents: [''], tiling: {packing: 'dice'}}, - {labels: [1], parents: [''], tiling: {packing: 'slice-dice'}}, - {labels: [1], parents: [''], tiling: {packing: 'dice-slice'}} + { labels: [1], parents: [''] }, + { labels: [1], parents: [''], tiling: { packing: 'binary' } }, + { labels: [1], parents: [''], tiling: { packing: 'slice' } }, + { labels: [1], parents: [''], tiling: { packing: 'dice' } }, + { labels: [1], parents: [''], tiling: { packing: 'slice-dice' } }, + { labels: [1], parents: [''], tiling: { packing: 'dice-slice' } } ]); expect(fullData[0].tiling.squarifyratio).toBe(1); @@ -228,10 +250,8 @@ describe('Test treemap defaults:', function() { expect(fullData[5].tiling.squarifyratio).toBe(undefined, 'no squarify'); }); - it('should not coerce *pathbar* attributes when *pathbar.visible* is false', function() { - _supply([ - {labels: [1], parents: [''], pathbar: {visible: false}} - ]); + it('should not coerce *pathbar* attributes when *pathbar.visible* is false', function () { + _supply([{ labels: [1], parents: [''], pathbar: { visible: false } }]); expect(fullData[0].pathbar.visible).toBe(false); expect(fullData[0].pathbar.textfont).toBe(undefined); @@ -240,18 +260,14 @@ describe('Test treemap defaults:', function() { expect(fullData[0].pathbar.edgeshape).toBe(undefined); }); - it('should set *pathbar.visible* to true by default', function() { - _supply([ - {labels: [1], parents: ['']} - ]); + it('should set *pathbar.visible* to true by default', function () { + _supply([{ labels: [1], parents: [''] }]); expect(fullData[0].pathbar.visible).toBe(true); }); - it('should set *pathbar.visible* to true by default', function() { - _supply([ - {labels: [1], parents: ['']} - ]); + it('should set *pathbar.visible* to true by default', function () { + _supply([{ labels: [1], parents: [''] }]); expect(fullData[0].pathbar.textfont.family).toBe('"Open Sans", verdana, arial, sans-serif'); expect(fullData[0].pathbar.textfont.color).toBe(undefined); @@ -261,11 +277,9 @@ describe('Test treemap defaults:', function() { expect(fullData[0].pathbar.edgeshape).toBe('>'); }); - it('should default *pathbar* sizes and styles to layout', function() { - _supply([ - {labels: [1], parents: ['']} - ], { - font: {family: 'Times New Romans', color: '#ABC', size: 24} + it('should default *pathbar* sizes and styles to layout', function () { + _supply([{ labels: [1], parents: [''] }], { + font: { family: 'Times New Romans', color: '#ABC', size: 24 } }); expect(fullData[0].pathbar.textfont.family).toBe('Times New Romans'); @@ -274,36 +288,38 @@ describe('Test treemap defaults:', function() { expect(fullData[0].pathbar.thickness).toBe(30); }); - it('should not default *marker.colorscale* when *marker.colors* is not present', function() { - _supply([ - {labels: [1], parents: ['']} - ]); + 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() { + it('should default *marker.colorscale* to *Reds* when *marker.colors* is present', function () { _supply([ - {labels: [1], parents: [''], marker: { - colors: [0] - }} + { + 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)' ] + [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 treemap calc:', function() { +describe('Test treemap calc:', function () { var gd; - beforeEach(function() { + beforeEach(function () { spyOn(Lib, 'warn'); }); @@ -311,8 +327,8 @@ describe('Test treemap calc:', function() { gd = {}; opts = Array.isArray(opts) ? opts : [opts]; - gd.data = opts.map(function(o) { - return Lib.extendFlat({type: 'treemap'}, o || {}); + gd.data = opts.map(function (o) { + return Lib.extendFlat({ type: 'treemap' }, o || {}); }); gd.layout = layout || {}; @@ -321,22 +337,24 @@ describe('Test treemap calc:', function() { } function extract(k) { - var out = gd.calcdata.map(function(cd) { - return cd.map(function(pt) { return pt[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) { + var out = gd.calcdata.map(function (cd) { + return cd[0].hierarchy.descendants().map(function (pt) { return pt[k]; }); }); return out.length > 1 ? out : out[0]; } - it('should generate *id* when it can', function() { + it('should generate *id* when it can', function () { _calc({ labels: ['Root', 'A', 'B', 'b'], parents: ['', 'Root', 'Root', 'B'] @@ -346,9 +364,9 @@ describe('Test treemap calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should generate "implied root" when it can', function() { + it('should generate "implied root" when it can', function () { _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['Root', 'Root', 'B'] }); @@ -358,23 +376,25 @@ describe('Test treemap calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when there are multiple implied roots', function() { + it('should warn when there are multiple implied roots', function () { _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['Root1', 'Root22', 'B'] }); expect(Lib.warn).toHaveBeenCalledTimes(1); - expect(Lib.warn).toHaveBeenCalledWith('Multiple implied roots, cannot build treemap hierarchy of trace 0. These roots include: Root1, Root22'); + expect(Lib.warn).toHaveBeenCalledWith( + 'Multiple implied roots, cannot build treemap 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() { + it('should generate "root of roots" when it can', function () { + spyOn(Lib, 'randstr').and.callFake(function () { return 'dummy'; }); _calc({ - labels: [ 'A', 'B', 'b'], + labels: ['A', 'B', 'b'], parents: ['', '', 'B'] }); @@ -383,16 +403,16 @@ describe('Test treemap calc:', function() { expect(extract('label')).toEqual(['', 'A', 'B', 'b']); }); - it('should compute hierarchy values', function() { + 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'} + { 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([ @@ -405,7 +425,7 @@ describe('Test treemap calc:', function() { expect(Lib.warn).toHaveBeenCalledTimes(0); }); - it('should warn when values under *branchvalues:total* do not add up and not show trace', function() { + 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'], @@ -416,11 +436,15 @@ describe('Test treemap calc:', function() { 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'); + 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() { + it('should warn labels/parents lead to ambiguous hierarchy', function () { _calc({ labels: ['Root', 'A', 'A', 'B'], parents: ['', 'Root', 'Root', 'A'] @@ -430,7 +454,7 @@ describe('Test treemap calc:', function() { expect(Lib.warn).toHaveBeenCalledWith('Failed to build treemap hierarchy of trace 0. Error: ambiguous: A'); }); - it('should warn ids/parents lead to ambiguous hierarchy', function() { + 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'], @@ -441,7 +465,7 @@ describe('Test treemap calc:', function() { expect(Lib.warn).toHaveBeenCalledWith('Failed to build treemap hierarchy of trace 0. Error: ambiguous: b'); }); - it('should accept numbers (even `0`) are ids/parents items', function() { + 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], @@ -452,7 +476,7 @@ describe('Test treemap calc:', function() { expect(extract('pid')).toEqual(['', '0', '0', '2', '2', '0', '0', '6', '0']); }); - it('should accept mix typed are ids/parents items', function() { + 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], @@ -463,7 +487,7 @@ describe('Test treemap calc:', function() { expect(extract('pid')).toEqual(['', 'true', 'true', '2', '2', 'true', 'true', '6', 'true']); }); - it('should use *marker.colors*', function() { + 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'], @@ -483,7 +507,7 @@ describe('Test treemap calc:', function() { expect(cd[8].color).toEqual('rgba(255, 255, 255, 1)'); }); - it('should use *marker.colors* numbers with default colorscale', function() { + 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'], @@ -503,7 +527,7 @@ describe('Test treemap calc:', function() { expect(cd[8].color).toEqual('rgb(178, 10, 28)'); }); - it('should use *marker.colors* numbers with desired colorscale', function() { + 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'], @@ -523,7 +547,7 @@ describe('Test treemap calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use *marker.colors* numbers not values with colorscale', function() { + 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' }, @@ -544,7 +568,7 @@ describe('Test treemap calc:', function() { expect(cd[8].color).toEqual('rgb(217, 30, 30)'); }); - it('should use values with colorscale when *marker.colors* in empty', function() { + 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' }, @@ -566,29 +590,33 @@ describe('Test treemap calc:', function() { }); }); -describe('Test treemap plot:', function() { +describe('Test treemap plot:', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); - it('should return early from the plot when there is no entry', function(done) { - Plotly.newPlot(gd, [{ - labels: ['a', 'b'], - parents: ['A', 'B'], - type: 'treemap' - }]) - .then(function() { - var gd3 = d3Select(gd); - var element = gd3.select('.treemap trace').node(); - expect(element).toBe(null); - }) - .then(done, done.fail); + it('should return early from the plot when there is no entry', function (done) { + Plotly.newPlot(gd, [ + { + labels: ['a', 'b'], + parents: ['A', 'B'], + type: 'treemap' + } + ]) + .then(function () { + var gd3 = d3Select(gd); + var element = gd3.select('.treemap trace').node(); + expect(element).toBe(null); + }) + .then(done, done.fail); }); }); -describe('Test treemap hover:', function() { +describe('Test treemap hover:', function () { var gd; var labels0 = ['Eve', 'Cain', 'Seth', 'Enos', 'Noam', 'Abel', 'Awan', 'Enoch', 'Azura']; @@ -600,31 +628,36 @@ describe('Test treemap hover:', function() { function run(spec) { gd = createGraphDiv(); - var data = (spec.traces || [{}]).map(function(t) { + var data = (spec.traces || [{}]).map(function (t) { t.type = 'treemap'; - if(!t.labels) t.labels = labels0.slice(); - if(!t.parents) t.parents = parents0.slice(); + 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 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(function () { + gd.once('plotly_hover', function (d) { + ptData = d.points[0]; + }); }) .then(hover(gd, spec.pos)) - .then(function() { + .then(function () { assertHoverLabelContent(exp.label); - for(var k in exp.ptData) { + for (var k in exp.ptData) { expect(ptData[k]).toBe(exp.ptData[k], 'pt event data key ' + k); } @@ -634,315 +667,430 @@ describe('Test treemap hover:', function() { expect(typeof ptData.bbox.y0).toEqual('number'); expect(typeof ptData.bbox.y1).toEqual('number'); - if(exp.style) { + 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: 'base', + pos: 2, + exp: { + label: { + nums: 'Seth' + }, + ptData: { + curveNumber: 0, + pointNumber: 2, + label: 'Seth', + 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 scalar hovertext', + traces: [{ hovertext: 'A' }], + pos: 3, + exp: { + label: { + nums: 'Cain\nA' + }, + ptData: { + curveNumber: 0, + pointNumber: 1, + label: 'Cain', + 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 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 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 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 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 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 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' + }, + { + 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 } } - }], - 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: '' + }, + { + 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) { + ].forEach(function (spec) { + it('should generate correct hover labels and event data - ' + spec.desc, function (done) { run(spec).then(done, done.fail); }); }); }); -describe('Test treemap hover with and without levels', function() { +describe('Test treemap hover with and without levels', function () { var gd; - var labels0 = ['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']; - var parents0 = ['', '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']; - var values0 = [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]; - var text0 = ['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']; + var labels0 = [ + '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' + ]; + var parents0 = [ + '', + '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' + ]; + var values0 = [ + 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 + ]; + var text0 = [ + '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' + ]; afterEach(destroyGraphDiv); function run(spec) { gd = createGraphDiv(); - var data = (spec.traces || [{}]).map(function(t) { + var data = (spec.traces || [{}]).map(function (t) { t.type = 'treemap'; t.text = text0; t.values = values0; t.level = spec.level; t.branchvalues = 'total'; - t.hovertemplate = 'path = %{currentPath}
label = %{label}
text = %{text}
value = %{value}
ratio to %{parent} = %{percentParent}
ratio to %{entry} = %{percentEntry}
ratio to %{root} = %{percentRoot}'; + t.hovertemplate = + 'path = %{currentPath}
label = %{label}
text = %{text}
value = %{value}
ratio to %{parent} = %{percentParent}
ratio to %{entry} = %{percentEntry}
ratio to %{root} = %{percentRoot}'; - if(!t.labels) t.labels = labels0.slice(); - if(!t.parents) t.parents = parents0.slice(); + 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 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(function () { + gd.once('plotly_hover', function (d) { + ptData = d.points[0]; + }); }) .then(hover(gd, spec.pos)) - .then(function() { + .then(function () { assertHoverLabelContent(exp.label); - for(var k in exp.ptData) { + for (var k in exp.ptData) { expect(ptData[k]).toBe(exp.ptData[k], 'pt event data key ' + k); } - if(exp.style) { + if (exp.style) { var gd3 = d3Select(gd); assertHoverLabelStyle(gd3.select('.hovertext'), exp.style); } }); } - [{ - desc: 'entry', - level: 'X ray', - pos: 0, - exp: { - label: { - name: 'trace 0', - nums: 'path = Alpha/Charlie/Foxtrot/Juliet/Oscar/Uniform/\nlabel = X ray\ntext = two\nvalue = 2\nratio to Uniform = 0.2\nratio to X ray = 1\nratio to Alpha = 0.05', - }, - ptData: { - curveNumber: 0, - pointNumber: 23, - label: 'X ray', - parent: 'Uniform' + [ + { + desc: 'entry', + level: 'X ray', + pos: 0, + exp: { + label: { + name: 'trace 0', + nums: 'path = Alpha/Charlie/Foxtrot/Juliet/Oscar/Uniform/\nlabel = X ray\ntext = two\nvalue = 2\nratio to Uniform = 0.2\nratio to X ray = 1\nratio to Alpha = 0.05' + }, + ptData: { + curveNumber: 0, + pointNumber: 23, + label: 'X ray', + parent: 'Uniform' + } } - } - }, { - desc: 'entry', - level: 'Oscar', - pos: 0, - exp: { - label: { - name: 'trace 0', - nums: 'path = Alpha/Charlie/Foxtrot/Juliet/\nlabel = Oscar\ntext = twenty\nvalue = 20\nratio to Juliet = 0.7142857142857143\nratio to Oscar = 1\nratio to Alpha = 0.5', - }, - ptData: { - curveNumber: 0, - pointNumber: 14, - label: 'Oscar', - parent: 'Juliet' + }, + { + desc: 'entry', + level: 'Oscar', + pos: 0, + exp: { + label: { + name: 'trace 0', + nums: 'path = Alpha/Charlie/Foxtrot/Juliet/\nlabel = Oscar\ntext = twenty\nvalue = 20\nratio to Juliet = 0.7142857142857143\nratio to Oscar = 1\nratio to Alpha = 0.5' + }, + ptData: { + curveNumber: 0, + pointNumber: 14, + label: 'Oscar', + parent: 'Juliet' + } } - } - }, { - desc: 'leaf', - level: 'Oscar', - pos: 10, - exp: { - label: { - name: 'trace 0', - nums: 'path = Alpha/Charlie/Foxtrot/Juliet/Oscar/Uniform/\nlabel = X ray\ntext = two\nvalue = 2\nratio to Uniform = 0.2\nratio to Oscar = 0.1\nratio to Alpha = 0.05', - }, - ptData: { - curveNumber: 0, - pointNumber: 23, - label: 'X ray', - parent: 'Uniform' + }, + { + desc: 'leaf', + level: 'Oscar', + pos: 10, + exp: { + label: { + name: 'trace 0', + nums: 'path = Alpha/Charlie/Foxtrot/Juliet/Oscar/Uniform/\nlabel = X ray\ntext = two\nvalue = 2\nratio to Uniform = 0.2\nratio to Oscar = 0.1\nratio to Alpha = 0.05' + }, + ptData: { + curveNumber: 0, + pointNumber: 23, + label: 'X ray', + parent: 'Uniform' + } } - } - }, { - desc: 'entry', - level: undefined, // root - pos: 0, - exp: { - label: { - name: 'trace 0', - nums: 'path = /\nlabel = Alpha\ntext = forty\nvalue = 40\nratio to = 1\nratio to Alpha = 1\nratio to Alpha = 1', - }, - ptData: { - curveNumber: 0, - pointNumber: 0, - label: 'Alpha', - parent: '' + }, + { + desc: 'entry', + level: undefined, // root + pos: 0, + exp: { + label: { + name: 'trace 0', + nums: 'path = /\nlabel = Alpha\ntext = forty\nvalue = 40\nratio to = 1\nratio to Alpha = 1\nratio to Alpha = 1' + }, + ptData: { + curveNumber: 0, + pointNumber: 0, + label: 'Alpha', + parent: '' + } } - } - }, { - desc: 'leaf', - level: undefined, // root - pos: 10, - exp: { - label: { - name: 'trace 0', - nums: 'path = Alpha/Charlie/Foxtrot/\nlabel = Golf\ntext = one\nvalue = 1\nratio to Foxtrot = 0.029411764705882353\nratio to Alpha = 0.025\nratio to Alpha = 0.025', - }, - ptData: { - curveNumber: 0, - pointNumber: 6, - label: 'Golf', - parent: 'Foxtrot' + }, + { + desc: 'leaf', + level: undefined, // root + pos: 10, + exp: { + label: { + name: 'trace 0', + nums: 'path = Alpha/Charlie/Foxtrot/\nlabel = Golf\ntext = one\nvalue = 1\nratio to Foxtrot = 0.029411764705882353\nratio to Alpha = 0.025\nratio to Alpha = 0.025' + }, + ptData: { + curveNumber: 0, + pointNumber: 6, + label: 'Golf', + parent: 'Foxtrot' + } } } - }] - .forEach(function(spec) { - it('should generate correct hover labels and event data - ' + spec.desc + ' with level:' + spec.level, function(done) { - run(spec).then(done, done.fail); - }); + ].forEach(function (spec) { + it( + 'should generate correct hover labels and event data - ' + spec.desc + ' with level:' + spec.level, + function (done) { + run(spec).then(done, done.fail); + } + ); }); }); -describe('Test treemap hover lifecycle:', function() { +describe('Test treemap hover lifecycle:', function () { var gd; var hoverData; var unhoverData; var hoverCnt; var unhoverCnt; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); @@ -952,62 +1100,62 @@ describe('Test treemap hover lifecycle:', function() { hoverCnt = 0; unhoverCnt = 0; - return function() { - gd.on('plotly_hover', function(d) { + return function () { + gd.on('plotly_hover', function (d) { hoverData = d; hoverCnt++; }); - gd.on('plotly_unhover', function(d) { + gd.on('plotly_unhover', function (d) { unhoverData = d; unhoverCnt++; }); }; } - it('should fire the correct events', function(done) { + it('should fire the correct events', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(hover(gd, 1)) - .then(function() { - if(hoverCnt === 1) { - expect(hoverData.event).toBeDefined(); - expect(hoverData.points[0].label).toBe('Eve'); - } else { - fail('did not trigger correct # of plotly_hover events'); - } + .then(setupListeners()) + .then(hover(gd, 1)) + .then(function () { + if (hoverCnt === 1) { + expect(hoverData.event).toBeDefined(); + expect(hoverData.points[0].label).toBe('Eve'); + } else { + fail('did not trigger correct # of plotly_hover events'); + } - if(unhoverCnt) { - fail('should not have triggered plotly_unhover'); - } - }) - .then(unhover(gd, 1)) - .then(hover(gd, 2)) - .then(function() { - if(hoverCnt === 2) { - expect(hoverData.event).toBeDefined(); - expect(hoverData.points[0].label).toBe('Seth'); - } else { - fail('did not trigger correct # of plotly_hover events'); - } + if (unhoverCnt) { + fail('should not have triggered plotly_unhover'); + } + }) + .then(unhover(gd, 1)) + .then(hover(gd, 2)) + .then(function () { + if (hoverCnt === 2) { + expect(hoverData.event).toBeDefined(); + expect(hoverData.points[0].label).toBe('Seth'); + } else { + fail('did not trigger correct # of plotly_hover events'); + } - if(unhoverCnt === 1) { - expect(unhoverData.event).toBeDefined(); - expect(unhoverData.points[0].label).toBe('Eve'); - } else { - fail('did not trigger correct # of plotly_unhover events'); - } - }) - .then(done, done.fail); + if (unhoverCnt === 1) { + expect(unhoverData.event).toBeDefined(); + expect(unhoverData.points[0].label).toBe('Eve'); + } else { + fail('did not trigger correct # of plotly_unhover events'); + } + }) + .then(done, done.fail); }); }); -describe('Test treemap clicks:', function() { +describe('Test treemap clicks:', function () { var gd; var trackers; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); trackers = {}; }); @@ -1024,242 +1172,247 @@ describe('Test treemap clicks:', function() { // use `.unshift` that way to latest event data object // will be in entry [0], which is easier to pick out - return function() { - gd.on('plotly_treemapclick', function(d) { + return function () { + gd.on('plotly_treemapclick', function (d) { trackers.treemapclick.unshift(d); - if(opts.turnOffAnimation) return false; + if (opts.turnOffAnimation) return false; }); - gd.on('plotly_click', function(d) { + gd.on('plotly_click', function (d) { trackers.click.unshift(d); }); - gd.on('plotly_animating', function() { + gd.on('plotly_animating', function () { // N.B. does not emit event data trackers.animating.unshift(true); }); }; } - it('should trigger animation when clicking on branches', function(done) { + it('should trigger animation when clicking on branches', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 2)) - .then(function() { - if(trackers.treemapclick.length === 1) { - expect(trackers.treemapclick[0].event).toBeDefined(); - expect(trackers.treemapclick[0].points[0].label).toBe('Seth'); - expect(trackers.treemapclick[0].nextLevel).toBe('Seth'); - } else { - fail('incorrect plotly_treemapclick triggering'); - } + .then(setupListeners()) + .then(click(gd, 2)) + .then(function () { + if (trackers.treemapclick.length === 1) { + expect(trackers.treemapclick[0].event).toBeDefined(); + expect(trackers.treemapclick[0].points[0].label).toBe('Seth'); + expect(trackers.treemapclick[0].nextLevel).toBe('Seth'); + } else { + fail('incorrect plotly_treemapclick triggering'); + } - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(); - expect(trackers.click[0].points[0].label).toBe('Seth'); - expect(trackers.click[0].nextLevel).not.toBeDefined(); - } else { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(); + expect(trackers.click[0].points[0].label).toBe('Seth'); + expect(trackers.click[0].nextLevel).not.toBeDefined(); + } else { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); - it('should trigger plotly_click event when clicking on leaf node', function(done) { + it('should trigger plotly_click event when clicking on leaf node', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 8)) - .then(function() { - if(trackers.treemapclick.length === 1) { - expect(trackers.treemapclick[0].event).toBeDefined(); - expect(trackers.treemapclick[0].points[0].label).toBe('Noam'); - } else { - fail('incorrect plotly_treemapclick triggering'); - } + .then(setupListeners()) + .then(click(gd, 8)) + .then(function () { + if (trackers.treemapclick.length === 1) { + expect(trackers.treemapclick[0].event).toBeDefined(); + expect(trackers.treemapclick[0].points[0].label).toBe('Noam'); + } else { + fail('incorrect plotly_treemapclick triggering'); + } - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(); - expect(trackers.click[0].points[0].label).toBe('Noam'); - } - }) - .then(done, done.fail); + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(); + expect(trackers.click[0].points[0].label).toBe('Noam'); + } + }) + .then(done, done.fail); }); - it('should not trigger animation when graph is transitioning', function(done) { + it('should not trigger animation when graph is transitioning', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners()) - .then(click(gd, 2)) - .then(function() { - var msg = 'after 1st click'; - - if(trackers.treemapclick.length === 1) { - expect(trackers.treemapclick[0].event).toBeDefined(msg); - expect(trackers.treemapclick[0].points[0].label).toBe('Seth', msg); - expect(trackers.treemapclick[0].nextLevel).toBe('Seth', msg); - } else { - fail('incorrect plotly_treemapclick triggering - ' + msg); - } - - if(trackers.click.length === 1) { - expect(trackers.click[0].event).toBeDefined(msg); - expect(trackers.click[0].points[0].label).toBe('Seth', msg); - expect(trackers.click[0].nextLevel).not.toBeDefined(msg); - } else { - fail('incorrect plotly_click triggering - ' + msg); - } + .then(setupListeners()) + .then(click(gd, 2)) + .then(function () { + var msg = 'after 1st click'; + + if (trackers.treemapclick.length === 1) { + expect(trackers.treemapclick[0].event).toBeDefined(msg); + expect(trackers.treemapclick[0].points[0].label).toBe('Seth', msg); + expect(trackers.treemapclick[0].nextLevel).toBe('Seth', msg); + } else { + fail('incorrect plotly_treemapclick triggering - ' + msg); + } - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering - ' + msg); - } - }) - .then(click(gd, 4)) - .then(function() { - var msg = 'after 2nd click'; - - // should trigger plotly_treemapclick and plotly_click twice, - // but not plotly_animating - - if(trackers.treemapclick.length === 2) { - expect(trackers.treemapclick[0].event).toBeDefined(msg); - expect(trackers.treemapclick[0].points[0].label).toBe('Awan', msg); - expect(trackers.treemapclick[0].nextLevel).toBe('Awan', msg); - } else { - fail('incorrect plotly_treemapclick triggering - ' + msg); - } + if (trackers.click.length === 1) { + expect(trackers.click[0].event).toBeDefined(msg); + expect(trackers.click[0].points[0].label).toBe('Seth', msg); + expect(trackers.click[0].nextLevel).not.toBeDefined(msg); + } else { + fail('incorrect plotly_click triggering - ' + msg); + } - if(trackers.click.length === 2) { - expect(trackers.click[0].event).toBeDefined(msg); - expect(trackers.click[0].points[0].label).toBe('Awan', msg); - expect(trackers.click[0].nextLevel).not.toBeDefined(msg); - } else { - fail('incorrect plotly_click triggering - ' + msg); - } + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering - ' + msg); + } + }) + .then(click(gd, 4)) + .then(function () { + var msg = 'after 2nd click'; + + // should trigger plotly_treemapclick and plotly_click twice, + // but not plotly_animating + + if (trackers.treemapclick.length === 2) { + expect(trackers.treemapclick[0].event).toBeDefined(msg); + expect(trackers.treemapclick[0].points[0].label).toBe('Awan', msg); + expect(trackers.treemapclick[0].nextLevel).toBe('Awan', msg); + } else { + fail('incorrect plotly_treemapclick triggering - ' + msg); + } + if (trackers.click.length === 2) { + expect(trackers.click[0].event).toBeDefined(msg); + expect(trackers.click[0].points[0].label).toBe('Awan', msg); + expect(trackers.click[0].nextLevel).not.toBeDefined(msg); + } else { + fail('incorrect plotly_click triggering - ' + msg); + } - if(trackers.animating.length !== 1) { - fail('incorrect plotly_animating triggering - ' + msg); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 1) { + fail('incorrect plotly_animating triggering - ' + msg); + } + }) + .then(done, done.fail); }); - it('should be able to override default click behavior using plotly_treemapclick handler ()', function(done) { + it('should be able to override default click behavior using plotly_treemapclick handler ()', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); Plotly.newPlot(gd, mock) - .then(setupListeners({turnOffAnimation: true})) - .then(click(gd, 2)) - .then(function() { - if(trackers.treemapclick.length === 1) { - expect(trackers.treemapclick[0].event).toBeDefined(); - expect(trackers.treemapclick[0].points[0].label).toBe('Seth'); - } else { - fail('incorrect plotly_treemapclick triggering'); - } + .then(setupListeners({ turnOffAnimation: true })) + .then(click(gd, 2)) + .then(function () { + if (trackers.treemapclick.length === 1) { + expect(trackers.treemapclick[0].event).toBeDefined(); + expect(trackers.treemapclick[0].points[0].label).toBe('Seth'); + } else { + fail('incorrect plotly_treemapclick triggering'); + } - if(trackers.click.length !== 0) { - fail('incorrect plotly_click triggering'); - } + if (trackers.click.length !== 0) { + fail('incorrect plotly_click triggering'); + } - if(trackers.animating.length !== 0) { - fail('incorrect plotly_animating triggering'); - } - }) - .then(done, done.fail); + if (trackers.animating.length !== 0) { + fail('incorrect plotly_animating triggering'); + } + }) + .then(done, done.fail); }); }); -describe('Test treemap restyle:', function() { +describe('Test treemap restyle:', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); function _restyle(updateObj) { - return function() { return Plotly.restyle(gd, updateObj); }; + return function () { + return Plotly.restyle(gd, updateObj); + }; } - it('should be able to toggle visibility', function(done) { + it('should be able to toggle visibility', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_first.json')); function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.treemaplayer'); 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); + .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) { + it('should be able to restyle *maxdepth* and *level* w/o recomputing the hierarchy', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/treemap_coffee.json')); function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.treemaplayer'); expect(layer.selectAll('.slice').size()).toBe(exp, msg); // editType:plot - if(msg !== 'base') { + 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); + .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 *textinfo*', function(done) { + it('should be able to restyle *textinfo*', function (done) { var mock = { - data: [{ - type: 'treemap', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - text: ['node0', 'node1', 'node2', 'node3'], - values: [0, 1, 2, 3], - pathbar: { visible: false } - }] + data: [ + { + type: 'treemap', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + text: ['node0', 'node1', 'node2', 'node3'], + values: [0, 1, 2, 3], + pathbar: { visible: false } + } + ] }; function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.treemaplayer'); var tx = []; - layer.selectAll('text.slicetext').each(function() { + layer.selectAll('text.slicetext').each(function () { var lines = d3Select(this).selectAll('tspan'); - if(lines.size()) { + if (lines.size()) { var t = []; - lines.each(function() { + lines.each(function () { t.push(this.innerHTML); }); tx.push(t.join('\n')); @@ -1271,46 +1424,48 @@ describe('Test treemap restyle:', function() { expect(tx).toEqual(exp, msg); // editType:plot - if(msg !== 'base') { + if (msg !== 'base') { expect(Plots.doCalcdata).toHaveBeenCalledTimes(0); } }; } Plotly.newPlot(gd, mock) - .then(_assert('base', ['Root', 'B', 'A\nnode1', 'b\nnode3'])) - .then(function() { - spyOn(Plots, 'doCalcdata').and.callThrough(); - }) - .then(_restyle({textinfo: 'label'})) - .then(_assert('just label', ['Root', 'B', 'A', 'b'])) - .then(_restyle({textinfo: 'value'})) - .then(_assert('show input values', ['Root', 'B', '1', '3'])) - .then(_restyle({textinfo: 'none'})) - .then(_assert('no textinfo', ['Root', 'B', ' ', ' '])) // use one space character instead of a blank string to avoid jumps during transition - .then(_restyle({textinfo: 'label+text+value'})) - .then(_assert('show everything', ['Root', 'B', 'A\n1\nnode1', 'b\n3\nnode3'])) - .then(_restyle({textinfo: null})) - .then(_assert('back to dflt', ['Root', 'B', 'A\nnode1', 'b\nnode3'])) - .then(done, done.fail); - }); - - it('should be able to restyle *marker.cornerradius*', function(done) { + .then(_assert('base', ['Root', 'B', 'A\nnode1', 'b\nnode3'])) + .then(function () { + spyOn(Plots, 'doCalcdata').and.callThrough(); + }) + .then(_restyle({ textinfo: 'label' })) + .then(_assert('just label', ['Root', 'B', 'A', 'b'])) + .then(_restyle({ textinfo: 'value' })) + .then(_assert('show input values', ['Root', 'B', '1', '3'])) + .then(_restyle({ textinfo: 'none' })) + .then(_assert('no textinfo', ['Root', 'B', ' ', ' '])) // use one space character instead of a blank string to avoid jumps during transition + .then(_restyle({ textinfo: 'label+text+value' })) + .then(_assert('show everything', ['Root', 'B', 'A\n1\nnode1', 'b\n3\nnode3'])) + .then(_restyle({ textinfo: null })) + .then(_assert('back to dflt', ['Root', 'B', 'A\nnode1', 'b\nnode3'])) + .then(done, done.fail); + }); + + it('should be able to restyle *marker.cornerradius*', function (done) { var mock = { - data: [{ - type: 'treemap', - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - text: ['node0', 'node1', 'node2', 'node3'], - values: [0, 1, 2, 3], - pathbar: { visible: false } - }] + data: [ + { + type: 'treemap', + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + text: ['node0', 'node1', 'node2', 'node3'], + values: [0, 1, 2, 3], + pathbar: { visible: false } + } + ] }; function _assert(msg, exp) { - return function() { + return function () { var layer = d3Select(gd).select('.treemaplayer'); - layer.selectAll('.surface').each(function() { + layer.selectAll('.surface').each(function () { var surfaces = d3Select(this); var path = surfaces[0][0].getAttribute('d'); expect(path.indexOf('a') !== -1).toEqual(exp); @@ -1319,30 +1474,30 @@ describe('Test treemap restyle:', function() { } Plotly.newPlot(gd, mock) - .then(_assert('no arcs', false)) - .then(function() { - return Plotly.restyle(gd, 'marker.cornerradius', 10); - }) - .then(_assert('has arcs', true)) - .then(function() { - return Plotly.restyle(gd, 'marker.cornerradius', 0); - }) - .then(_assert('no arcs', false)) - .then(done, done.fail); + .then(_assert('no arcs', false)) + .then(function () { + return Plotly.restyle(gd, 'marker.cornerradius', 10); + }) + .then(_assert('has arcs', true)) + .then(function () { + return Plotly.restyle(gd, 'marker.cornerradius', 0); + }) + .then(_assert('no arcs', false)) + .then(done, done.fail); }); }); -describe('Test treemap tweening:', function() { +describe('Test treemap tweening:', function () { var gd; var pathTweenFnLookup; var textTweenFnLookup; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); // hacky way to track tween functions - spyOn(d3Transition.prototype, 'attrTween').and.callFake(function(attrName, fn) { - var lookup = {d: pathTweenFnLookup, transform: textTweenFnLookup}[attrName]; + spyOn(d3Transition.prototype, 'attrTween').and.callFake(function (attrName, fn) { + var lookup = { d: pathTweenFnLookup, transform: textTweenFnLookup }[attrName]; var pt = this[0][0].__data__; var id = pt.data.data.id; @@ -1370,9 +1525,8 @@ describe('Test treemap tweening:', function() { return s.replace(/\s/g, ''); } - function _assert(msg, attrName, id, exp, tolerance) { - var lookup = {d: pathTweenFnLookup, transform: textTweenFnLookup}[attrName]; + var lookup = { d: pathTweenFnLookup, transform: textTweenFnLookup }[attrName]; var fn = lookup[id]; // normalize time in [0, 1] where we'll assert the tweening fn output, // asserting at the mid point *should be* representative enough @@ -1380,8 +1534,12 @@ describe('Test treemap tweening:', function() { var actual = trim(fn(t)); var msg2 = msg + ' | node ' + id + ' @t=' + t; - if(attrName === 'transform') { - var fake = {attr: function() { return actual; }}; + if (attrName === 'transform') { + var fake = { + attr: function () { + return actual; + } + }; var xy = Drawing.getTranslate(fake); expect([xy.x, xy.y]).toBeWithinArray(exp, tolerance || 2, msg2); } else { @@ -1392,137 +1550,124 @@ describe('Test treemap tweening:', function() { } } - it('should tween sector exit/update (case: click on branch, no maxdepth)', function(done) { + it('should tween sector exit/update (case: click on branch, no maxdepth)', function (done) { var mock = { - data: [{ - type: 'treemap', pathbar: { visible: false }, - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'] - }] + data: [ + { + type: 'treemap', + pathbar: { visible: false }, + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'] + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 3)) - .then(function() { - _assert('exit entry', 'd', 'Root', - 'M80,100L620,100L620,370L80,370Z' - ); - _assert('update A to new position', 'd', 'A', - 'M83,112L214.25,112L214.25,367L83,367Z' - ); - _assert('update B to new position', 'd', 'B', - 'M215.75,112L617,112L617,367L215.75,367Z' - ); - _assert('update b to new position', 'd', 'b', - 'M221.75,136L611,136L611,361L221.75,361Z' - ); - _assert('move B text to new position', 'transform', 'B', [222.75, 126], 3); - _assert('move b text to new position', 'transform', 'b', [225.75, 150], 3); - }) - .then(done, done.fail); + .then(_run(gd, 3)) + .then(function () { + _assert('exit entry', 'd', 'Root', 'M80,100L620,100L620,370L80,370Z'); + _assert('update A to new position', 'd', 'A', 'M83,112L214.25,112L214.25,367L83,367Z'); + _assert('update B to new position', 'd', 'B', 'M215.75,112L617,112L617,367L215.75,367Z'); + _assert('update b to new position', 'd', 'b', 'M221.75,136L611,136L611,361L221.75,361Z'); + _assert('move B text to new position', 'transform', 'B', [222.75, 126], 3); + _assert('move b text to new position', 'transform', 'b', [225.75, 150], 3); + }) + .then(done, done.fail); }); - it('should tween sector enter/update (case: click on entry, no maxdepth)', function(done) { + it('should tween sector enter/update (case: click on entry, no maxdepth)', function (done) { var mock = { - data: [{ - type: 'treemap', pathbar: { visible: false }, - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - level: 'B' - }] + data: [ + { + type: 'treemap', + pathbar: { visible: false }, + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + level: 'B' + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 1)) - .then(function() { - _assert('enter new entry', 'd', 'Root', - 'M80,100L620,100L620,370L80,370Z' - ); - _assert('update A to new position', 'd', 'A', - 'M83,112L214.25,112L214.25,367L83,367Z' - ); - _assert('update B to new position', 'd', 'B', - 'M215.75,112L617,112L617,367L215.75,367Z' - ); - _assert('update b to new position', 'd', 'b', - 'M221.75,136L611,136L611,361L221.75,361Z' - ); - _assert('move B text to new position', 'transform', 'B', [222.75, 126], 3); - _assert('move b text to new position', 'transform', 'b', [225.75, 150], 3); - }) - .then(done, done.fail); + .then(_run(gd, 1)) + .then(function () { + _assert('enter new entry', 'd', 'Root', 'M80,100L620,100L620,370L80,370Z'); + _assert('update A to new position', 'd', 'A', 'M83,112L214.25,112L214.25,367L83,367Z'); + _assert('update B to new position', 'd', 'B', 'M215.75,112L617,112L617,367L215.75,367Z'); + _assert('update b to new position', 'd', 'b', 'M221.75,136L611,136L611,361L221.75,361Z'); + _assert('move B text to new position', 'transform', 'B', [222.75, 126], 3); + _assert('move b text to new position', 'transform', 'b', [225.75, 150], 3); + }) + .then(done, done.fail); }); - it('should tween sector enter/update/exit (case: click on entry, maxdepth=2)', function(done) { + it('should tween sector enter/update/exit (case: click on entry, maxdepth=2)', function (done) { var mock = { - data: [{ - type: 'treemap', pathbar: { visible: false }, - labels: ['Root', 'A', 'B', 'b'], - parents: ['', 'Root', 'Root', 'B'], - maxdepth: 2 - }] + data: [ + { + type: 'treemap', + pathbar: { visible: false }, + labels: ['Root', 'A', 'B', 'b'], + parents: ['', 'Root', 'Root', 'B'], + maxdepth: 2 + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 3)) - .then(function() { - _assert('exit entry', 'd', 'Root', - 'M80,100L620,100L620,370L80,370Z' - ); - _assert('update A to new position', 'd', 'A', - 'M83,112L214.25,112L214.25,367L83,367Z' - ); - _assert('update B to new position', 'd', 'B', - 'M215.75,112L617,112L617,367L215.75,367Z' - ); - _assert('enter b for parent position', 'd', 'b', - 'M284.375,188.5L548.375,188.5L548.375,308.5L284.375,308.5Z' - ); - _assert('move B text to new position', 'transform', 'B', [221.25, 126], 3); - _assert('enter b text to new position', 'transform', 'b', [287.625, 192]); - }) - .then(done, done.fail); + .then(_run(gd, 3)) + .then(function () { + _assert('exit entry', 'd', 'Root', 'M80,100L620,100L620,370L80,370Z'); + _assert('update A to new position', 'd', 'A', 'M83,112L214.25,112L214.25,367L83,367Z'); + _assert('update B to new position', 'd', 'B', 'M215.75,112L617,112L617,367L215.75,367Z'); + _assert( + 'enter b for parent position', + 'd', + 'b', + 'M284.375,188.5L548.375,188.5L548.375,308.5L284.375,308.5Z' + ); + _assert('move B text to new position', 'transform', 'B', [221.25, 126], 3); + _assert('enter b text to new position', 'transform', 'b', [287.625, 192]); + }) + .then(done, done.fail); }); - it('should tween sector enter/update/exit (case: click on entry, maxdepth=2, level=B)', function(done) { + it('should tween sector enter/update/exit (case: click on entry, maxdepth=2, level=B)', function (done) { var mock = { - data: [{ - type: 'treemap', pathbar: { visible: false }, - labels: ['Root', 'A', 'B', 'b', 'bb'], - parents: ['', 'Root', 'Root', 'B', 'b'], - maxdepth: 2, - level: 'B' - }] + data: [ + { + type: 'treemap', + pathbar: { visible: false }, + labels: ['Root', 'A', 'B', 'b', 'bb'], + parents: ['', 'Root', 'Root', 'B', 'b'], + maxdepth: 2, + level: 'B' + } + ] }; Plotly.newPlot(gd, mock) - .then(_run(gd, 1)) - .then(function() { - _assert('exit b', 'd', 'b', - 'M284.375,188.5L548.375,188.5L548.375,308.5L284.375,308.5Z' - ); - _assert('enter new entry', 'd', 'Root', - 'M80,100L620,100L620,370L80,370Z' - ); - _assert('enter A counterclockwise', 'd', 'A', - 'M83,112L214.25,112L214.25,367L83,367Z' - ); - _assert('update B to new position', 'd', 'B', - 'M215.75,112L617,112L617,367L215.75,367Z' - ); - }) - .then(done, done.fail); + .then(_run(gd, 1)) + .then(function () { + _assert('exit b', 'd', 'b', 'M284.375,188.5L548.375,188.5L548.375,308.5L284.375,308.5Z'); + _assert('enter new entry', 'd', 'Root', 'M80,100L620,100L620,370L80,370Z'); + _assert('enter A counterclockwise', 'd', 'A', 'M83,112L214.25,112L214.25,367L83,367Z'); + _assert('update B to new position', 'd', 'B', 'M215.75,112L617,112L617,367L215.75,367Z'); + }) + .then(done, done.fail); }); }); -describe('Test treemap interactions edge cases', function() { +describe('Test treemap interactions edge cases', function () { var gd; - beforeEach(function() { gd = createGraphDiv(); }); + beforeEach(function () { + gd = createGraphDiv(); + }); afterEach(destroyGraphDiv); - it('should keep tracking hover labels and hover events after *calc* edits', function(done) { + it('should keep tracking hover labels and hover events after *calc* edits', function (done) { var mock = Lib.extendFlat({}, require('../../image/mocks/treemap_first.json')); var hoverCnt = 0; var unhoverCnt = 0; @@ -1541,68 +1686,70 @@ describe('Test treemap interactions edge cases', function() { } Plotly.newPlot(gd, mock) - .then(function() { - gd.on('plotly_hover', function() { - hoverCnt++; - // N.B. trigger a 'plot' edit - Plotly.restyle(gd, 'textinfo', 'none'); - }); - gd.on('plotly_unhover', function() { - unhoverCnt++; - // N.B. trigger a 'plot' edit - Plotly.restyle(gd, 'textinfo', null); - }); - }) - .then(hover(gd, 1)) - .then(function() { - _assert('after hovering on first sector', { - hoverCnt: 1, - unhoverCnt: 0, - hoverLabel: 1 - }); - }) - .then(unhover(gd, 1)) - .then(function() { - _assert('after un-hovering from first sector', { - hoverCnt: 0, - unhoverCnt: 1, - hoverLabel: 0 - }); - }) - .then(hover(gd, 2)) - .then(function() { - _assert('after hovering onto second sector', { - hoverCnt: 1, - unhoverCnt: 0, - hoverLabel: 1 - }); - }) - .then(done, done.fail); + .then(function () { + gd.on('plotly_hover', function () { + hoverCnt++; + // N.B. trigger a 'plot' edit + Plotly.restyle(gd, 'textinfo', 'none'); + }); + gd.on('plotly_unhover', function () { + unhoverCnt++; + // N.B. trigger a 'plot' edit + Plotly.restyle(gd, 'textinfo', null); + }); + }) + .then(hover(gd, 1)) + .then(function () { + _assert('after hovering on first sector', { + hoverCnt: 1, + unhoverCnt: 0, + hoverLabel: 1 + }); + }) + .then(unhover(gd, 1)) + .then(function () { + _assert('after un-hovering from first sector', { + hoverCnt: 0, + unhoverCnt: 1, + hoverLabel: 0 + }); + }) + .then(hover(gd, 2)) + .then(function () { + _assert('after hovering onto second sector', { + hoverCnt: 1, + unhoverCnt: 0, + hoverLabel: 1 + }); + }) + .then(done, done.fail); }); - it('should show falsy zero text', function(done) { + it('should show falsy zero text', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'treemap', - parents: ['', 'A', 'B', 'C', 'D', 'E', 'F'], - labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], - values: [7, 6, 5, 4, 3, 2, 1], - text: [null, '', '0', 0, 1, true, false], - textinfo: 'label+text+value' - }], + data: [ + { + type: 'treemap', + parents: ['', 'A', 'B', 'C', 'D', 'E', 'F'], + labels: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], + values: [7, 6, 5, 4, 3, 2, 1], + text: [null, '', '0', 0, 1, true, false], + textinfo: 'label+text+value' + } + ], layout: { width: 400, height: 400 } }) - .then(hover(gd, 4)) - .then(function() { - assertHoverLabelContent({ nums: 'D\n4\n0' }); - }) - .then(done, done.fail); + .then(hover(gd, 4)) + .then(function () { + assertHoverLabelContent({ nums: 'D\n4\n0' }); + }) + .then(done, done.fail); }); - it('should transition treemap traces only', function(done) { + it('should transition treemap traces only', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/display-text_zero-number.json')); mock.data[0].visible = false; mock.data[1].type = 'treemap'; @@ -1610,163 +1757,391 @@ describe('Test treemap interactions edge cases', function() { function _assert(msg, exp) { var gd3 = d3Select(gd); - expect(gd3.select('.cartesianlayer').selectAll('.trace').size()) - .toBe(exp.cartesianTraceCnt, '# of cartesian traces'); - expect(gd3.select('.pielayer').selectAll('.trace').size()) - .toBe(exp.pieTraceCnt, '# of pie traces'); - expect(gd3.select('.treemaplayer').selectAll('.trace').size()) - .toBe(exp.treemapTraceCnt, '# of treemap traces'); + expect(gd3.select('.cartesianlayer').selectAll('.trace').size()).toBe( + exp.cartesianTraceCnt, + '# of cartesian traces' + ); + expect(gd3.select('.pielayer').selectAll('.trace').size()).toBe(exp.pieTraceCnt, '# of pie traces'); + expect(gd3.select('.treemaplayer').selectAll('.trace').size()).toBe( + exp.treemapTraceCnt, + '# of treemap traces' + ); } Plotly.newPlot(gd, mock) - .then(function() { - _assert('base', { - cartesianTraceCnt: 2, - pieTraceCnt: 0, - treemapTraceCnt: 1 - }); - }) - .then(click(gd, 2)) - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(function() { - _assert('after treemap click', { - cartesianTraceCnt: 2, - pieTraceCnt: 0, - treemapTraceCnt: 1 - }); - }) - .then(done, done.fail); + .then(function () { + _assert('base', { + cartesianTraceCnt: 2, + pieTraceCnt: 0, + treemapTraceCnt: 1 + }); + }) + .then(click(gd, 2)) + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then(function () { + _assert('after treemap click', { + cartesianTraceCnt: 2, + pieTraceCnt: 0, + treemapTraceCnt: 1 + }); + }) + .then(done, done.fail); }); - it('should be able to transition treemap traces via `Plotly.react`', function(done) { + it('should be able to transition treemap traces via `Plotly.react`', function (done) { var mock = Lib.extendDeep({}, require('../../image/mocks/display-text_zero-number.json')); mock.data[1].type = 'treemap'; mock.data[1].name = 'treemap'; - mock.layout.transition = {duration: 200}; + mock.layout.transition = { duration: 200 }; spyOn(Plots, 'transitionFromReact').and.callThrough(); Plotly.newPlot(gd, mock) - .then(function() { - gd.data[1].level = 'B'; - return Plotly.react(gd, gd.data, gd.layout); - }) - .then(delay(202)) - .then(function() { - expect(Plots.transitionFromReact).toHaveBeenCalledTimes(1); - }) - .then(done, done.fail); + .then(function () { + gd.data[1].level = 'B'; + return Plotly.react(gd, gd.data, gd.layout); + }) + .then(delay(202)) + .then(function () { + expect(Plots.transitionFromReact).toHaveBeenCalledTimes(1); + }) + .then(done, done.fail); }); }); -describe('Test treemap texttemplate without `values` should work:', function() { - checkTextTemplate([{ - type: 'treemap', pathbar: { visible: false }, - 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}', ['Eve', 'color: #1f77b4', 'Seth', 'color: #2ca02c', 'color: #d62728', 'color: #9467bd', 'Awan', 'color: #ff7f0e', 'color: #d62728']], - ['label: %{label}', ['Eve', 'label: Cain', 'Seth', 'label: Enos', 'label: Noam', 'label: Abel', 'Awan', 'label: Enoch', 'label: Azura']], - ['text: %{text}', ['Eve', 'text: fourteen', 'Seth', 'text: ten', 'text: two', 'text: six', 'Awan', 'text: one', 'text: four']], - ['path: %{currentPath}', ['Eve', 'path: Eve/', 'Seth', 'path: Eve/', 'path: Eve/', 'path: Eve/Seth/', 'Awan', 'path: Eve/Seth/', 'path: Eve/Awan/']], - ['%{percentRoot} of %{root}', ['Eve', '33% of Eve', 'Seth', '17% of Eve', '17% of Eve', '17% of Eve', 'Awan', '17% of Eve', '17% of Eve']], - ['%{percentEntry} of %{entry}', ['Eve', '33% of Eve', 'Seth', '17% of Eve', '17% of Eve', '17% of Eve', 'Awan', '17% of Eve', '17% of Eve']], - ['%{percentParent} of %{parent}', ['Eve', '100% of Seth', 'Seth', '17% of Eve', '17% of Eve', '17% of Eve', 'Awan', '50% of Seth', '100% of Awan']], +describe('Test treemap texttemplate without `values` should work:', function () { + checkTextTemplate( + [ + { + type: 'treemap', + pathbar: { visible: false }, + 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}', - 'label: %{label}, text: %{text}', + [ + 'Eve', + 'color: #1f77b4', + 'Seth', + 'color: #2ca02c', + 'color: #d62728', + 'color: #9467bd', + 'Awan', + 'color: #ff7f0e', + 'color: #d62728' + ] + ], + [ + 'label: %{label}', + [ + 'Eve', + 'label: Cain', + 'Seth', + 'label: Enos', + 'label: Noam', + 'label: Abel', + 'Awan', + 'label: Enoch', + 'label: Azura' + ] + ], + [ 'text: %{text}', - 'value: %{value}', + [ + 'Eve', + 'text: fourteen', + 'Seth', + 'text: ten', + 'text: two', + 'text: six', + 'Awan', + 'text: one', + 'text: four' + ] + ], + [ + 'path: %{currentPath}', + [ + 'Eve', + 'path: Eve/', + 'Seth', + 'path: Eve/', + 'path: Eve/', + 'path: Eve/Seth/', + 'Awan', + 'path: Eve/Seth/', + 'path: Eve/Awan/' + ] + ], + [ '%{percentRoot} of %{root}', + [ + 'Eve', + '33% of Eve', + 'Seth', + '17% of Eve', + '17% of Eve', + '17% of Eve', + 'Awan', + '17% of Eve', + '17% of Eve' + ] + ], + [ '%{percentEntry} of %{entry}', + [ + 'Eve', + '33% of Eve', + 'Seth', + '17% of Eve', + '17% of Eve', + '17% of Eve', + 'Awan', + '17% of Eve', + '17% of Eve' + ] + ], + [ '%{percentParent} of %{parent}', - '%{percentParent} of %{parent}', - '%{percentParent} of %{parent}' + [ + 'Eve', + '100% of Seth', + 'Seth', + '17% of Eve', + '17% of Eve', + '17% of Eve', + 'Awan', + '50% of Seth', + '100% of Awan' + ] ], [ - 'Eve', - 'label: Cain, text: fourteen', - 'Seth', - 'value: %{value}', // N.B. there is no `values` array - '17% of Eve', - '17% of Eve', - 'Awan', - '17% of Eve', - '100% of Awan' + [ + 'color: %{color}', + 'label: %{label}, text: %{text}', + 'text: %{text}', + 'value: %{value}', + '%{percentRoot} of %{root}', + '%{percentEntry} of %{entry}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}', + '%{percentParent} of %{parent}' + ], + [ + 'Eve', + 'label: Cain, text: fourteen', + 'Seth', + 'value: %{value}', // N.B. there is no `values` array + '17% of Eve', + '17% of Eve', + 'Awan', + '17% of Eve', + '100% of Awan' + ] ] - ] - ], /* skipEtra */ true); + ], + /* skipEtra */ true + ); }); -describe('Test treemap texttemplate with *total* `values` should work:', function() { - checkTextTemplate([{ - type: 'treemap', pathbar: { visible: false }, - 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}', ['Eve', 'color: #1f77b4', 'Seth', 'color: #2ca02c', 'color: #d62728', 'color: #9467bd', 'Awan', 'color: #ff7f0e', 'color: #d62728']], - ['label: %{label}', ['Eve', 'label: Cain', 'Seth', 'label: Enos', 'label: Noam', 'label: Abel', 'Awan', 'label: Enoch', 'label: Azura']], - ['value: %{value}', ['Eve', 'value: 14', 'Seth', 'value: 10', 'value: 2', 'value: 6', 'Awan', 'value: 1', 'value: 4']], - ['text: %{text}', ['Eve', 'text: fourteen', 'Seth', 'text: ten', 'text: two', 'text: six', 'Awan', 'text: one', 'text: four']], - ['path: %{currentPath}', ['Eve', 'path: Eve/', 'Seth', 'path: Eve/', 'path: Eve/', 'path: Eve/Seth/', 'Awan', 'path: Eve/Seth/', 'path: Eve/Awan/']] - ], /* skipEtra */ true); +describe('Test treemap texttemplate with *total* `values` should work:', function () { + checkTextTemplate( + [ + { + type: 'treemap', + pathbar: { visible: false }, + 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}', + [ + 'Eve', + 'color: #1f77b4', + 'Seth', + 'color: #2ca02c', + 'color: #d62728', + 'color: #9467bd', + 'Awan', + 'color: #ff7f0e', + 'color: #d62728' + ] + ], + [ + 'label: %{label}', + [ + 'Eve', + 'label: Cain', + 'Seth', + 'label: Enos', + 'label: Noam', + 'label: Abel', + 'Awan', + 'label: Enoch', + 'label: Azura' + ] + ], + [ + 'value: %{value}', + ['Eve', 'value: 14', 'Seth', 'value: 10', 'value: 2', 'value: 6', 'Awan', 'value: 1', 'value: 4'] + ], + [ + 'text: %{text}', + [ + 'Eve', + 'text: fourteen', + 'Seth', + 'text: ten', + 'text: two', + 'text: six', + 'Awan', + 'text: one', + 'text: four' + ] + ], + [ + 'path: %{currentPath}', + [ + 'Eve', + 'path: Eve/', + 'Seth', + 'path: Eve/', + 'path: Eve/', + 'path: Eve/Seth/', + 'Awan', + 'path: Eve/Seth/', + 'path: Eve/Awan/' + ] + ] + ], + /* skipEtra */ true + ); }); -describe('Test treemap texttemplate with *remainder* `values` should work:', function() { - checkTextTemplate([{ - type: 'treemap', pathbar: { visible: false }, - branchvalues: 'remainder', - 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}', ['Eve', 'color: #1f77b4', 'Seth', 'color: #2ca02c', 'color: #d62728', 'color: #9467bd', 'Awan', 'color: #ff7f0e', 'color: #d62728']], - ['label: %{label}', ['Eve', 'label: Cain', 'Seth', 'label: Enos', 'label: Noam', 'label: Abel', 'Awan', 'label: Enoch', 'label: Azura']], - ['value: %{value}', ['Eve', 'value: 14', 'Seth', 'value: 10', 'value: 2', 'value: 6', 'Awan', 'value: 1', 'value: 4']], - ['text: %{text}', ['Eve', 'text: fourteen', 'Seth', 'text: ten', 'text: two', 'text: six', 'Awan', 'text: one', 'text: four']], - ['path: %{currentPath}', ['Eve', 'path: Eve/', 'Seth', 'path: Eve/', 'path: Eve/', 'path: Eve/Seth/', 'Awan', 'path: Eve/Seth/', 'path: Eve/Awan/']] - ], /* skipEtra */ true); +describe('Test treemap texttemplate with *remainder* `values` should work:', function () { + checkTextTemplate( + [ + { + type: 'treemap', + pathbar: { visible: false }, + branchvalues: 'remainder', + 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}', + [ + 'Eve', + 'color: #1f77b4', + 'Seth', + 'color: #2ca02c', + 'color: #d62728', + 'color: #9467bd', + 'Awan', + 'color: #ff7f0e', + 'color: #d62728' + ] + ], + [ + 'label: %{label}', + [ + 'Eve', + 'label: Cain', + 'Seth', + 'label: Enos', + 'label: Noam', + 'label: Abel', + 'Awan', + 'label: Enoch', + 'label: Azura' + ] + ], + [ + 'value: %{value}', + ['Eve', 'value: 14', 'Seth', 'value: 10', 'value: 2', 'value: 6', 'Awan', 'value: 1', 'value: 4'] + ], + [ + 'text: %{text}', + [ + 'Eve', + 'text: fourteen', + 'Seth', + 'text: ten', + 'text: two', + 'text: six', + 'Awan', + 'text: one', + 'text: four' + ] + ], + [ + 'path: %{currentPath}', + [ + 'Eve', + 'path: Eve/', + 'Seth', + 'path: Eve/', + 'path: Eve/', + 'path: Eve/Seth/', + 'Awan', + 'path: Eve/Seth/', + 'path: Eve/Awan/' + ] + ] + ], + /* skipEtra */ true + ); }); -describe('treemap uniformtext', function() { +describe('treemap uniformtext', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); function assertTextSizes(msg, opts) { - return function() { + return function () { var selection = d3SelectAll(SLICES_TEXT_SELECTOR); var size = selection.size(); - ['fontsizes', 'scales'].forEach(function(e) { + ['fontsizes', 'scales'].forEach(function (e) { expect(size).toBe(opts[e].length, 'length for ' + e + ' does not match with the number of elements'); }); - selection.each(function(d, i) { + selection.each(function (d, i) { var fontSize = this.style.fontSize; expect(fontSize).toBe(opts.fontsizes[i] + 'px', 'fontSize for element ' + i, msg); }); - for(var i = 0; i < selection[0].length; i++) { + for (var i = 0; i < selection[0].length; i++) { var transform = selection[0][i].getAttribute('transform'); var pos0 = transform.indexOf('scale('); var scale = 1; - if(pos0 !== -1) { + if (pos0 !== -1) { pos0 += 'scale('.length; var pos1 = transform.indexOf(')', pos0); - scale = +(transform.substring(pos0, pos1)); + scale = +transform.substring(pos0, pos1); } expect(opts.scales[i]).toBeCloseTo(scale, 1, 'scale for element ' + i, msg); @@ -1774,29 +2149,32 @@ describe('treemap uniformtext', function() { }; } - it('should be able to react with new uniform text options', function(done) { + it('should be able to react with new uniform text options', function (done) { var fig = { - data: [{ - type: 'treemap', tiling: { packing: 'slice' }, - parents: ['', '', '', '', '', '', '', '', '', ''], - labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - values: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - - text: [ - 0, - '
', - null, - '', - ' ', - '.', - '|', - '=', - 'longest word in German', - 'Rindfleischetikettierungsueberwachungsaufgabenuebertragungsgesetz' - ], - - textinfo: 'text' - }], + data: [ + { + type: 'treemap', + tiling: { packing: 'slice' }, + parents: ['', '', '', '', '', '', '', '', '', ''], + labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + values: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + + text: [ + 0, + '
', + null, + '', + ' ', + '.', + '|', + '=', + 'longest word in German', + 'Rindfleischetikettierungsueberwachungsaufgabenuebertragungsgesetz' + ], + + textinfo: 'text' + } + ], layout: { width: 500, height: 500 @@ -1804,90 +2182,104 @@ describe('treemap uniformtext', function() { }; Plotly.newPlot(gd, fig) - .then(assertTextSizes('without uniformtext', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.84], - })) - .then(function() { - fig.layout.uniformtext = {mode: 'hide'}; // default with minsize=0 - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using mode: "hide"', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84], - })) - .then(function() { - fig.layout.uniformtext.minsize = 9; // set a minsize less than trace font size - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using minsize: 9', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84], - })) - .then(function() { - fig.layout.uniformtext.minsize = 13; // set a minsize greater than trace font size - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using minsize: 13', { - fontsizes: [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], - })) - .then(function() { - fig.layout.uniformtext.mode = 'show'; - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('using mode: "show"', { - fontsizes: [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], - })) - .then(function() { - fig.layout.uniformtext = undefined; // back to default - return Plotly.react(gd, fig); - }) - .then(assertTextSizes('clear uniformtext', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.84], - })) - .then(done, done.fail); + .then( + assertTextSizes('without uniformtext', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.84] + }) + ) + .then(function () { + fig.layout.uniformtext = { mode: 'hide' }; // default with minsize=0 + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using mode: "hide"', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84] + }) + ) + .then(function () { + fig.layout.uniformtext.minsize = 9; // set a minsize less than trace font size + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using minsize: 9', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84, 0.84] + }) + ) + .then(function () { + fig.layout.uniformtext.minsize = 13; // set a minsize greater than trace font size + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using minsize: 13', { + fontsizes: [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0] + }) + ) + .then(function () { + fig.layout.uniformtext.mode = 'show'; + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('using mode: "show"', { + fontsizes: [13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + }) + ) + .then(function () { + fig.layout.uniformtext = undefined; // back to default + return Plotly.react(gd, fig); + }) + .then( + assertTextSizes('clear uniformtext', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.84] + }) + ) + .then(done, done.fail); }); - it('should uniform text scales after transition', function(done) { + it('should uniform text scales after transition', function (done) { Plotly.newPlot(gd, { - data: [{ - type: 'treemap', - tiling: { packing: 'dice'}, - pathbar: { visible: false }, - parents: [ - '', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Oscar', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform', - 'Uniform' - ], - labels: [ - 'Oscar', - 'Papa', - 'Quebec', - 'Romeo and Juliet', - 'Sierra', - 'Tango', - 'Uniform', - 'ViKtor Korchnoi - Anatoly Karpov', - 'Whiskey', - 'X ray', - 'Yankee', - 'Zulu' - ], - textinfo: 'label' - }], + data: [ + { + type: 'treemap', + tiling: { packing: 'dice' }, + pathbar: { visible: false }, + parents: [ + '', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Oscar', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform', + 'Uniform' + ], + labels: [ + 'Oscar', + 'Papa', + 'Quebec', + 'Romeo and Juliet', + 'Sierra', + 'Tango', + 'Uniform', + 'ViKtor Korchnoi - Anatoly Karpov', + 'Whiskey', + 'X ray', + 'Yankee', + 'Zulu' + ], + textinfo: 'label' + } + ], layout: { width: 850, height: 350, @@ -1897,50 +2289,58 @@ describe('treemap uniformtext', function() { } } }) - .then(assertTextSizes('before click', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], - })) - .then(click(gd, 2)) // click on Uniform - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(assertTextSizes('after click child', { - fontsizes: [12, 12, 12, 12, 12, 12], - scales: [1, 0, 1, 1, 1, 1], - })) - .then(click(gd, 1)) // click on Oscar - .then(delay(constants.CLICK_TRANSITION_TIME + 1)) - .then(assertTextSizes('after click parent', { - fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], - scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1], - })) - .then(done, done.fail); + .then( + assertTextSizes('before click', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1] + }) + ) + .then(click(gd, 2)) // click on Uniform + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then( + assertTextSizes('after click child', { + fontsizes: [12, 12, 12, 12, 12, 12], + scales: [1, 0, 1, 1, 1, 1] + }) + ) + .then(click(gd, 1)) // click on Oscar + .then(delay(constants.CLICK_TRANSITION_TIME + 1)) + .then( + assertTextSizes('after click parent', { + fontsizes: [12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + scales: [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1] + }) + ) + .then(done, done.fail); }); }); -describe('treemap pathbar react', function() { +describe('treemap pathbar react', function () { 'use strict'; var gd; - beforeEach(function() { + beforeEach(function () { gd = createGraphDiv(); }); afterEach(destroyGraphDiv); - it('should show and hide pathbar', function(done) { + it('should show and hide pathbar', function (done) { var fig = { - data: [{ - type: 'treemap', - parents: ['', 'A', 'B', 'C'], - labels: ['A', 'B', 'C', 'D'], - level: 'C' - }], + data: [ + { + type: 'treemap', + parents: ['', 'A', 'B', 'C'], + labels: ['A', 'B', 'C', 'D'], + level: 'C' + } + ], layout: {} }; function _assert(msg, exp) { - return function() { + return function () { var selection = d3SelectAll(SLICES_SELECTOR); var size = selection.size(); @@ -1949,17 +2349,17 @@ describe('treemap pathbar react', function() { } 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); + .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); }); }); From 212f8924074b7713d14b7d046672a1c9f31020cf Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 29 Sep 2025 10:43:41 -0600 Subject: [PATCH 02/18] Return empty string for undefined value in templateFormatString --- src/lib/index.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 7e460293ef6..61944d585b6 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1182,26 +1182,19 @@ function templateFormatString(string, labels, d3locale) { } } - // Apply mult/div operation (if applicable) - if (value !== undefined) { - if (parsedOp === '*') value *= parsedNumber; - if (parsedOp === '/') value /= parsedNumber; - } - - if (value === undefined && opts) { - if (opts.count < opts.max) { - lib.warn("Variable '" + key + "' in " + opts.name + ' could not be found!'); - value = match; - } - - if (opts.count === opts.max) { - lib.warn('Too many ' + opts.name + ' warnings - additional warnings will be suppressed'); - } + if (value === undefined) { + const { count, max, name } = opts; + if (count < max) lib.warn(`Variable '${key}' in ${name} could not be found!`); + if (count === max) lib.warn(`Too many '${name}' warnings - additional warnings will be suppressed`); opts.count++; - return match; + // TODO: Make return valuable configurable with a reasonable default (like 'N/A') + return ''; } + if (parsedOp === '*') value *= parsedNumber; + if (parsedOp === '/') value /= parsedNumber; + if (format) { var fmt; if (format[0] === ':') { From 75b157f396f5b01ed512f9f136aae7b606f3d9ef Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 1 Oct 2025 18:39:23 -0600 Subject: [PATCH 03/18] Refactoring --- src/plots/template_attributes.js | 125 ++++++++++----------------- src/traces/histogram2d/attributes.js | 12 +-- 2 files changed, 46 insertions(+), 91 deletions(-) diff --git a/src/plots/template_attributes.js b/src/plots/template_attributes.js index fff57bc8388..2d9b1188c97 100644 --- a/src/plots/template_attributes.js +++ b/src/plots/template_attributes.js @@ -1,18 +1,14 @@ 'use strict'; +const { DATE_FORMAT_LINK, FORMAT_LINK } = require('../constants/docs'); -var docs = require('../constants/docs'); -var FORMAT_LINK = docs.FORMAT_LINK; -var DATE_FORMAT_LINK = docs.DATE_FORMAT_LINK; - -function templateFormatStringDescription(opts) { - var supportOther = opts && opts.supportOther; +function templateFormatStringDescription(opts = {}) { + const { supportOther } = opts; + const supportOtherText = + ' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.'; return [ 'Variables are inserted using %{variable},', - 'for example "y: %{y}"' + - (supportOther - ? ' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.' - : '.'), + 'for example "y: %{y}"' + (supportOther ? supportOtherText : '.'), 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', FORMAT_LINK, 'for details on the formatting syntax.', @@ -23,49 +19,23 @@ function templateFormatStringDescription(opts) { } exports.templateFormatStringDescription = templateFormatStringDescription; -function shapeTemplateFormatStringDescription() { - return [ - 'Variables are inserted using %{variable},', - 'for example "x0: %{x0}".', - 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{x0:$.2f}". See', - FORMAT_LINK, - 'for details on the formatting syntax.', - 'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{x0|%m %b %Y}". See', - DATE_FORMAT_LINK, - 'for details on the date formatting syntax.', - 'A single multiplication or division operation may be applied to numeric variables, and combined with', - 'd3 number formatting, for example "Length in cm: %{x0*2.54}", "%{slope*60:.1f} meters per second."', - 'For log axes, variable values are given in log units.', - 'For date axes, x/y coordinate variables and center variables use datetimes, while all other variable values use values in ms.' - ].join(' '); -} - -function describeVariables(extra) { - var descPart = extra.description ? ' ' + extra.description : ''; - var keys = extra.keys || []; +function describeVariables({ description, keys = [] }) { + let descPart = description ? ' ' : ''; if (keys.length > 0) { - var quotedKeys = []; - for (var i = 0; i < keys.length; i++) { - quotedKeys[i] = '`' + keys[i] + '`'; - } - descPart = descPart + 'Finally, the template string has access to '; + const quotedKeys = keys.map((k) => `\`${k}\``); + descPart += 'Finally, the template string has access to '; if (keys.length === 1) { - descPart = descPart + 'variable ' + quotedKeys[0]; + descPart += `variable ${quotedKeys[0]}`; } else { - descPart = - descPart + 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.'; + descPart += `variables ${quotedKeys.slice(0, -1).join(', ')} and ${quotedKeys.slice(-1)}.`; } } + return descPart; } -exports.hovertemplateAttrs = function (opts, extra) { - opts = opts || {}; - extra = extra || {}; - - var descPart = describeVariables(extra); - - var hovertemplate = { +exports.hovertemplateAttrs = (opts = {}, extra = {}) => { + const hovertemplate = { valType: 'string', dflt: '', editType: opts.editType || 'none', @@ -75,26 +45,19 @@ exports.hovertemplateAttrs = function (opts, extra) { templateFormatStringDescription({ supportOther: true }), 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.', 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', - descPart, + describeVariables(extra), 'Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`.', 'To hide the secondary box completely, use an empty tag ``.' ].join(' ') }; - if (opts.arrayOk !== false) { - hovertemplate.arrayOk = true; - } + if (opts.arrayOk !== false) hovertemplate.arrayOk = true; return hovertemplate; }; -exports.texttemplateAttrs = function (opts, extra) { - opts = opts || {}; - extra = extra || {}; - - var descPart = describeVariables(extra); - - var texttemplate = { +exports.texttemplateAttrs = (opts = {}, extra = {}) => { + const texttemplate = { valType: 'string', dflt: '', editType: opts.editType || 'calc', @@ -103,34 +66,34 @@ exports.texttemplateAttrs = function (opts, extra) { 'Note that this will override `textinfo`.', templateFormatStringDescription(), 'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', - descPart + describeVariables(extra) ].join(' ') }; - if (opts.arrayOk !== false) { - texttemplate.arrayOk = true; - } - return texttemplate; -}; - -exports.shapeTexttemplateAttrs = function (opts, extra) { - opts = opts || {}; - extra = extra || {}; - - var newStr = opts.newshape ? 'new ' : ''; - - var descPart = describeVariables(extra); + if (opts.arrayOk !== false) texttemplate.arrayOk = true; - var texttemplate = { - valType: 'string', - dflt: '', - editType: opts.editType || 'arraydraw', - description: [ - 'Template string used for rendering the ' + newStr + "shape's label.", - 'Note that this will override `text`.', - shapeTemplateFormatStringDescription(), - descPart - ].join(' ') - }; return texttemplate; }; + +exports.shapeTexttemplateAttrs = (opts = {}, extra = {}) => ({ + valType: 'string', + dflt: '', + editType: opts.editType || 'arraydraw', + description: [ + `Template string used for rendering the ${opts.newshape ? 'new ' : ''}shape's label.`, + 'Note that this will override `text`.', + 'Variables are inserted using %{variable},', + 'for example "x0: %{x0}".', + 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{x0:$.2f}". See', + FORMAT_LINK, + 'for details on the formatting syntax.', + 'Dates are formatted using d3-time-format\'s syntax %{variable|d3-time-format}, for example "Day: %{x0|%m %b %Y}". See', + DATE_FORMAT_LINK, + 'for details on the date formatting syntax.', + 'A single multiplication or division operation may be applied to numeric variables, and combined with', + 'd3 number formatting, for example "Length in cm: %{x0*2.54}", "%{slope*60:.1f} meters per second."', + 'For log axes, variable values are given in log units.', + 'For date axes, x/y coordinate variables and center variables use datetimes, while all other variable values use values in ms.', + describeVariables(extra) + ].join(' ') +}); diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 77d2cbe8441..21f85ba4fd0 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -69,16 +69,8 @@ module.exports = extendFlat( xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z', 1), - hovertemplate: hovertemplateAttrs({}, { keys: 'z' }), - texttemplate: texttemplateAttrs( - { - arrayOk: false, - editType: 'plot' - }, - { - keys: 'z' - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: ['z'] }), + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['z'] }), textfont: heatmapAttrs.textfont, showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, From 5f3670f763626cb43730bd3dd59f55524b4f74fe Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 3 Oct 2025 16:55:03 -0600 Subject: [PATCH 04/18] Add fallback value for template strings --- src/lib/index.js | 76 ++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 61944d585b6..6c5f032417d 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1074,23 +1074,19 @@ lib.templateString = function (string, obj) { }); }; -var hovertemplateWarnings = { +const hovertemplateWarnings = { max: 10, count: 0, name: 'hovertemplate' }; -lib.hovertemplateString = function () { - return templateFormatString.apply(hovertemplateWarnings, arguments); -}; +lib.hovertemplateString = (params) => templateFormatString({ ...params, opts: hovertemplateWarnings }); -var texttemplateWarnings = { +const texttemplateWarnings = { max: 10, count: 0, name: 'texttemplate' }; -lib.texttemplateString = function () { - return templateFormatString.apply(texttemplateWarnings, arguments); -}; +lib.texttemplateString = (params) => templateFormatString({ ...params, opts: texttemplateWarnings }); // Regex for parsing multiplication and division operations applied to a template key // Used for shape.label.texttemplate @@ -1108,9 +1104,7 @@ var texttemplateWarningsForShapes = { name: 'texttemplate', parseMultDiv: true }; -lib.texttemplateStringForShapes = function () { - return templateFormatString.apply(texttemplateWarningsForShapes, arguments); -}; +lib.texttemplateStringForShapes = (params) => templateFormatString({ ...params, opts: texttemplateWarningsForShapes }); var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; /** @@ -1118,41 +1112,36 @@ var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; * or fallback to associated labels. * * Examples: - * Lib.hovertemplateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' - * Lib.hovertemplateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf' - * Lib.hovertemplateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00' + * Lib.hovertemplateString({ string 'name: %{trace}', labels: {trace: 'asdf'} }) --> 'name: asdf' + * Lib.hovertemplateString({ string: 'name: %{trace[0].name}', labels: { trace: [{ name: 'asdf' }] } }) --> 'name: asdf' + * Lib.hovertemplateString({ string: 'price: %{y:$.2f}', labels: { y: 1 } }) --> 'price: $1.00' * - * @param {string} input string containing %{...:...} template strings - * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'} - * @param {obj} d3 locale - * @param {obj} data objects containing substitution values + * @param {object} options - Configuration object + * @param {array} options.args - Data objects containing substitution values + * @param {object} options.d3locale - D3 locale for formatting + * @param {string} options.fallback - Fallback value when substitution fails + * @param {object} options.labels - Data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'} + * @param {object} options.opts - Additional options + * @param {string} options.string - Input string containing %{...:...} template strings * * @return {string} templated string */ -function templateFormatString(string, labels, d3locale) { - var opts = this; - var args = arguments; - if (!labels) labels = {}; - - return string.replace(lib.TEMPLATE_STRING_REGEX, function (match, rawKey, format) { - var isOther = rawKey === 'xother' || rawKey === 'yother'; - - var isSpaceOther = rawKey === '_xother' || rawKey === '_yother'; - - var isSpaceOtherSpace = rawKey === '_xother_' || rawKey === '_yother_'; - - var isOtherSpace = rawKey === 'xother_' || rawKey === 'yother_'; - - var hasOther = isOther || isSpaceOther || isOtherSpace || isSpaceOtherSpace; - - var key = rawKey; +function templateFormatString({ args = [], d3locale, fallback, labels = {}, opts, string }) { + return string.replace(lib.TEMPLATE_STRING_REGEX, (_, rawKey, format) => { + const isOther = ['xother', 'yother'].includes(rawKey); + const isSpaceOther = ['_xother', '_yother'].includes(rawKey); + const isSpaceOtherSpace = ['_xother_', '_yother_'].includes(rawKey); + const isOtherSpace = ['xother_', 'yother_'].includes(rawKey); + const hasOther = isOther || isSpaceOther || isOtherSpace || isSpaceOtherSpace; + + let key = rawKey; if (isSpaceOther || isSpaceOtherSpace) key = key.substring(1); if (isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1); // Shape labels support * and / operators in template string // Parse these if the parseMultDiv param is set to true - var parsedOp = null; - var parsedNumber = null; + let parsedOp = null; + let parsedNumber = null; if (opts.parseMultDiv) { var _match = multDivParser(key); key = _match.key; @@ -1160,14 +1149,12 @@ function templateFormatString(string, labels, d3locale) { parsedNumber = _match.number; } - var value; + let value; if (hasOther) { + if (labels[key] === undefined) return ''; value = labels[key]; - if (value === undefined) return ''; } else { - var obj, i; - for (i = 3; i < args.length; i++) { - obj = args[i]; + for (const obj of args) { if (!obj) continue; if (obj.hasOwnProperty(key)) { value = obj[key]; @@ -1184,12 +1171,11 @@ function templateFormatString(string, labels, d3locale) { if (value === undefined) { const { count, max, name } = opts; - if (count < max) lib.warn(`Variable '${key}' in ${name} could not be found!`); + if (count < max) lib.warn(`Variable '${key}' in ${name} could not be found! Using fallback value.`); if (count === max) lib.warn(`Too many '${name}' warnings - additional warnings will be suppressed`); opts.count++; - // TODO: Make return valuable configurable with a reasonable default (like 'N/A') - return ''; + return fallback; } if (parsedOp === '*') value *= parsedNumber; From 336e9ae82a0d3224943fd80a9ef50f16234409bf Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 3 Oct 2025 16:58:08 -0600 Subject: [PATCH 05/18] Add fallback to calls to hovertemplateString --- src/components/fx/hover.js | 53 +++++++++++++++----------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 31175121656..45fa6dec959 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -796,9 +796,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { var winningPoint = hoverData[0]; // discard other points if (multipleHoverPoints[winningPoint.trace.type]) { - hoverData = hoverData.filter(function (d) { - return d.trace.index === winningPoint.trace.index; - }); + hoverData = hoverData.filter((d) => d.trace.index === winningPoint.trace.index); } else { hoverData = [winningPoint]; } @@ -851,8 +849,7 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) { // pull out just the data that's useful to // other people and send it to the event - for (itemnum = 0; itemnum < hoverData.length; itemnum++) { - var pt = hoverData[itemnum]; + for (const pt of hoverData) { var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); if (pt.hovertemplate !== false) { @@ -1237,9 +1234,7 @@ function createHoverText(hoverData, opts) { if (helpers.isUnifiedHover(hovermode)) { // Delete leftover hover labels from other hovermodes container.selectAll('g.hovertext').remove(); - var groupedHoverData = hoverData.filter(function (data) { - return data.hoverinfo !== 'none'; - }); + const groupedHoverData = hoverData.filter((data) => data.hoverinfo !== 'none'); // Return early if nothing is hovered on if (groupedHoverData.length === 0) return []; @@ -1253,12 +1248,12 @@ function createHoverText(hoverData, opts) { var mainText = !unifiedhovertitleText ? t0 - : Lib.hovertemplateString( - unifiedhovertitleText, - {}, - fullLayout._d3locale, - hovermode === 'x unified' ? { xa: item0.xa, x: item0.xVal } : { ya: item0.ya, y: item0.yVal } - ); + : Lib.hovertemplateString({ + args: hovermode === 'x unified' ? { xa: item0.xa, x: item0.xVal } : { ya: item0.ya, y: item0.yVal }, + d3locale: fullLayout._d3locale, + fallback: item0.trace.hovertemplatefallback, + string: unifiedhovertitleText + }); var mockLayoutIn = { showlegend: true, @@ -1624,9 +1619,7 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { if (d.nameOverride !== undefined) d.name = d.nameOverride; if (d.name) { - if (d.trace._meta) { - d.name = Lib.templateString(d.name, d.trace._meta); - } + if (d.trace._meta) d.name = Lib.templateString(d.name, d.trace._meta); name = plainText(d.name, d.nameLength); } @@ -1669,24 +1662,24 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { } // hovertemplate - var hovertemplate = d.hovertemplate || false; + const { hovertemplate = false } = d; if (hovertemplate) { - var labels = d.hovertemplateLabels || d; + const labels = d.hovertemplateLabels || d; if (d[h0 + 'Label'] !== t0) { labels[h0 + 'other'] = labels[h0 + 'Val']; labels[h0 + 'otherLabel'] = labels[h0 + 'Label']; } - text = Lib.hovertemplateString( - hovertemplate, + text = Lib.hovertemplateString({ + args: [d.eventData[0] || {}, d.trace._meta], + d3locale: fullLayout._d3locale, + fallback: d.trace.hovertemplatefallback, labels, - fullLayout._d3locale, - d.eventData[0] || {}, - d.trace._meta - ); + string: hovertemplate + }); - text = text.replace(EXTRA_STRING_REGEX, function (match, extra) { + text = text.replace(EXTRA_STRING_REGEX, (_, extra) => { // assign name for secondary text label name = plainText(extra, d.nameLength); // remove from main text label @@ -2487,12 +2480,8 @@ function getCoord(axLetter, winningPoint, fullLayout) { // Top/left hover offsets relative to graph div. As long as hover content is // a sibling of the graph div, it will be positioned correctly relative to // the offset parent, whatever that may be. -function getTopOffset(gd) { - return gd.offsetTop + gd.clientTop; -} -function getLeftOffset(gd) { - return gd.offsetLeft + gd.clientLeft; -} +const getTopOffset = (gd) => gd.offsetTop + gd.clientTop; +const getLeftOffset = (gd) => gd.offsetLeft + gd.clientLeft; function getBoundingClientRect(gd, node) { var fullLayout = gd._fullLayout; From 02a4cadb53c618d71bf89dac79f7ff66ec384005 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Fri, 3 Oct 2025 17:37:17 -0600 Subject: [PATCH 06/18] Add fallback to calls to texttemplateString --- src/components/drawing/index.js | 9 +++++++-- src/traces/bar/plot.js | 8 +++++++- src/traces/heatmap/plot.js | 14 +++++++------- src/traces/pie/plot.js | 8 +++++++- src/traces/scatter3d/convert.js | 9 +++++++-- src/traces/scattergl/convert.js | 11 +++++++++-- src/traces/scattermap/convert.js | 9 +++++++-- src/traces/scattermapbox/convert.js | 9 +++++++-- src/traces/sunburst/plot.js | 8 +++++++- 9 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 659e45bded7..18539c34065 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -1299,8 +1299,13 @@ drawing.textPointStyle = function (s, trace, gd) { var labels = fn ? fn(d, trace, fullLayout) : {}; var pointValues = {}; appendArrayPointValue(pointValues, trace, d.i); - var meta = trace._meta || {}; - text = Lib.texttemplateString(text, labels, fullLayout._d3locale, pointValues, d, meta); + text = Lib.texttemplateString({ + args: [pointValues, d, trace._meta], + d3locale: fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels, + string: text + }); } var pos = d.tp || trace.textposition; diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 9a34835376b..78b48f14b7b 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -1073,7 +1073,13 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { var customdata = Lib.castOption(trace, cdi.i, 'customdata'); if (customdata) obj.customdata = customdata; - return Lib.texttemplateString(texttemplate, obj, fullLayout._d3locale, pt, obj, trace._meta || {}); + return Lib.texttemplateString({ + args: [pt, obj, trace._meta], + d3locale: fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels: obj, + string: texttemplate + }); } function calcTextinfo(cd, index, xa, ya) { diff --git a/src/traces/heatmap/plot.js b/src/traces/heatmap/plot.js index 415f9183a75..8639b575e26 100644 --- a/src/traces/heatmap/plot.js +++ b/src/traces/heatmap/plot.js @@ -450,13 +450,13 @@ module.exports = function (gd, plotinfo, cdheatmaps, heatmapLayer) { if (theText === undefined || theText === false) theText = ''; obj.text = theText; - var _t = Lib.texttemplateString( - texttemplate, - obj, - gd._fullLayout._d3locale, - obj, - trace._meta || {} - ); + var _t = Lib.texttemplateString({ + args: [obj, trace._meta], + d3locale: gd._fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels: obj, + string: texttemplate + }); if (!_t) continue; var lines = _t.split('
'); diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index ea6a5359f97..30fb980286c 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -1271,7 +1271,13 @@ function formatSliceLabel(gd, pt, cd0) { var obj = makeTemplateVariables(pt); var ptTx = helpers.getFirstFilled(trace.text, pt.pts); if (isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; - pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {}); + pt.text = Lib.texttemplateString({ + args: [obj, trace._meta], + d3locale: gd._fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels: obj, + string: txt + }); } } } diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index e6c65b4b5fd..7f1e325d2fb 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -257,8 +257,13 @@ function convertPlotlyOptions(scene, data) { }; var pointValues = {}; appendArrayPointValue(pointValues, data, i); - var meta = data._meta || {}; - text[i] = Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta); + text[i] = Lib.texttemplateString({ + args: [pointValues, d, data._meta], + d3locale, + fallback: data.texttemplatefallback, + labels, + string: txt(i) + }); } } diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 0dfedee705e..643105a853d 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -141,8 +141,15 @@ function convertTextStyle(gd, trace) { var labels = trace._module.formatLabels(d, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, i); - var meta = trace._meta || {}; - optsOut.text.push(Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta)); + optsOut.text.push( + Lib.texttemplateString({ + args: [pointValues, d, trace._meta], + d3locale, + fallback: trace.texttemplatefallback, + labels, + string: txt(i) + }) + ); } } else { if (isArrayOrTypedArray(trace.text) && trace.text.length < count) { diff --git a/src/traces/scattermap/convert.js b/src/traces/scattermap/convert.js index 653fb160ff4..c66d807b696 100644 --- a/src/traces/scattermap/convert.js +++ b/src/traces/scattermap/convert.js @@ -300,8 +300,13 @@ function makeSymbolGeoJSON(calcTrace, gd) { var labels = trace._module.formatLabels(calcPt, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); - var meta = trace._meta || {}; - text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); + text = Lib.texttemplateString({ + args: [pointValues, calcPt, trace._meta], + d3locale: fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels, + string: tt + }); } else { text = fillText(i); } diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index b935bf5256e..370d5fbad50 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -300,8 +300,13 @@ function makeSymbolGeoJSON(calcTrace, gd) { var labels = trace._module.formatLabels(calcPt, trace, fullLayout); var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); - var meta = trace._meta || {}; - text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); + text = Lib.texttemplateString({ + args: [pointValues, calcPt, trace._meta], + d3locale: fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels, + string: tt + }); } else { text = fillText(i); } diff --git a/src/traces/sunburst/plot.js b/src/traces/sunburst/plot.js index 11e07cb3049..412f05adc79 100644 --- a/src/traces/sunburst/plot.js +++ b/src/traces/sunburst/plot.js @@ -632,7 +632,13 @@ exports.formatSliceLabel = function (pt, entry, trace, cd, fullLayout) { var ptTx = Lib.castOption(trace, cdi.i, 'text'); if (Lib.isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; obj.customdata = Lib.castOption(trace, cdi.i, 'customdata'); - return Lib.texttemplateString(txt, obj, fullLayout._d3locale, obj, trace._meta || {}); + return Lib.texttemplateString({ + args: [obj, trace._meta], + d3locale: fullLayout._d3locale, + fallback: trace.texttemplatefallback, + labels: obj, + string: txt + }); }; function getInscribedRadiusFraction(pt) { From 77bc2b9a5a02da76854942091c35ce69a2629fa9 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Tue, 7 Oct 2025 17:49:06 -0600 Subject: [PATCH 07/18] Add fallback to calls to texttemplateStringForShapes --- src/components/shapes/display_labels.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/shapes/display_labels.js b/src/components/shapes/display_labels.js index 1993956c803..2906a68e2cd 100644 --- a/src/components/shapes/display_labels.js +++ b/src/components/shapes/display_labels.js @@ -32,12 +32,13 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { if (val !== undefined) templateValues[key] = val; } } - text = Lib.texttemplateStringForShapes( - options.label.texttemplate, - {}, - gd._fullLayout._d3locale, - templateValues - ); + text = Lib.texttemplateStringForShapes({ + args: [templateValues], + d3locale: gd._fullLayout._d3locale, + fallback: options.label.texttemplatefallback, + labels: {}, + string: options.label.texttemplate + }); } else { text = options.label.text; } From 85b67e91dfe7d44e2f1c25bcb10aeda362b67e90 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 6 Oct 2025 14:23:02 -0600 Subject: [PATCH 08/18] Update defaults calculations --- src/components/shapes/defaults.js | 1 + src/components/shapes/draw_newshape/defaults.js | 1 + src/traces/bar/defaults.js | 2 ++ src/traces/barpolar/defaults.js | 1 + src/traces/box/defaults.js | 1 + src/traces/choropleth/defaults.js | 1 + src/traces/choroplethmap/defaults.js | 1 + src/traces/choroplethmapbox/defaults.js | 1 + src/traces/cone/defaults.js | 1 + src/traces/contour/defaults.js | 1 + src/traces/densitymap/defaults.js | 1 + src/traces/densitymapbox/defaults.js | 1 + src/traces/funnel/defaults.js | 1 + src/traces/funnelarea/defaults.js | 2 ++ src/traces/heatmap/defaults.js | 1 + src/traces/heatmap/label_defaults.js | 1 + src/traces/histogram/defaults.js | 1 + src/traces/histogram2d/defaults.js | 1 + src/traces/histogram2dcontour/defaults.js | 1 + src/traces/icicle/defaults.js | 2 ++ src/traces/image/defaults.js | 1 + src/traces/mesh3d/defaults.js | 1 + src/traces/parcats/defaults.js | 2 ++ src/traces/pie/defaults.js | 2 ++ src/traces/scatter/defaults.js | 6 +++++- src/traces/scatter3d/defaults.js | 2 ++ src/traces/scattercarpet/defaults.js | 6 +++++- src/traces/scattergeo/defaults.js | 2 ++ src/traces/scattergl/defaults.js | 2 ++ src/traces/scattermap/defaults.js | 2 ++ src/traces/scattermapbox/defaults.js | 2 ++ src/traces/scatterpolar/defaults.js | 6 +++++- src/traces/scatterpolargl/defaults.js | 6 +++++- src/traces/scattersmith/defaults.js | 6 +++++- src/traces/scatterternary/defaults.js | 6 +++++- src/traces/splom/defaults.js | 1 + src/traces/streamtube/defaults.js | 1 + src/traces/sunburst/defaults.js | 2 ++ src/traces/surface/defaults.js | 1 + src/traces/treemap/defaults.js | 2 ++ src/traces/waterfall/defaults.js | 2 ++ 41 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/components/shapes/defaults.js b/src/components/shapes/defaults.js index c5584cffdb3..728d803cc03 100644 --- a/src/components/shapes/defaults.js +++ b/src/components/shapes/defaults.js @@ -149,6 +149,7 @@ function handleShapeDefaults(shapeIn, shapeOut, fullLayout) { var labelTextTemplate, labelText; if (noPath) { labelTextTemplate = coerce('label.texttemplate'); + coerce('label.texttemplatefallback'); } if (!labelTextTemplate) { labelText = coerce('label.text'); diff --git a/src/components/shapes/draw_newshape/defaults.js b/src/components/shapes/draw_newshape/defaults.js index 6178c6da71a..9ade2c0fc53 100644 --- a/src/components/shapes/draw_newshape/defaults.js +++ b/src/components/shapes/draw_newshape/defaults.js @@ -42,6 +42,7 @@ module.exports = function supplyDrawNewShapeDefaults(layoutIn, layoutOut, coerce var isLine = layoutIn.dragmode === 'drawline'; var labelText = coerce('newshape.label.text'); var labelTextTemplate = coerce('newshape.label.texttemplate'); + coerce('newshape.label.texttemplatefallback'); if (labelText || labelTextTemplate) { coerce('newshape.label.textangle'); var labelTextPosition = coerce('newshape.label.textposition', isLine ? 'middle' : 'middle center'); diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 843f8ce7a14..3ff7f51b7c7 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -39,6 +39,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { @@ -152,6 +153,7 @@ function handleText(traceIn, traceOut, layout, coerce, textposition, opts) { if (moduleHasTextangle) coerce('textangle'); coerce('texttemplate'); + coerce('texttemplatefallback'); } if (hasInside) { diff --git a/src/traces/barpolar/defaults.js b/src/traces/barpolar/defaults.js index b8d1677f9fd..de8b98d62f8 100644 --- a/src/traces/barpolar/defaults.js +++ b/src/traces/barpolar/defaults.js @@ -27,6 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); // var textPosition = coerce('textposition'); // var hasBoth = Array.isArray(textPosition) || textPosition === 'auto'; diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index 132957dc816..a7ea6ce01bb 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -277,6 +277,7 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) { var hoveron = coerce('hoveron'); if (hoveron === 'all' || hoveron.indexOf('points') !== -1) { coerce('hovertemplate'); + coerce('hovertemplatefallback'); } Lib.coerceSelectionMarkerOpacity(traceOut, coerce); diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index 097233b813a..795167eb3ca 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -45,6 +45,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var mlw = coerce('marker.line.width'); if (mlw) coerce('marker.line.color'); diff --git a/src/traces/choroplethmap/defaults.js b/src/traces/choroplethmap/defaults.js index 7eef2b0b02b..68a9dc2d430 100644 --- a/src/traces/choroplethmap/defaults.js +++ b/src/traces/choroplethmap/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var mlw = coerce('marker.line.width'); if (mlw) coerce('marker.line.color'); diff --git a/src/traces/choroplethmapbox/defaults.js b/src/traces/choroplethmapbox/defaults.js index 7eef2b0b02b..68a9dc2d430 100644 --- a/src/traces/choroplethmapbox/defaults.js +++ b/src/traces/choroplethmapbox/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var mlw = coerce('marker.line.width'); if (mlw) coerce('marker.line.color'); diff --git a/src/traces/cone/defaults.js b/src/traces/cone/defaults.js index da9385fea39..459be0f43f9 100644 --- a/src/traces/cone/defaults.js +++ b/src/traces/cone/defaults.js @@ -55,6 +55,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('uhoverformat'); coerce('vhoverformat'); coerce('whoverformat'); diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js index aced53a1de1..220ff69d965 100644 --- a/src/traces/contour/defaults.js +++ b/src/traces/contour/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hoverongaps'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var isConstraint = coerce('contours.type') === 'constraint'; coerce('connectgaps', Lib.isArray1D(traceOut.z)); diff --git a/src/traces/densitymap/defaults.js b/src/traces/densitymap/defaults.js index 38652d515a0..25d04a7f72d 100644 --- a/src/traces/densitymap/defaults.js +++ b/src/traces/densitymap/defaults.js @@ -27,6 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/densitymapbox/defaults.js b/src/traces/densitymapbox/defaults.js index 38652d515a0..25d04a7f72d 100644 --- a/src/traces/densitymapbox/defaults.js +++ b/src/traces/densitymapbox/defaults.js @@ -27,6 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/funnel/defaults.js b/src/traces/funnel/defaults.js index a2f6c79a503..d7c875518fb 100644 --- a/src/traces/funnel/defaults.js +++ b/src/traces/funnel/defaults.js @@ -32,6 +32,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { diff --git a/src/traces/funnelarea/defaults.js b/src/traces/funnelarea/defaults.js index dbf132044ba..949659d8c56 100644 --- a/src/traces/funnelarea/defaults.js +++ b/src/traces/funnelarea/defaults.js @@ -37,11 +37,13 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var textData = coerce('text'); var textTemplate = coerce('texttemplate'); + coerce('texttemplatefallback'); var textInfo; if (!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js index 8cd3d5ba0ea..98c35bb980b 100644 --- a/src/traces/heatmap/defaults.js +++ b/src/traces/heatmap/defaults.js @@ -27,6 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); handleHeatmapLabelDefaults(coerce, layout); handleStyleDefaults(traceIn, traceOut, coerce, layout); diff --git a/src/traces/heatmap/label_defaults.js b/src/traces/heatmap/label_defaults.js index 4ed899111b4..84e3ba7db1f 100644 --- a/src/traces/heatmap/label_defaults.js +++ b/src/traces/heatmap/label_defaults.js @@ -4,6 +4,7 @@ var Lib = require('../../lib'); module.exports = function handleHeatmapLabelDefaults(coerce, layout) { coerce('texttemplate'); + coerce('texttemplatefallback'); var fontDflt = Lib.extendFlat({}, layout.font, { color: 'auto', diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index 5ec5c62fbfc..226d5b2bd2e 100644 --- a/src/traces/histogram/defaults.js +++ b/src/traces/histogram/defaults.js @@ -35,6 +35,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); diff --git a/src/traces/histogram2d/defaults.js b/src/traces/histogram2d/defaults.js index e8adbc350e1..3c085939fe0 100644 --- a/src/traces/histogram2d/defaults.js +++ b/src/traces/histogram2d/defaults.js @@ -19,6 +19,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleStyleDefaults(traceIn, traceOut, coerce, layout); colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); coerce('hovertemplate'); + coerce('hovertemplatefallback'); handleHeatmapLabelDefaults(coerce, layout); diff --git a/src/traces/histogram2dcontour/defaults.js b/src/traces/histogram2dcontour/defaults.js index 4016ebd7162..ef5b647e586 100644 --- a/src/traces/histogram2dcontour/defaults.js +++ b/src/traces/histogram2dcontour/defaults.js @@ -25,6 +25,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('xhoverformat'); coerce('yhoverformat'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); if (traceOut.contours && traceOut.contours.coloring === 'heatmap') { handleHeatmapLabelDefaults(coerce, layout); } diff --git a/src/traces/icicle/defaults.js b/src/traces/icicle/defaults.js index 766035db64f..4c4ad48332a 100644 --- a/src/traces/icicle/defaults.js +++ b/src/traces/icicle/defaults.js @@ -41,10 +41,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var text = coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var hasPathbar = coerce('pathbar.visible'); diff --git a/src/traces/image/defaults.js b/src/traces/image/defaults.js index 54a648c8cac..9a2efa00528 100644 --- a/src/traces/image/defaults.js +++ b/src/traces/image/defaults.js @@ -43,6 +43,7 @@ module.exports = function supplyDefaults(traceIn, traceOut) { coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); traceOut._length = null; diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index 1653488afa4..26cf1b475b3 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -87,6 +87,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); diff --git a/src/traces/parcats/defaults.js b/src/traces/parcats/defaults.js index eca598c0259..b967d6050aa 100644 --- a/src/traces/parcats/defaults.js +++ b/src/traces/parcats/defaults.js @@ -13,6 +13,7 @@ var isTypedArraySpec = require('../../lib/array').isTypedArraySpec; function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { coerce('line.shape'); coerce('line.hovertemplate'); + coerce('line.hovertemplatefallback'); var lineColor = coerce('line.color', layout.colorway[0]); if (hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) { @@ -89,6 +90,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hoveron'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('arrangement'); coerce('bundlecolors'); coerce('sortpaths'); diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 1291d04cd79..f27c890de77 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -80,11 +80,13 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { var textData = coerce('text'); var textTemplate = coerce('texttemplate'); + coerce('texttemplatefallback'); var textInfo; if (!textTemplate) textInfo = coerce('textinfo', Lib.isArrayOrTypedArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 670059b44ac..b32e5a8fe2a 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -55,6 +55,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce); } @@ -84,7 +85,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); - if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'y' }); errorBarsSupplyDefaults(traceIn, traceOut, lineColor || markerColor || defaultColor, { axis: 'x', inherit: 'y' }); diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index 65a60ef988d..e99182fef69 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -24,6 +24,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); @@ -41,6 +42,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noSelect: true, noFontShadow: true, diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index b2a253b3f0d..2b710791df7 100644 --- a/src/traces/scattercarpet/defaults.js +++ b/src/traces/scattercarpet/defaults.js @@ -36,6 +36,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); coerce('hovertext'); var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; @@ -73,7 +74,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var hoverOn = coerce('hoveron', dfltHoverOn.join('+') || 'points'); - if (hoverOn !== 'fills') coerce('hovertemplate'); + if (hoverOn !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } coerce('zorder'); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index 015bd849e5e..88e20a17ff7 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -58,6 +58,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('mode'); if (subTypes.hasMarkers(traceOut)) { @@ -71,6 +72,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce); } diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index ecda5087fdf..efaecbb5e63 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -37,6 +37,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('mode', defaultMode); if (subTypes.hasMarkers(traceOut)) { @@ -52,6 +53,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noFontShadow: true, noFontLineposition: true, diff --git a/src/traces/scattermap/defaults.js b/src/traces/scattermap/defaults.js index 0fe5eb99aea..faeb083ac08 100644 --- a/src/traces/scattermap/defaults.js +++ b/src/traces/scattermap/defaults.js @@ -27,8 +27,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('mode'); coerce('below'); diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index 0fe5eb99aea..faeb083ac08 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -27,8 +27,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('mode'); coerce('below'); diff --git a/src/traces/scatterpolar/defaults.js b/src/traces/scatterpolar/defaults.js index 0458cd3d942..a3260367ce7 100644 --- a/src/traces/scatterpolar/defaults.js +++ b/src/traces/scatterpolar/defaults.js @@ -27,7 +27,10 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } if (subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); @@ -41,6 +44,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce); } diff --git a/src/traces/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index e4a0a46255b..7f095c4ea71 100644 --- a/src/traces/scatterpolargl/defaults.js +++ b/src/traces/scatterpolargl/defaults.js @@ -27,7 +27,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } if (subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { noAngleRef: true, noStandOff: true }); @@ -40,6 +43,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noFontShadow: true, noFontLineposition: true, diff --git a/src/traces/scattersmith/defaults.js b/src/traces/scattersmith/defaults.js index 05792c3a759..7765e0212e6 100644 --- a/src/traces/scattersmith/defaults.js +++ b/src/traces/scattersmith/defaults.js @@ -26,7 +26,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('mode', len < PTS_LINESONLY ? 'lines+markers' : 'lines'); coerce('text'); coerce('hovertext'); - if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } if (subTypes.hasMarkers(traceOut)) { handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, { gradient: true }); @@ -40,6 +43,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce); } diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 895d060b01d..6be1e2d0363 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -49,7 +49,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); - if (traceOut.hoveron !== 'fills') coerce('hovertemplate'); + if (traceOut.hoveron !== 'fills') { + coerce('hovertemplate'); + coerce('hovertemplatefallback'); + } var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('mode', defaultMode); @@ -66,6 +69,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if (subTypes.hasText(traceOut)) { coerce('texttemplate'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce); } diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index 34b2737140f..d3c4154b9ba 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); diff --git a/src/traces/streamtube/defaults.js b/src/traces/streamtube/defaults.js index 34f5e8266bb..f49de256e21 100644 --- a/src/traces/streamtube/defaults.js +++ b/src/traces/streamtube/defaults.js @@ -57,6 +57,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('uhoverformat'); coerce('vhoverformat'); coerce('whoverformat'); diff --git a/src/traces/sunburst/defaults.js b/src/traces/sunburst/defaults.js index c62da03ccbf..2439c817df6 100644 --- a/src/traces/sunburst/defaults.js +++ b/src/traces/sunburst/defaults.js @@ -45,10 +45,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var text = coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var textposition = 'auto'; handleText(traceIn, traceOut, layout, coerce, textposition, { diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 195bc21c4b5..9da3b2e3d26 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -66,6 +66,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); diff --git a/src/traces/treemap/defaults.js b/src/traces/treemap/defaults.js index 089bf65f318..b877b34bedd 100644 --- a/src/traces/treemap/defaults.js +++ b/src/traces/treemap/defaults.js @@ -45,10 +45,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var text = coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var hasPathbar = coerce('pathbar.visible'); diff --git a/src/traces/waterfall/defaults.js b/src/traces/waterfall/defaults.js index 4c43922d9b5..203f7af7ce2 100644 --- a/src/traces/waterfall/defaults.js +++ b/src/traces/waterfall/defaults.js @@ -46,6 +46,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { @@ -59,6 +60,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { if (traceOut.textposition !== 'none') { coerce('texttemplate'); + coerce('texttemplatefallback'); if (!traceOut.texttemplate) coerce('textinfo'); } From 680c7930ce7cdeee2fef8375ebcc0489a8de9baa Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 6 Oct 2025 15:39:37 -0600 Subject: [PATCH 09/18] Add helper function for template fallback attributes --- src/plots/template_attributes.js | 84 +++++++++++++++----------------- 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/src/plots/template_attributes.js b/src/plots/template_attributes.js index 2d9b1188c97..063753892c7 100644 --- a/src/plots/template_attributes.js +++ b/src/plots/template_attributes.js @@ -1,8 +1,7 @@ 'use strict'; const { DATE_FORMAT_LINK, FORMAT_LINK } = require('../constants/docs'); -function templateFormatStringDescription(opts = {}) { - const { supportOther } = opts; +function templateFormatStringDescription({ supportOther } = {}) { const supportOtherText = ' as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown.'; @@ -34,53 +33,43 @@ function describeVariables({ description, keys = [] }) { return descPart; } -exports.hovertemplateAttrs = (opts = {}, extra = {}) => { - const hovertemplate = { - valType: 'string', - dflt: '', - editType: opts.editType || 'none', - description: [ - 'Template string used for rendering the information that appear on hover box.', - 'Note that this will override `hoverinfo`.', - templateFormatStringDescription({ supportOther: true }), - 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.', - 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', - describeVariables(extra), - 'Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`.', - 'To hide the secondary box completely, use an empty tag ``.' - ].join(' ') - }; - - if (opts.arrayOk !== false) hovertemplate.arrayOk = true; - - return hovertemplate; -}; - -exports.texttemplateAttrs = (opts = {}, extra = {}) => { - const texttemplate = { - valType: 'string', - dflt: '', - editType: opts.editType || 'calc', - description: [ - 'Template string used for rendering the information text that appear on points.', - 'Note that this will override `textinfo`.', - templateFormatStringDescription(), - 'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', - describeVariables(extra) - ].join(' ') - }; - - if (opts.arrayOk !== false) texttemplate.arrayOk = true; +exports.hovertemplateAttrs = ({ editType = 'none', arrayOk } = {}, extra = {}) => ({ + valType: 'string', + dflt: '', + editType, + description: [ + 'Template string used for rendering the information that appear on hover box.', + 'Note that this will override `hoverinfo`.', + templateFormatStringDescription({ supportOther: true }), + 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.', + 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', + describeVariables(extra), + 'Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`.', + 'To hide the secondary box completely, use an empty tag ``.' + ].join(' '), + ...(arrayOk !== false ? { arrayOk: true } : {}) +}); - return texttemplate; -}; +exports.texttemplateAttrs = ({ editType = 'calc', arrayOk } = {}, extra = {}) => ({ + valType: 'string', + dflt: '', + editType, + description: [ + 'Template string used for rendering the information text that appears on points.', + 'Note that this will override `textinfo`.', + templateFormatStringDescription(), + 'Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', + describeVariables(extra) + ].join(' '), + ...(arrayOk !== false ? { arrayOk: true } : {}) +}); -exports.shapeTexttemplateAttrs = (opts = {}, extra = {}) => ({ +exports.shapeTexttemplateAttrs = ({ editType = 'arraydraw', newshape }, extra = {}) => ({ valType: 'string', dflt: '', - editType: opts.editType || 'arraydraw', + editType, description: [ - `Template string used for rendering the ${opts.newshape ? 'new ' : ''}shape's label.`, + `Template string used for rendering the ${newshape ? 'new ' : ''}shape's label.`, 'Note that this will override `text`.', 'Variables are inserted using %{variable},', 'for example "x0: %{x0}".', @@ -97,3 +86,10 @@ exports.shapeTexttemplateAttrs = (opts = {}, extra = {}) => ({ describeVariables(extra) ].join(' ') }); + +exports.templatefallbackAttrs = ({ editType = 'none' } = {}) => ({ + valType: 'string', + dflt: '', + editType, + description: "Fallback value that's displayed when a variable referenced in a template can't be found." +}); From 52c29ccc8c591c527085f08e6399ebd6ea889bd8 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Tue, 7 Oct 2025 11:07:58 -0600 Subject: [PATCH 10/18] Add fallback value to attributes files --- src/components/shapes/attributes.js | 3 +- .../shapes/draw_newshape/attributes.js | 3 +- src/traces/bar/attributes.js | 19 ++---- src/traces/barpolar/attributes.js | 3 +- src/traces/box/attributes.js | 3 +- src/traces/choropleth/attributes.js | 3 +- src/traces/choroplethmap/attributes.js | 3 +- src/traces/choroplethmapbox/attributes.js | 3 +- src/traces/cone/attributes.js | 3 +- src/traces/contour/attributes.js | 2 + src/traces/densitymap/attributes.js | 3 +- src/traces/densitymapbox/attributes.js | 3 +- src/traces/funnel/attributes.js | 19 ++---- src/traces/funnelarea/attributes.js | 19 ++---- src/traces/heatmap/attributes.js | 28 +++----- src/traces/histogram/attributes.js | 24 ++----- src/traces/histogram2d/attributes.js | 5 +- src/traces/histogram2dcontour/attributes.js | 2 + src/traces/icicle/attributes.js | 19 ++---- src/traces/image/attributes.js | 10 +-- src/traces/isosurface/attributes.js | 3 +- src/traces/mesh3d/attributes.js | 3 +- src/traces/parcats/attributes.js | 6 +- src/traces/pie/attributes.js | 19 ++---- src/traces/sankey/attributes.js | 4 +- src/traces/scatter/attributes.js | 14 ++-- src/traces/scatter3d/attributes.js | 7 +- src/traces/scattercarpet/attributes.js | 12 ++-- src/traces/scattergeo/attributes.js | 14 ++-- src/traces/scattergl/attributes.js | 2 + src/traces/scattermap/attributes.js | 14 ++-- src/traces/scattermapbox/attributes.js | 14 ++-- src/traces/scatterpolar/attributes.js | 12 ++-- src/traces/scatterpolargl/attributes.js | 64 ++++++------------- src/traces/scattersmith/attributes.js | 12 ++-- src/traces/scatterternary/attributes.js | 14 ++-- src/traces/splom/attributes.js | 3 +- src/traces/streamtube/attributes.js | 7 +- src/traces/sunburst/attributes.js | 19 ++---- src/traces/surface/attributes.js | 3 +- src/traces/treemap/attributes.js | 19 ++---- src/traces/violin/attributes.js | 1 + src/traces/volume/attributes.js | 3 +- src/traces/waterfall/attributes.js | 19 ++---- 44 files changed, 169 insertions(+), 296 deletions(-) diff --git a/src/components/shapes/attributes.js b/src/components/shapes/attributes.js index 6902aa4cd74..953b1613d58 100644 --- a/src/components/shapes/attributes.js +++ b/src/components/shapes/attributes.js @@ -8,7 +8,7 @@ var extendFlat = require('../../lib/extend').extendFlat; var templatedArray = require('../../plot_api/plot_template').templatedArray; var axisPlaceableObjs = require('../../constants/axis_placeable_objects'); var basePlotAttributes = require('../../plots/attributes'); -var shapeTexttemplateAttrs = require('../../plots/template_attributes').shapeTexttemplateAttrs; +const { shapeTexttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var shapeLabelTexttemplateVars = require('./label_texttemplate'); module.exports = templatedArray('shape', { @@ -331,6 +331,7 @@ module.exports = templatedArray('shape', { ].join(' ') }, texttemplate: shapeTexttemplateAttrs({}, { keys: Object.keys(shapeLabelTexttemplateVars) }), + texttemplatefallback: templatefallbackAttrs(), font: fontAttrs({ editType: 'calc+arraydraw', colorEditType: 'arraydraw', diff --git a/src/components/shapes/draw_newshape/attributes.js b/src/components/shapes/draw_newshape/attributes.js index 86f2d25b0c3..30671801f26 100644 --- a/src/components/shapes/draw_newshape/attributes.js +++ b/src/components/shapes/draw_newshape/attributes.js @@ -5,7 +5,7 @@ var basePlotAttributes = require('../../../plots/attributes'); var fontAttrs = require('../../../plots/font_attributes'); var dash = require('../../drawing/attributes').dash; var extendFlat = require('../../../lib/extend').extendFlat; -var shapeTexttemplateAttrs = require('../../../plots/template_attributes').shapeTexttemplateAttrs; +const { shapeTexttemplateAttrs, templatefallbackAttrs } = require('../../../plots/template_attributes'); var shapeLabelTexttemplateVars = require('../label_texttemplate'); module.exports = overrideAll( @@ -150,6 +150,7 @@ module.exports = overrideAll( { newshape: true }, { keys: Object.keys(shapeLabelTexttemplateVars) } ), + texttemplatefallback: templatefallbackAttrs(), font: fontAttrs({ description: 'Sets the new shape label text font.' }), diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 50981430039..c9c2307eeda 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -2,8 +2,7 @@ var scatterAttrs = require('../scatter/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var fontAttrs = require('../../plots/font_attributes'); var constants = require('./constants'); @@ -78,19 +77,11 @@ module.exports = { yhoverformat: axisHoverFormat('y'), text: scatterAttrs.text, - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys }), + texttemplatefallback: templatefallbackAttrs(), hovertext: scatterAttrs.hovertext, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), textposition: { valType: 'enumerated', diff --git a/src/traces/barpolar/attributes.js b/src/traces/barpolar/attributes.js index c7d41c74a5a..a15127ee42c 100644 --- a/src/traces/barpolar/attributes.js +++ b/src/traces/barpolar/attributes.js @@ -1,6 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var scatterPolarAttrs = require('../scatterpolar/attributes'); var barAttrs = require('../bar/attributes'); @@ -59,6 +59,7 @@ module.exports = { hoverinfo: scatterPolarAttrs.hoverinfo, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: barAttrs.selected, unselected: barAttrs.unselected diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js index f2e90228f0e..19178d10d1e 100644 --- a/src/traces/box/attributes.js +++ b/src/traces/box/attributes.js @@ -5,7 +5,7 @@ var scatterAttrs = require('../scatter/attributes'); var barAttrs = require('../bar/attributes'); var colorAttrs = require('../../components/color/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var scatterMarkerAttrs = scatterAttrs.marker; @@ -410,6 +410,7 @@ module.exports = { }), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: 'Same as `text`.' }), hovertemplate: hovertemplateAttrs({ description: 'N.B. This only has an effect when hovering on points.' }), + hovertemplatefallback: templatefallbackAttrs(), hoveron: { valType: 'flaglist', diff --git a/src/traces/choropleth/attributes.js b/src/traces/choropleth/attributes.js index d601a4e918a..069e2432f39 100644 --- a/src/traces/choropleth/attributes.js +++ b/src/traces/choropleth/attributes.js @@ -1,6 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var scatterGeoAttrs = require('../scattergeo/attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -84,6 +84,7 @@ module.exports = extendFlat( flags: ['location', 'z', 'text', 'name'] }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, diff --git a/src/traces/choroplethmap/attributes.js b/src/traces/choroplethmap/attributes.js index 5d1d64def74..dae30b0c5da 100644 --- a/src/traces/choroplethmap/attributes.js +++ b/src/traces/choroplethmap/attributes.js @@ -2,7 +2,7 @@ var choroplethAttrs = require('../choropleth/attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -98,6 +98,7 @@ module.exports = extendFlat( hoverinfo: choroplethAttrs.hoverinfo, hovertemplate: hovertemplateAttrs({}, { keys: ['properties'] }), + hovertemplatefallback: templatefallbackAttrs(), showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, diff --git a/src/traces/choroplethmapbox/attributes.js b/src/traces/choroplethmapbox/attributes.js index ee39ac4cf7b..00f89532c7b 100644 --- a/src/traces/choroplethmapbox/attributes.js +++ b/src/traces/choroplethmapbox/attributes.js @@ -2,7 +2,7 @@ var choroplethAttrs = require('../choropleth/attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -98,6 +98,7 @@ module.exports = extendFlat( hoverinfo: choroplethAttrs.hoverinfo, hovertemplate: hovertemplateAttrs({}, { keys: ['properties'] }), + hovertemplatefallback: templatefallbackAttrs(), showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, diff --git a/src/traces/cone/attributes.js b/src/traces/cone/attributes.js index 7d121e3e57d..cde96d305a6 100644 --- a/src/traces/cone/attributes.js +++ b/src/traces/cone/attributes.js @@ -2,7 +2,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var mesh3dAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -146,6 +146,7 @@ var attrs = { }, hovertemplate: hovertemplateAttrs({ editType: 'calc' }, { keys: ['norm'] }), + hovertemplatefallback: templatefallbackAttrs(), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 1c6be4e85b8..e28bab378a6 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -42,12 +42,14 @@ module.exports = extendFlat( yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z', 1), hovertemplate: heatmapAttrs.hovertemplate, + hovertemplatefallback: heatmapAttrs.hovertemplatefallback, texttemplate: extendFlat({}, heatmapAttrs.texttemplate, { description: [ 'For this trace it only has an effect if `coloring` is set to *heatmap*.', heatmapAttrs.texttemplate.description ].join(' ') }), + texttemplatefallback: heatmapAttrs.texttemplatefallback, textfont: extendFlat({}, heatmapAttrs.textfont, { description: [ 'For this trace it only has an effect if `coloring` is set to *heatmap*.', diff --git a/src/traces/densitymap/attributes.js b/src/traces/densitymap/attributes.js index 11088da3350..838dc9297c8 100644 --- a/src/traces/densitymap/attributes.js +++ b/src/traces/densitymap/attributes.js @@ -1,7 +1,7 @@ 'use strict'; var colorScaleAttrs = require('../../components/colorscale/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var scatterMapAttrs = require('../scattermap/attributes'); @@ -76,6 +76,7 @@ module.exports = extendFlat( flags: ['lon', 'lat', 'z', 'text', 'name'] }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, colorScaleAttrs('', { diff --git a/src/traces/densitymapbox/attributes.js b/src/traces/densitymapbox/attributes.js index 8e21730551f..e18180c716c 100644 --- a/src/traces/densitymapbox/attributes.js +++ b/src/traces/densitymapbox/attributes.js @@ -1,7 +1,7 @@ 'use strict'; var colorScaleAttrs = require('../../components/colorscale/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var scatterMapboxAttrs = require('../scattermapbox/attributes'); @@ -76,6 +76,7 @@ module.exports = extendFlat( flags: ['lon', 'lat', 'z', 'text', 'name'] }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, colorScaleAttrs('', { diff --git a/src/traces/funnel/attributes.js b/src/traces/funnel/attributes.js index 19b04dc76d7..9ffb0f84de1 100644 --- a/src/traces/funnel/attributes.js +++ b/src/traces/funnel/attributes.js @@ -4,8 +4,7 @@ var barAttrs = require('../bar/attributes'); var lineAttrs = require('../scatter/attributes').line; var baseAttrs = require('../../plots/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var constants = require('./constants'); var extendFlat = require('../../lib/extend').extendFlat; var Color = require('../../components/color'); @@ -28,12 +27,8 @@ module.exports = { yhoverformat: axisHoverFormat('y'), hovertext: barAttrs.hovertext, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['name', 'x', 'y', 'text', 'percent initial', 'percent previous', 'percent total'] @@ -52,12 +47,8 @@ module.exports = { ].join(' ') }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys.concat(['label', 'value']) - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys.concat(['label', 'value']) }), + texttemplatefallback: templatefallbackAttrs(), text: barAttrs.text, textposition: barAttrs.textposition, diff --git a/src/traces/funnelarea/attributes.js b/src/traces/funnelarea/attributes.js index ff0024bd866..9ed3c3f6882 100644 --- a/src/traces/funnelarea/attributes.js +++ b/src/traces/funnelarea/attributes.js @@ -3,8 +3,7 @@ var pieAttrs = require('../pie/attributes'); var baseAttrs = require('../../plots/attributes'); var domainAttrs = require('../../plots/domain').attributes; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -47,23 +46,15 @@ module.exports = { flags: ['label', 'text', 'value', 'percent'] }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['label', 'color', 'value', 'text', 'percent'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['label', 'color', 'value', 'text', 'percent'] }), + texttemplatefallback: templatefallbackAttrs(), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: hovertemplateAttrs( - {}, - { - keys: ['label', 'color', 'value', 'text', 'percent'] - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: ['label', 'color', 'value', 'text', 'percent'] }), + hovertemplatefallback: templatefallbackAttrs(), textposition: extendFlat({}, pieAttrs.textposition, { values: ['inside', 'none'], diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 08433890ed0..009d74c256f 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -1,14 +1,12 @@ 'use strict'; -var scatterAttrs = require('../scatter/attributes'); -var baseAttrs = require('../../plots/attributes'); -var fontAttrs = require('../../plots/font_attributes'); -var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; -var colorScaleAttrs = require('../../components/colorscale/attributes'); - -var extendFlat = require('../../lib/extend').extendFlat; +const colorScaleAttrs = require('../../components/colorscale/attributes'); +const { extendFlat } = require('../../lib/extend'); +const baseAttrs = require('../../plots/attributes'); +const { axisHoverFormat } = require('../../plots/cartesian/axis_format_attributes'); +const fontAttrs = require('../../plots/font_attributes'); +const { hovertemplateAttrs, templatefallbackAttrs, texttemplateAttrs } = require('../../plots/template_attributes'); +const scatterAttrs = require('../scatter/attributes'); module.exports = extendFlat( { @@ -117,15 +115,9 @@ module.exports = extendFlat( zhoverformat: axisHoverFormat('z', 1), hovertemplate: hovertemplateAttrs(), - texttemplate: texttemplateAttrs( - { - arrayOk: false, - editType: 'plot' - }, - { - keys: ['x', 'y', 'z', 'text'] - } - ), + hovertemplatefallback: templatefallbackAttrs(), + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['x', 'y', 'z', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), textfont: fontAttrs({ editType: 'plot', autoSize: true, diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 51a7cc61e1e..54bf47a3aea 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -2,8 +2,7 @@ var barAttrs = require('../bar/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var fontAttrs = require('../../plots/font_attributes'); var makeBinAttrs = require('./bin_attributes'); var constants = require('./constants'); @@ -192,22 +191,11 @@ module.exports = { ].join(' ') }, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), - - texttemplate: texttemplateAttrs( - { - arrayOk: false, - editType: 'plot' - }, - { - keys: ['label', 'value'] - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), + + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['label', 'value'] }), + texttemplatefallback: templatefallbackAttrs(), textposition: extendFlat({}, barAttrs.textposition, { arrayOk: false diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 21f85ba4fd0..3f404629c8b 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -5,8 +5,7 @@ var makeBinAttrs = require('../histogram/bin_attributes'); var heatmapAttrs = require('../heatmap/attributes'); var baseAttrs = require('../../plots/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -70,7 +69,9 @@ module.exports = extendFlat( yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z', 1), hovertemplate: hovertemplateAttrs({}, { keys: ['z'] }), + hovertemplatefallback: templatefallbackAttrs(), texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['z'] }), + texttemplatefallback: templatefallbackAttrs(), textfont: heatmapAttrs.textfont, showlegend: extendFlat({}, baseAttrs.showlegend, { dflt: false }) }, diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index 037392d8a59..94b787e1524 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -44,7 +44,9 @@ module.exports = extendFlat( yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z', 1), hovertemplate: histogram2dAttrs.hovertemplate, + hovertemplatefallback: histogram2dAttrs.hovertemplatefallback, texttemplate: contourAttrs.texttemplate, + texttemplatefallback: contourAttrs.texttemplatefallback, textfont: contourAttrs.textfont }, colorScaleAttrs('', { diff --git a/src/traces/icicle/attributes.js b/src/traces/icicle/attributes.js index 60d2fa33851..39326270e85 100644 --- a/src/traces/icicle/attributes.js +++ b/src/traces/icicle/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var domainAttrs = require('../../plots/domain').attributes; @@ -77,21 +76,13 @@ module.exports = { text: pieAttrs.text, textinfo: sunburstAttrs.textinfo, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys.concat(['label', 'value']) - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys.concat(['label', 'value']) }), + texttemplatefallback: templatefallbackAttrs(), hovertext: pieAttrs.hovertext, hoverinfo: sunburstAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), textfont: pieAttrs.textfont, insidetextfont: pieAttrs.insidetextfont, diff --git a/src/traces/image/attributes.js b/src/traces/image/attributes.js index 77bf13d66c5..2d9ce34163c 100644 --- a/src/traces/image/attributes.js +++ b/src/traces/image/attributes.js @@ -2,7 +2,7 @@ var baseAttrs = require('../../plots/attributes'); var zorder = require('../scatter/attributes').zorder; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var colormodel = require('./constants').colormodel; @@ -130,12 +130,8 @@ module.exports = extendFlat({ flags: ['x', 'y', 'z', 'color', 'name', 'text'], dflt: 'x+y+z+text+name' }), - hovertemplate: hovertemplateAttrs( - {}, - { - keys: ['z', 'color', 'colormodel'] - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: ['z', 'color', 'colormodel'] }), + hovertemplatefallback: templatefallbackAttrs(), zorder: zorder }); diff --git a/src/traces/isosurface/attributes.js b/src/traces/isosurface/attributes.js index 5a51a04d920..ad8e0856709 100644 --- a/src/traces/isosurface/attributes.js +++ b/src/traces/isosurface/attributes.js @@ -2,7 +2,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var meshAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -197,6 +197,7 @@ var attrs = (module.exports = overrideAll( description: 'Same as `text`.' }, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), zhoverformat: axisHoverFormat('z'), diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index 53df77ad9c3..b5a8d4138c1 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -2,7 +2,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var surfaceAttrs = require('../surface/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -88,6 +88,7 @@ module.exports = extendFlat( description: 'Same as `text`.' }, hovertemplate: hovertemplateAttrs({ editType: 'calc' }), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js index dd7013d840e..960c173440b 100644 --- a/src/traces/parcats/attributes.js +++ b/src/traces/parcats/attributes.js @@ -4,7 +4,7 @@ var extendFlat = require('../../lib/extend').extendFlat; var baseAttrs = require('../../plots/attributes'); var fontAttrs = require('../../plots/font_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var domainAttrs = require('../../plots/domain').attributes; var line = extendFlat({ editType: 'calc' }, colorScaleAttrs('line', { editTypeOverride: 'calc' }), { @@ -29,7 +29,8 @@ var line = extendFlat({ editType: 'calc' }, colorScaleAttrs('line', { editTypeOv keys: ['count', 'probability'], description: ['This value here applies when hovering over lines.'].join(' ') } - ) + ), + hovertemplatefallback: templatefallbackAttrs() }); module.exports = { @@ -66,6 +67,7 @@ module.exports = { ].join(' ') } ), + hovertemplatefallback: templatefallbackAttrs(), arrangement: { valType: 'enumerated', diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index dc9f39a6b4f..e7ea1930cb7 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -4,8 +4,7 @@ var baseAttrs = require('../../plots/attributes'); var domainAttrs = require('../../plots/domain').attributes; var fontAttrs = require('../../plots/font_attributes'); var colorAttrs = require('../../components/color/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var pattern = require('../../components/drawing/attributes').pattern; @@ -139,18 +138,10 @@ module.exports = { hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: hovertemplateAttrs( - {}, - { - keys: ['label', 'color', 'value', 'percent', 'text'] - } - ), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['label', 'color', 'value', 'percent', 'text'] - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: ['label', 'color', 'value', 'percent', 'text'] }), + hovertemplatefallback: templatefallbackAttrs(), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['label', 'color', 'value', 'percent', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), textposition: { valType: 'enumerated', values: ['inside', 'outside', 'auto', 'none'], diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index e533a76a279..a2ee49f0083 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -5,7 +5,7 @@ var baseAttrs = require('../../plots/attributes'); var colorAttrs = require('../../components/color/attributes'); var fxAttrs = require('../../components/fx/attributes'); var domainAttrs = require('../../plots/domain').attributes; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var templatedArray = require('../../plot_api/plot_template').templatedArray; var descriptionOnlyNumbers = require('../../plots/cartesian/axis_format_attributes').descriptionOnlyNumbers; @@ -163,6 +163,7 @@ var attrs = (module.exports = overrideAll( keys: ['value', 'label'] } ), + hovertemplatefallback: templatefallbackAttrs(), align: { valType: 'enumerated', values: ['justify', 'left', 'right', 'center'], @@ -254,6 +255,7 @@ var attrs = (module.exports = overrideAll( keys: ['value', 'label'] } ), + hovertemplatefallback: templatefallbackAttrs(), colorscales: templatedArray('concentrationscales', { editType: 'calc', label: { diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 3a29e1b075a..94e1de454a5 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -1,8 +1,7 @@ 'use strict'; var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var fontAttrs = require('../../plots/font_attributes'); var dash = require('../../components/drawing/attributes').dash; @@ -225,7 +224,8 @@ module.exports = { ].join(' ') }, - texttemplate: texttemplateAttrs({}, {}), + texttemplate: texttemplateAttrs(), + texttemplatefallback: templatefallbackAttrs(), hovertext: { valType: 'string', dflt: '', @@ -266,12 +266,8 @@ module.exports = { 'or text, then the default is *fills*, otherwise it is *points*.' ].join(' ') }, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), line: { color: { diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 90df4ddac84..b97f6e475ec 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -4,8 +4,7 @@ var scatterAttrs = require('../scatter/attributes'); var fontAttrs = require('../../plots/font_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var DASHES = require('../../constants/gl3d_dashes'); @@ -75,7 +74,8 @@ var attrs = (module.exports = overrideAll( 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs({}, {}), + texttemplate: texttemplateAttrs(), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets text elements associated with each (x,y,z) triplet.', @@ -87,6 +87,7 @@ var attrs = (module.exports = overrideAll( ].join(' ') }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index 066cc88f515..d7db53208a8 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -3,8 +3,7 @@ var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterAttrs = require('../scatter/attributes'); var baseAttrs = require('../../plots/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -45,12 +44,8 @@ module.exports = { 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['a', 'b', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['a', 'b', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (a,b) point.', @@ -123,5 +118,6 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), zorder: scatterAttrs.zorder }; diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index f547883d4f1..457d1420504 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterAttrs = require('../scatter/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -94,12 +93,8 @@ module.exports = overrideAll( 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['lat', 'lon', 'location', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['lat', 'lon', 'location', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (lon,lat) pair', @@ -172,7 +167,8 @@ module.exports = overrideAll( hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['lon', 'lat', 'location', 'text', 'name'] }), - hovertemplate: hovertemplateAttrs() + hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs() }, 'calc', 'nested' diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 2d62563098a..8f05e9f5cc4 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -109,4 +109,6 @@ var attrs = (module.exports = overrideAll( attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; attrs.hovertemplate = scatterAttrs.hovertemplate; +attrs.hovertemplatefallback = scatterAttrs.hovertemplatefallback; attrs.texttemplate = scatterAttrs.texttemplate; +attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; diff --git a/src/traces/scattermap/attributes.js b/src/traces/scattermap/attributes.js index 132a7b9229c..e13e14aa72f 100644 --- a/src/traces/scattermap/attributes.js +++ b/src/traces/scattermap/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterGeoAttrs = require('../scattergeo/attributes'); var scatterAttrs = require('../scatter/attributes'); @@ -85,12 +84,8 @@ module.exports = overrideAll( 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['lat', 'lon', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['lat', 'lon', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (lon,lat) pair', @@ -177,7 +172,8 @@ module.exports = overrideAll( hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['lon', 'lat', 'text', 'name'] }), - hovertemplate: hovertemplateAttrs() + hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs() }, 'calc', 'nested' diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 521415041a9..e81e3977ae2 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterGeoAttrs = require('../scattergeo/attributes'); var scatterAttrs = require('../scatter/attributes'); @@ -85,12 +84,8 @@ module.exports = overrideAll( 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['lat', 'lon', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['lat', 'lon', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (lon,lat) pair', @@ -177,7 +172,8 @@ module.exports = overrideAll( hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['lon', 'lat', 'text', 'name'] }), - hovertemplate: hovertemplateAttrs() + hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs() }, 'calc', 'nested' diff --git a/src/traces/scatterpolar/attributes.js b/src/traces/scatterpolar/attributes.js index 283f43c27bc..5159f426c56 100644 --- a/src/traces/scatterpolar/attributes.js +++ b/src/traces/scatterpolar/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterAttrs = require('../scatter/attributes'); @@ -74,12 +73,8 @@ module.exports = { }, text: scatterAttrs.text, - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['r', 'theta', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['r', 'theta', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: scatterAttrs.hovertext, line: { @@ -128,6 +123,7 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: scatterAttrs.selected, unselected: scatterAttrs.unselected diff --git a/src/traces/scatterpolargl/attributes.js b/src/traces/scatterpolargl/attributes.js index cc2b511a94c..80f896b4dbc 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -1,50 +1,24 @@ 'use strict'; -var scatterPolarAttrs = require('../scatterpolar/attributes'); -var scatterGlAttrs = require('../scattergl/attributes'); -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +// No cliponaxis or hoveron +const { cliponaxis, hoveron, ...scatterPolarAttrs } = require('../scatterpolar/attributes'); +const { + connectgaps, + line: { color, dash, width }, + fill, + fillcolor, + marker, + textfont, + textposition +} = require('../scattergl/attributes'); module.exports = { - mode: scatterPolarAttrs.mode, - r: scatterPolarAttrs.r, - theta: scatterPolarAttrs.theta, - r0: scatterPolarAttrs.r0, - dr: scatterPolarAttrs.dr, - theta0: scatterPolarAttrs.theta0, - dtheta: scatterPolarAttrs.dtheta, - thetaunit: scatterPolarAttrs.thetaunit, - - text: scatterPolarAttrs.text, - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['r', 'theta', 'text'] - } - ), - hovertext: scatterPolarAttrs.hovertext, - hovertemplate: scatterPolarAttrs.hovertemplate, - - line: { - color: scatterGlAttrs.line.color, - width: scatterGlAttrs.line.width, - dash: scatterGlAttrs.line.dash, - editType: 'calc' - }, - - connectgaps: scatterGlAttrs.connectgaps, - - marker: scatterGlAttrs.marker, - // no cliponaxis - - fill: scatterGlAttrs.fill, - fillcolor: scatterGlAttrs.fillcolor, - - textposition: scatterGlAttrs.textposition, - textfont: scatterGlAttrs.textfont, - - hoverinfo: scatterPolarAttrs.hoverinfo, - // no hoveron - - selected: scatterPolarAttrs.selected, - unselected: scatterPolarAttrs.unselected + ...scatterPolarAttrs, + connectgaps, + fill, + fillcolor, + line: { color, dash, editType: 'calc', width }, + marker, + textfont, + textposition }; diff --git a/src/traces/scattersmith/attributes.js b/src/traces/scattersmith/attributes.js index 72b99d44d40..150d0561c3c 100644 --- a/src/traces/scattersmith/attributes.js +++ b/src/traces/scattersmith/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterAttrs = require('../scatter/attributes'); @@ -30,12 +29,8 @@ module.exports = { }, text: scatterAttrs.text, - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['real', 'imag', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['real', 'imag', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: scatterAttrs.hovertext, line: { @@ -79,6 +74,7 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: scatterAttrs.selected, unselected: scatterAttrs.unselected diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 58518b17404..e9991383091 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var makeFillcolorAttr = require('../scatter/fillcolor_attribute'); var scatterAttrs = require('../scatter/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -73,12 +72,8 @@ module.exports = { 'these elements will be seen in the hover labels.' ].join(' ') }), - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: ['a', 'b', 'c', 'text'] - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['a', 'b', 'c', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), hovertext: extendFlat({}, scatterAttrs.hovertext, { description: [ 'Sets hover text elements associated with each (a,b,c) point.', @@ -151,5 +146,6 @@ module.exports = { flags: ['a', 'b', 'c', 'text', 'name'] }), hoveron: scatterAttrs.hoveron, - hovertemplate: hovertemplateAttrs() + hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs() }; diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index e7be4a0971e..679337b30bd 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -3,7 +3,7 @@ var scatterAttrs = require('../scatter/attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var scatterGlAttrs = require('../scattergl/attributes'); var cartesianIdRegex = require('../../plots/cartesian/constants').idRegex; var templatedArray = require('../../plot_api/plot_template').templatedArray; @@ -131,6 +131,7 @@ module.exports = { }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), diff --git a/src/traces/streamtube/attributes.js b/src/traces/streamtube/attributes.js index c78ceaf6bee..e337728618b 100644 --- a/src/traces/streamtube/attributes.js +++ b/src/traces/streamtube/attributes.js @@ -2,7 +2,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var mesh3dAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -117,10 +117,9 @@ var attrs = { }, hovertemplate: hovertemplateAttrs( { editType: 'calc' }, - { - keys: ['tubex', 'tubey', 'tubez', 'tubeu', 'tubev', 'tubew', 'norm', 'divergence'] - } + { keys: ['tubex', 'tubey', 'tubez', 'tubeu', 'tubev', 'tubew', 'norm', 'divergence'] } ), + hovertemplatefallback: templatefallbackAttrs(), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), diff --git a/src/traces/sunburst/attributes.js b/src/traces/sunburst/attributes.js index 10ccc3b507f..53bcc884c7a 100644 --- a/src/traces/sunburst/attributes.js +++ b/src/traces/sunburst/attributes.js @@ -1,8 +1,7 @@ 'use strict'; var baseAttrs = require('../../plots/attributes'); -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var domainAttrs = require('../../plots/domain').attributes; @@ -143,24 +142,16 @@ module.exports = { }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys.concat(['label', 'value']) - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys.concat(['label', 'value']) }), + texttemplatefallback: templatefallbackAttrs(), hovertext: pieAttrs.hovertext, hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'name', 'current path', 'percent root', 'percent entry', 'percent parent'], dflt: 'label+text+value+name' }), - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), textfont: pieAttrs.textfont, insidetextorientation: pieAttrs.insidetextorientation, diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index 5f8bdbfc084..9293bcec383 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -3,7 +3,7 @@ var Color = require('../../components/color'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; +const { hovertemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -140,6 +140,7 @@ var attrs = (module.exports = overrideAll( description: 'Same as `text`.' }, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), diff --git a/src/traces/treemap/attributes.js b/src/traces/treemap/attributes.js index e3c862bf2fa..9be64ee1620 100644 --- a/src/traces/treemap/attributes.js +++ b/src/traces/treemap/attributes.js @@ -1,7 +1,6 @@ 'use strict'; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var colorScaleAttrs = require('../../components/colorscale/attributes'); var domainAttrs = require('../../plots/domain').attributes; @@ -187,21 +186,13 @@ module.exports = { text: pieAttrs.text, textinfo: sunburstAttrs.textinfo, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys.concat(['label', 'value']) - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys.concat(['label', 'value']) }), + texttemplatefallback: templatefallbackAttrs(), hovertext: pieAttrs.hovertext, hoverinfo: sunburstAttrs.hoverinfo, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), textfont: pieAttrs.textfont, insidetextfont: pieAttrs.insidetextfont, diff --git a/src/traces/violin/attributes.js b/src/traces/violin/attributes.js index 63289c66427..fc1e1f35fb7 100644 --- a/src/traces/violin/attributes.js +++ b/src/traces/violin/attributes.js @@ -153,6 +153,7 @@ module.exports = { text: boxAttrs.text, hovertext: boxAttrs.hovertext, hovertemplate: boxAttrs.hovertemplate, + hovertemplatefallback: boxAttrs.hovertemplatefallback, quartilemethod: boxAttrs.quartilemethod, diff --git a/src/traces/volume/attributes.js b/src/traces/volume/attributes.js index 3913bd7cf2e..46f3959a9eb 100644 --- a/src/traces/volume/attributes.js +++ b/src/traces/volume/attributes.js @@ -49,7 +49,8 @@ var attrs = (module.exports = overrideAll( yhoverformat: isosurfaceAttrs.yhoverformat, zhoverformat: isosurfaceAttrs.zhoverformat, valuehoverformat: isosurfaceAttrs.valuehoverformat, - hovertemplate: isosurfaceAttrs.hovertemplate + hovertemplate: isosurfaceAttrs.hovertemplate, + hovertemplatefallback: isosurfaceAttrs.hovertemplatefallback }, colorScaleAttrs('', { diff --git a/src/traces/waterfall/attributes.js b/src/traces/waterfall/attributes.js index 468eec1543e..e2c3b2508f8 100644 --- a/src/traces/waterfall/attributes.js +++ b/src/traces/waterfall/attributes.js @@ -4,8 +4,7 @@ var barAttrs = require('../bar/attributes'); var lineAttrs = require('../scatter/attributes').line; var baseAttrs = require('../../plots/attributes'); var axisHoverFormat = require('../../plots/cartesian/axis_format_attributes').axisHoverFormat; -var hovertemplateAttrs = require('../../plots/template_attributes').hovertemplateAttrs; -var texttemplateAttrs = require('../../plots/template_attributes').texttemplateAttrs; +const { hovertemplateAttrs, texttemplateAttrs, templatefallbackAttrs } = require('../../plots/template_attributes'); var constants = require('./constants'); var extendFlat = require('../../lib/extend').extendFlat; var Color = require('../../components/color'); @@ -76,12 +75,8 @@ module.exports = { yhoverformat: axisHoverFormat('y'), hovertext: barAttrs.hovertext, - hovertemplate: hovertemplateAttrs( - {}, - { - keys: constants.eventDataKeys - } - ), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), hoverinfo: extendFlat({}, baseAttrs.hoverinfo, { flags: ['name', 'x', 'y', 'text', 'initial', 'delta', 'final'] @@ -100,12 +95,8 @@ module.exports = { ].join(' ') }, // TODO: incorporate `label` and `value` in the eventData - texttemplate: texttemplateAttrs( - { editType: 'plot' }, - { - keys: constants.eventDataKeys.concat(['label']) - } - ), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: constants.eventDataKeys.concat(['label']) }), + texttemplatefallback: templatefallbackAttrs(), text: barAttrs.text, textposition: barAttrs.textposition, insidetextanchor: barAttrs.insidetextanchor, From f623b2273f34d792377707daab291a806958bfd3 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 10:20:43 -0600 Subject: [PATCH 11/18] Update esbuild strip meta plugin to handle more joined arrays --- tasks/compress_attributes.js | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/tasks/compress_attributes.js b/tasks/compress_attributes.js index 29b1ac8fa33..f2efe60a9e9 100644 --- a/tasks/compress_attributes.js +++ b/tasks/compress_attributes.js @@ -1,31 +1,23 @@ const fs = require('fs'); /** - * ESBuild plugin that strips out meta attributes - * of the plotly.js bundles + * esbuild plugin that strips out meta attributes of the plotly.js + * bundles. This helps reduce the file size for the build. */ -var WHITESPACE_BEFORE = '\\s*'; -var OPTIONAL_COMMA = ',?'; +const WHITESPACE_BEFORE = '\\s*'; +const OPTIONAL_COMMA = ',?'; -// one line string with or without trailing comma -function makeStringRegex(attr) { - return makeRegex(WHITESPACE_BEFORE + attr + ": '.*'" + OPTIONAL_COMMA); -} +// Match one line string +const makeStringRegex = (attr) => makeRegex(attr + ": '.*'"); -// joined array of strings with or without trailing comma -function makeJoinedArrayRegex(attr) { - return makeRegex(WHITESPACE_BEFORE + attr + ': \\[[\\s\\S]*?\\]' + '\\.join\\(.*' + OPTIONAL_COMMA); -} +// Match joined array of strings +const makeJoinedArrayRegex = (attr) => makeRegex(attr + ': \\[[\\s\\S]*?\\]' + '\\.join\\([\\s\\S]*?\\)'); -// array with or without trailing comma -function makeArrayRegex(attr) { - return makeRegex(WHITESPACE_BEFORE + attr + ': \\[[\\s\\S]*?\\]' + OPTIONAL_COMMA); -} +// Match array +const makeArrayRegex = (attr) => makeRegex(attr + ': \\[[\\s\\S]*?\\]'); -function makeRegex(regexStr) { - return new RegExp(regexStr, 'g'); -} +const makeRegex = (regexStr) => new RegExp(WHITESPACE_BEFORE + regexStr + OPTIONAL_COMMA, 'g'); const allRegexes = [ makeStringRegex('description'), @@ -39,7 +31,6 @@ const allRegexes = [ const esbuildPluginStripMeta = { name: 'strip-meta-attributes', setup(build) { - const loader = 'js'; build.onLoad({ filter: /\.js$/ }, async (file) => ({ contents: await fs.promises.readFile(file.path, 'utf-8').then((c) => { allRegexes.forEach((r) => { @@ -47,7 +38,7 @@ const esbuildPluginStripMeta = { }); return c; }), - loader + loader: 'js' })); } }; From 074f8a0134a99118f8ed63192fe9909b8f24414b Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 14:36:58 -0600 Subject: [PATCH 12/18] Return array from ternary --- src/components/fx/hover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 45fa6dec959..000bf7c9154 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1249,7 +1249,8 @@ function createHoverText(hoverData, opts) { var mainText = !unifiedhovertitleText ? t0 : Lib.hovertemplateString({ - args: hovermode === 'x unified' ? { xa: item0.xa, x: item0.xVal } : { ya: item0.ya, y: item0.yVal }, + args: + hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }], d3locale: fullLayout._d3locale, fallback: item0.trace.hovertemplatefallback, string: unifiedhovertitleText From 6f3daa804b2858ad8ebe53cf5bda02ac71d2274f Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 14:37:33 -0600 Subject: [PATCH 13/18] Update tests per default fallback value --- test/jasmine/tests/icicle_test.js | 6 +- test/jasmine/tests/lib_test.js | 203 +++++++++++++++++++++------- test/jasmine/tests/sunburst_test.js | 8 +- test/jasmine/tests/treemap_test.js | 2 +- 4 files changed, 165 insertions(+), 54 deletions(-) diff --git a/test/jasmine/tests/icicle_test.js b/test/jasmine/tests/icicle_test.js index ed5ef817fc9..2d3d9c180a1 100644 --- a/test/jasmine/tests/icicle_test.js +++ b/test/jasmine/tests/icicle_test.js @@ -931,7 +931,7 @@ describe('Test icicle texttemplate without `values` should work at root level:', [ '%{percentParent} of %{parent}', [ - '%{percentParent} of %{parent}', + ' of ', '100% of Seth', '33% of Eve', '17% of Eve', @@ -957,7 +957,7 @@ describe('Test icicle texttemplate without `values` should work at root level:', [ 'label: Eve', 'text: fourteen', - 'value: %{value}', // N.B. there is no `values` array + 'value: ', // N.B. there is no `values` array '17% of Eve', '17% of Eve', '17% of Eve', @@ -1085,7 +1085,7 @@ describe('Test icicle texttemplate with *total* `values` should work at root lev [ '%{percentParent} of %{parent}', [ - '%{percentParent} of %{parent}', + ' of ', '22% of Eve', '18% of Eve', '9% of Eve', diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index daea90b327f..ac505363a22 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2522,56 +2522,94 @@ describe('Test lib.js:', function () { }); describe('hovertemplateString', function () { - var locale = false; it('evaluates attributes', function () { - expect(Lib.hovertemplateString('foo %{bar}', {}, locale, { bar: 'baz' })).toEqual('foo baz'); + expect( + Lib.hovertemplateString({ + args: [{ bar: 'baz' }], + fallback: '', + string: 'foo %{bar}' + }) + ).toEqual('foo baz'); }); it('evaluates attributes with a dot in their name', function () { expect( - Lib.hovertemplateString('%{marker.size}', {}, locale, { 'marker.size': 12 }, { marker: { size: 14 } }) + Lib.hovertemplateString({ + args: [{ 'marker.size': 12 }, { marker: { size: 14 } }], + fallback: '', + string: '%{marker.size}' + }) ).toEqual('12'); }); it('evaluates nested properties', function () { - expect(Lib.hovertemplateString('foo %{bar.baz}', {}, locale, { bar: { baz: 'asdf' } })).toEqual('foo asdf'); + expect( + Lib.hovertemplateString({ + args: [{ bar: { baz: 'asdf' } }], + fallback: '', + string: 'foo %{bar.baz}' + }) + ).toEqual('foo asdf'); }); it('evaluates array nested properties', function () { - expect(Lib.hovertemplateString('foo %{bar[0].baz}', {}, locale, { bar: [{ baz: 'asdf' }] })).toEqual( - 'foo asdf' - ); + expect( + Lib.hovertemplateString({ + args: [{ bar: [{ baz: 'asdf' }] }], + fallback: '', + string: 'foo %{bar[0].baz}' + }) + ).toEqual('foo asdf'); }); it('should work with the number *0*', function () { - expect(Lib.hovertemplateString('%{group}', {}, locale, { group: 0 })).toEqual('0'); + expect( + Lib.hovertemplateString({ + args: [{ group: 0 }], + fallback: '', + string: '%{group}' + }) + ).toEqual('0'); }); it('should work with the number *0* (nested case)', function () { - expect(Lib.hovertemplateString('%{x.y}', {}, locale, { x: { y: 0 } })).toEqual('0'); + expect( + Lib.hovertemplateString({ + args: [{ x: { y: 0 } }], + fallback: '', + string: '%{x.y}' + }) + ).toEqual('0'); }); it('preserves null and NaN', function () { expect( - Lib.hovertemplateString('%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', {}, locale, { - a: null, - b: NaN, - c: { d: null, e: NaN }, - f: [null, NaN] + Lib.hovertemplateString({ + args: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + fallback: '', + string: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' }) ).toEqual('null NaN null NaN null NaN'); }); it('subtitutes multiple matches', function () { expect( - Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, { group: 'asdf', trace: 'jkl;' }) + Lib.hovertemplateString({ + args: [{ group: 'asdf', trace: 'jkl;' }], + fallback: '', + string: 'foo %{group} %{trace}' + }) ).toEqual('foo asdf jkl;'); }); - it('replaces missing matches with template string', function () { - expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, { group: 1 })).toEqual( - 'foo 1 %{trace}' - ); + it('replaces missing matches with fallback value', function () { + expect( + Lib.hovertemplateString({ + args: [{ group: 1 }], + fallback: '', + string: 'foo %{group} %{trace}' + }) + ).toEqual('foo 1 '); }); it('uses the value from the first object with the specified key', function () { @@ -2579,77 +2617,150 @@ describe('Test lib.js:', function () { var obj2 = { a: 'second', foo: { bar: 'bar' } }; // Simple key - expect(Lib.hovertemplateString('foo %{a}', {}, locale, obj1, obj2)).toEqual('foo first'); - expect(Lib.hovertemplateString('foo %{a}', {}, locale, obj2, obj1)).toEqual('foo second'); + expect( + Lib.hovertemplateString({ + args: [obj1, obj2], + fallback: '', + string: 'foo %{a}' + }) + ).toEqual('foo first'); + expect( + Lib.hovertemplateString({ + args: [obj2, obj1], + fallback: '', + string: 'foo %{a}' + }) + ).toEqual('foo second'); // Nested Keys - expect(Lib.hovertemplateString('foo %{foo.bar}', {}, locale, obj1, obj2)).toEqual('foo bar'); + expect( + Lib.hovertemplateString({ + args: [obj1, obj2], + fallback: '', + string: 'foo %{foo.bar}' + }) + ).toEqual('foo bar'); // Nested keys with 0 - expect(Lib.hovertemplateString('y: %{y}', {}, locale, { y: 0 }, { y: 1 })).toEqual('y: 0'); + expect( + Lib.hovertemplateString({ + args: [{ y: 0 }, { y: 1 }], + fallback: '', + string: 'y: %{y}' + }) + ).toEqual('y: 0'); }); it('formats numbers using d3-format mini-language when `:`', function () { - expect(Lib.hovertemplateString('a: %{a:.0%}', {}, locale, { a: 0.123 })).toEqual('a: 12%'); - expect(Lib.hovertemplateString('a: %{a:0.2%}', {}, locale, { a: 0.123 })).toEqual('a: 12.30%'); - expect(Lib.hovertemplateString('b: %{b:2.2f}', {}, locale, { b: 43 })).toEqual('b: 43.00'); + expect( + Lib.hovertemplateString({ + args: [{ a: 0.123 }], + fallback: '', + string: 'a: %{a:.0%}' + }) + ).toEqual('a: 12%'); + expect( + Lib.hovertemplateString({ + args: [{ a: 0.123 }], + fallback: '', + string: 'a: %{a:0.2%}' + }) + ).toEqual('a: 12.30%'); + expect( + Lib.hovertemplateString({ + args: [{ b: 43 }], + fallback: '', + string: 'b: %{b:2.2f}' + }) + ).toEqual('b: 43.00'); }); it('formats date using d3-time-format mini-language `|`', function () { - expect(Lib.hovertemplateString('a: %{a|%A}', {}, locale, { a: '2019-05-22' })).toEqual('a: Wednesday'); - expect(Lib.hovertemplateString('%{x|%b %-d, %Y}', {}, locale, { x: '2019-01-01' })).toEqual('Jan 1, 2019'); + expect( + Lib.hovertemplateString({ + args: [{ a: '2019-05-22' }], + fallback: '', + string: 'a: %{a|%A}' + }) + ).toEqual('a: Wednesday'); + expect( + Lib.hovertemplateString({ + args: [{ x: '2019-01-01' }], + fallback: '', + string: '%{x|%b %-d, %Y}' + }) + ).toEqual('Jan 1, 2019'); }); it('looks for default label if no format is provided', function () { - expect(Lib.hovertemplateString('y: %{y}', { yLabel: '0.1' }, locale, { y: 0.123 })).toEqual('y: 0.1'); + expect( + Lib.hovertemplateString({ + args: [{ y: 0.123 }], + fallback: '', + labels: { yLabel: '0.1' }, + string: 'y: %{y}' + }) + ).toEqual('y: 0.1'); }); it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); - Lib.hovertemplateString('%{idontexist}', {}); + Lib.hovertemplateString({ + fallback: '', + string: '%{idontexist}' + }); expect(Lib.warn.calls.count()).toBe(1); for (var i = 0; i < 15; i++) { - Lib.hovertemplateString('%{idontexist}', {}); + Lib.hovertemplateString({ + fallback: '', + string: '%{idontexist}' + }); } expect(Lib.warn.calls.count()).toBe(10); }); - - it('does not error out when arguments are undefined', function () { - expect(function () { - Lib.hovertemplateString('y: %{y}', undefined, locale, undefined); - }).not.toThrow(); - }); }); describe('texttemplateString', function () { var locale = false; it('evaluates attributes', function () { - expect(Lib.texttemplateString('foo %{bar}', {}, locale, { bar: 'baz' })).toEqual('foo baz'); + expect( + Lib.texttemplateString({ + args: [{ bar: 'baz' }], + fallback: '', + string: 'foo %{bar}' + }) + ).toEqual('foo baz'); }); it('looks for default label if no format is provided', function () { - expect(Lib.texttemplateString('y: %{y}', { yLabel: '0.1' }, locale, { y: 0.123 })).toEqual('y: 0.1'); + expect( + Lib.texttemplateString({ + args: [{ y: 0.123 }], + fallback: '', + labels: { yLabel: '0.1' }, + string: 'y: %{y}' + }) + ).toEqual('y: 0.1'); }); it('preserves null and NaN', function () { expect( - Lib.texttemplateString('%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}', {}, locale, { - a: null, - b: NaN, - c: { d: null, e: NaN }, - f: [null, NaN] + Lib.texttemplateString({ + args: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + fallback: '', + string: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' }) ).toEqual('null NaN null NaN null NaN'); }); it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); - Lib.texttemplateString('%{idontexist}', {}); + Lib.texttemplateString({ fallback: '', string: '%{idontexist}' }); expect(Lib.warn.calls.count()).toBe(1); for (var i = 0; i < 15; i++) { - Lib.texttemplateString('%{idontexist}', {}); + Lib.texttemplateString({ fallback: '', string: '%{idontexist}' }); } expect(Lib.warn.calls.count()).toBe(11); }); diff --git a/test/jasmine/tests/sunburst_test.js b/test/jasmine/tests/sunburst_test.js index a87ef2288d6..d082bfe9ebf 100644 --- a/test/jasmine/tests/sunburst_test.js +++ b/test/jasmine/tests/sunburst_test.js @@ -2293,7 +2293,7 @@ describe('Test sunburst texttemplate without `values` should work at root level: [ '%{percentParent} of %{parent}', [ - '%{percentParent} of %{parent}', + ' of ', '100% of Seth', '33% of Eve', '17% of Eve', @@ -2319,7 +2319,7 @@ describe('Test sunburst texttemplate without `values` should work at root level: [ 'label: Eve', 'text: fourteen', - 'value: %{value}', // N.B. there is no `values` array + 'value: ', // N.B. there is no `values` array '17% of Eve', '17% of Eve', '17% of Eve', @@ -2447,7 +2447,7 @@ describe('Test sunburst texttemplate with *total* `values` should work at root l [ '%{percentParent} of %{parent}', [ - '%{percentParent} of %{parent}', + ' of ', '22% of Eve', '18% of Eve', '9% of Eve', @@ -2601,7 +2601,7 @@ describe('Test sunburst texttemplate with *remainder* `values` should work at ro [ '%{percentParent} of %{parent}', [ - '%{percentParent} of %{parent}', + ' of ', '20% of Eve', '12% of Eve', '6% of Eve', diff --git a/test/jasmine/tests/treemap_test.js b/test/jasmine/tests/treemap_test.js index 953a7d60ebb..91f3d4104b7 100644 --- a/test/jasmine/tests/treemap_test.js +++ b/test/jasmine/tests/treemap_test.js @@ -1937,7 +1937,7 @@ describe('Test treemap texttemplate without `values` should work:', function () 'Eve', 'label: Cain, text: fourteen', 'Seth', - 'value: %{value}', // N.B. there is no `values` array + 'value: ', // N.B. there is no `values` array '17% of Eve', '17% of Eve', 'Awan', From 6719bd3c93bcc888c9b1bd4281f7f268d63d50e3 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 15:07:21 -0600 Subject: [PATCH 14/18] Update schema --- test/plot-schema.json | 484 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 452 insertions(+), 32 deletions(-) diff --git a/test/plot-schema.json b/test/plot-schema.json index dbc320a4631..6ed54c19d3e 100644 --- a/test/plot-schema.json +++ b/test/plot-schema.json @@ -4815,6 +4815,12 @@ "editType": "none", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "xanchor": { "description": "Sets the label's horizontal position anchor This anchor binds the specified `textposition` to the *left*, *center* or *right* of the label text. For example, if `textposition` is set to *top right* and `xanchor` to *right* then the right-most portion of the label text lines up with the right-most edge of the new shape.", "dflt": "auto", @@ -9605,6 +9611,12 @@ "editType": "arraydraw", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "xanchor": { "description": "Sets the label's horizontal position anchor This anchor binds the specified `textposition` to the *left*, *center* or *right* of the label text. For example, if `textposition` is set to *top right* and `xanchor` to *right* then the right-most portion of the label text lines up with the right-most edge of the shape.", "dflt": "auto", @@ -16808,6 +16820,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -18448,11 +18466,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `value` and `label`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `value` and `label`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -19013,6 +19037,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -20595,7 +20625,7 @@ } }, "hoveron": { - "description": "Do the hover effects highlight individual boxes or sample points or both?", + "description": "Do the hover effects highlight individual boxes or sample points or both?", "dflt": "boxes+points", "editType": "style", "flags": [ @@ -20611,6 +20641,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -21412,7 +21448,7 @@ "valType": "string" }, "notchwidth": { - "description": "Sets the width of the notches relative to the box' width. For example, with 0, the notches are as wide as the box(es).", + "description": "Sets the width of the notches relative to the box width. For example, with 0, the notches are as wide as the box(es).", "dflt": 0.25, "editType": "calc", "max": 0.5, @@ -21639,7 +21675,7 @@ ] }, "whiskerwidth": { - "description": "Sets the width of the whiskers relative to the box' width. For example, with 1, the whiskers are as wide as the box(es).", + "description": "Sets the width of the whiskers relative to the box width. For example, with 1, the whiskers are as wide as the box(es).", "dflt": 0.5, "editType": "calc", "max": 1, @@ -22434,7 +22470,7 @@ ] }, "whiskerwidth": { - "description": "Sets the width of the whiskers relative to the box' width. For example, with 1, the whiskers are as wide as the box(es).", + "description": "Sets the width of the whiskers relative to the box width. For example, with 1, the whiskers are as wide as the box(es).", "dflt": 0, "editType": "calc", "max": 1, @@ -25186,6 +25222,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -26473,6 +26515,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -27756,6 +27804,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -29072,6 +29126,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -30645,6 +30705,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -31000,11 +31066,17 @@ "valType": "string" }, "texttemplate": { - "description": "For this trace it only has an effect if `coloring` is set to *heatmap*. Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", + "description": "For this trace it only has an effect if `coloring` is set to *heatmap*. Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "transpose": { "description": "Transposes the z data.", "dflt": false, @@ -33468,6 +33540,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -34689,6 +34767,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -35360,6 +35444,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -36872,11 +36962,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `percentInitial`, `percentPrevious`, `percentTotal`, `label` and `value`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `percentInitial`, `percentPrevious`, `percentTotal`, `label` and `value`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -37394,6 +37490,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -38090,11 +38192,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label`, `color`, `value`, `text` and `percent`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label`, `color`, `value`, `text` and `percent`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -39237,6 +39345,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -39548,11 +39662,17 @@ "valType": "string" }, "texttemplate": { - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "transpose": { "description": "Transposes the z data.", "dflt": false, @@ -40398,6 +40518,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -41870,11 +41996,17 @@ "valType": "string" }, "texttemplate": { - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label` and `value`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label` and `value`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "type": "histogram", "uid": { "description": "Assign an id to this trace, Use this to provide object constancy between traces during animations and transitions.", @@ -43045,6 +43177,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -43364,11 +43502,17 @@ } }, "texttemplate": { - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variable `z`", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variable `z`", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "type": "histogram2d", "uid": { "description": "Assign an id to this trace, Use this to provide object constancy between traces during animations and transitions.", @@ -44741,6 +44885,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -45105,11 +45255,17 @@ } }, "texttemplate": { - "description": "For this trace it only has an effect if `coloring` is set to *heatmap*. Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", + "description": "For this trace it only has an effect if `coloring` is set to *heatmap*. Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `x`, `y`, `z` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "type": "histogram2dcontour", "uid": { "description": "Assign an id to this trace, Use this to provide object constancy between traces during animations and transitions.", @@ -45673,6 +45829,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -47397,11 +47559,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -47765,6 +47933,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -50297,6 +50471,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -51844,6 +52024,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -53288,11 +53474,17 @@ ] }, "hovertemplate": { - "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. This value here applies when hovering over dimensions. Note that *categorycount*, *colorcount* and *bandcolorcount* are only available when `hoveron` contains the *color* flag. Finally, the template string has access to variables `count`, `probability`, `category`, `categorycount`, `colorcount` and `bandcolorcount`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `count`, `probability`, `category`, `categorycount`, `colorcount` and `bandcolorcount`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", "dflt": "", "editType": "plot", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "labelfont": { "color": { "editType": "calc", @@ -54162,11 +54354,17 @@ }, "editType": "calc", "hovertemplate": { - "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. This value here applies when hovering over lines.Finally, the template string has access to variables `count` and `probability`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `count` and `probability`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", "dflt": "", "editType": "plot", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "reversescale": { "description": "Reverses the color mapping if true. Has an effect only if in `line.color` is set to a numerical array. If true, `line.cmin` will correspond to the last color in the array and `line.cmax` will correspond to the first color.", "dflt": false, @@ -56139,6 +56337,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -57018,11 +57222,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label`, `color`, `value`, `percent` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `label`, `color`, `value`, `percent` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -58010,7 +58220,13 @@ }, "hovertemplate": { "arrayOk": true, - "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Variables `source` and `target` are node objects.Finally, the template string has access to variables `value` and `label`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `value` and `label`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -58381,7 +58597,13 @@ }, "hovertemplate": { "arrayOk": true, - "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Variables `sourceLinks` and `targetLinks` are arrays of link objects.Finally, the template string has access to variables `value` and `label`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "description": "Template string used for rendering the information that appear on hover box. Note that this will override `hoverinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\" as well as %{xother}, {%_xother}, {%_xother_}, {%xother_}. When showing info for several points, *xother* will be added to those with different x positions from the first point. An underscore before or after *(x|y)other* will add a space on that side, only when this field is shown. Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data. Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `value` and `label`. Anything contained in tag `` is displayed in the secondary box, for example `%{fullData.name}`. To hide the secondary box completely, use an empty tag ``.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -59276,6 +59498,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -61221,11 +61449,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", "dflt": "", "editType": "calc", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -62011,6 +62245,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -64001,7 +64241,13 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -64470,6 +64716,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -66364,11 +66616,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `a`, `b` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `a`, `b` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -66751,6 +67009,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -68650,7 +68914,13 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon`, `location` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon`, `location` and `text`.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -69199,6 +69469,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -70955,11 +71231,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. ", "dflt": "", "editType": "calc", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -71528,6 +71810,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -72657,7 +72945,13 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon` and `text`.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -73071,6 +73365,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -74200,7 +74500,13 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `lat`, `lon` and `text`.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", "dflt": "", "editType": "calc", "valType": "string" @@ -74568,6 +74874,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -76483,11 +76795,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `r`, `theta` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `r`, `theta` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -76875,6 +77193,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -78640,11 +78964,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `r`, `theta` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `r`, `theta` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -79034,6 +79364,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -80953,11 +81289,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `real`, `imag` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `real`, `imag` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -81349,6 +81691,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -83256,11 +83604,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `a`, `b`, `c` and `text`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `a`, `b`, `c` and `text`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -83656,6 +84010,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -86258,6 +86618,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -87075,6 +87441,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -88618,11 +88990,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -89880,6 +90258,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -91723,6 +92107,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -93481,11 +93871,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `currentPath`, `root`, `entry`, `percentRoot`, `percentEntry`, `percentParent`, `label` and `value`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", @@ -93904,6 +94300,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -96010,6 +96412,12 @@ "editType": "calc", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "calc", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -96967,6 +97375,12 @@ "editType": "none", "valType": "string" }, + "hovertemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "hovertemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `hovertemplate`.", "editType": "none", @@ -97735,11 +98149,17 @@ }, "texttemplate": { "arrayOk": true, - "description": "Template string used for rendering the information text that appear on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `initial`, `delta`, `final` and `label`.", + "description": "Template string used for rendering the information text that appears on points. Note that this will override `textinfo`. Variables are inserted using %{variable}, for example \"y: %{y}\". Numbers are formatted using d3-format's syntax %{variable:d3-format}, for example \"Price: %{y:$.2f}\". https://github.com/d3/d3-format/tree/v1.4.5#d3-format for details on the formatting syntax. Dates are formatted using d3-time-format's syntax %{variable|d3-time-format}, for example \"Day: %{2019-01-01|%A}\". https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format for details on the date formatting syntax. Every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available. Finally, the template string has access to variables `initial`, `delta`, `final` and `label`.", "dflt": "", "editType": "plot", "valType": "string" }, + "texttemplatefallback": { + "description": "Fallback value that's displayed when a variable referenced in a template can't be found.", + "dflt": "", + "editType": "none", + "valType": "string" + }, "texttemplatesrc": { "description": "Sets the source reference on Chart Studio Cloud for `texttemplate`.", "editType": "none", From 84fc044e63306f732240a8d9107b719ac90b297e Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 15:38:57 -0600 Subject: [PATCH 15/18] Update test baselines --- .../icicle_with-without_values_template.png | Bin 168006 -> 158501 bytes .../baselines/text_on_shapes_texttemplate.png | Bin 220140 -> 218941 bytes .../treemap_with-without_values_template.png | Bin 212254 -> 195386 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/icicle_with-without_values_template.png b/test/image/baselines/icicle_with-without_values_template.png index 32c22da12668fd0f9d461fdabaff8d9b8f55f6bf..ef1efaa30d1653faca30114d9d3a86f0f0c42bf9 100644 GIT binary patch literal 158501 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 CLW&yPOx?d5e&I43!-sOfdg?~e0Q2F~4g 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 z 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 literal 168006 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 diff --git a/test/image/baselines/text_on_shapes_texttemplate.png b/test/image/baselines/text_on_shapes_texttemplate.png index c0b1b2e989fa280734562d7d4c02a75ef570835b..73e262500bb1780219e956b2581c96e7052b8529 100644 GIT binary patch delta 122301 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 delta 123509 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 z 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

NXN&D&bz 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 F{{W^6PGkT8 delta 192513 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 FKLGR|LM8wJ From e719f7484a360f10e19994280a44dfccb5d5472c Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Wed, 8 Oct 2025 17:48:16 -0600 Subject: [PATCH 16/18] Rename object keys --- src/components/drawing/index.js | 6 +- src/components/fx/hover.js | 12 +-- src/components/shapes/display_labels.js | 7 +- src/lib/index.js | 22 ++--- src/traces/bar/plot.js | 6 +- src/traces/heatmap/plot.js | 6 +- src/traces/pie/plot.js | 6 +- src/traces/scatter3d/convert.js | 6 +- src/traces/scattergl/convert.js | 6 +- src/traces/scattermap/convert.js | 6 +- src/traces/scattermapbox/convert.js | 6 +- src/traces/sunburst/plot.js | 6 +- test/jasmine/tests/lib_test.js | 103 +++++++++++------------- 13 files changed, 95 insertions(+), 103 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 18539c34065..38e8686d102 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -1300,11 +1300,11 @@ drawing.textPointStyle = function (s, trace, gd) { var pointValues = {}; appendArrayPointValue(pointValues, trace, d.i); text = Lib.texttemplateString({ - args: [pointValues, d, trace._meta], - d3locale: fullLayout._d3locale, + data: [pointValues, d, trace._meta], fallback: trace.texttemplatefallback, labels, - string: text + locale: fullLayout._d3locale, + template: text }); } diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 000bf7c9154..1b4a2b46d27 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -1249,11 +1249,11 @@ function createHoverText(hoverData, opts) { var mainText = !unifiedhovertitleText ? t0 : Lib.hovertemplateString({ - args: + data: hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }], - d3locale: fullLayout._d3locale, fallback: item0.trace.hovertemplatefallback, - string: unifiedhovertitleText + locale: fullLayout._d3locale, + template: unifiedhovertitleText }); var mockLayoutIn = { @@ -1673,11 +1673,11 @@ function getHoverLabelText(d, showCommonLabel, hovermode, fullLayout, t0, g) { } text = Lib.hovertemplateString({ - args: [d.eventData[0] || {}, d.trace._meta], - d3locale: fullLayout._d3locale, + data: [d.eventData[0] || {}, d.trace._meta], fallback: d.trace.hovertemplatefallback, labels, - string: hovertemplate + locale: fullLayout._d3locale, + template: hovertemplate }); text = text.replace(EXTRA_STRING_REGEX, (_, extra) => { diff --git a/src/components/shapes/display_labels.js b/src/components/shapes/display_labels.js index 2906a68e2cd..4c3cb3b49af 100644 --- a/src/components/shapes/display_labels.js +++ b/src/components/shapes/display_labels.js @@ -33,11 +33,10 @@ module.exports = function drawLabel(gd, index, options, shapeGroup) { } } text = Lib.texttemplateStringForShapes({ - args: [templateValues], - d3locale: gd._fullLayout._d3locale, + data: [templateValues], fallback: options.label.texttemplatefallback, - labels: {}, - string: options.label.texttemplate + locale: gd._fullLayout._d3locale, + template: options.label.texttemplate }); } else { text = options.label.text; diff --git a/src/lib/index.js b/src/lib/index.js index 6c5f032417d..218e0620f73 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1112,22 +1112,22 @@ var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; * or fallback to associated labels. * * Examples: - * Lib.hovertemplateString({ string 'name: %{trace}', labels: {trace: 'asdf'} }) --> 'name: asdf' - * Lib.hovertemplateString({ string: 'name: %{trace[0].name}', labels: { trace: [{ name: 'asdf' }] } }) --> 'name: asdf' - * Lib.hovertemplateString({ string: 'price: %{y:$.2f}', labels: { y: 1 } }) --> 'price: $1.00' + * Lib.templateFormatString({ template 'name: %{trace}', labels: {trace: 'asdf'} }) --> 'name: asdf' + * Lib.templateFormatString({ template: 'name: %{trace[0].name}', labels: { trace: [{ name: 'asdf' }] } }) --> 'name: asdf' + * Lib.templateFormatString({ template: 'price: %{y:$.2f}', labels: { y: 1 } }) --> 'price: $1.00' * * @param {object} options - Configuration object - * @param {array} options.args - Data objects containing substitution values - * @param {object} options.d3locale - D3 locale for formatting + * @param {array} options.data - Data objects containing substitution values * @param {string} options.fallback - Fallback value when substitution fails * @param {object} options.labels - Data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'} + * @param {object} options.locale - D3 locale for formatting * @param {object} options.opts - Additional options - * @param {string} options.string - Input string containing %{...:...} template strings + * @param {string} options.template - Input string containing %{...:...} template strings * * @return {string} templated string */ -function templateFormatString({ args = [], d3locale, fallback, labels = {}, opts, string }) { - return string.replace(lib.TEMPLATE_STRING_REGEX, (_, rawKey, format) => { +function templateFormatString({ data = [], locale, fallback, labels = {}, opts, template }) { + return template.replace(lib.TEMPLATE_STRING_REGEX, (_, rawKey, format) => { const isOther = ['xother', 'yother'].includes(rawKey); const isSpaceOther = ['_xother', '_yother'].includes(rawKey); const isSpaceOtherSpace = ['_xother_', '_yother_'].includes(rawKey); @@ -1154,7 +1154,7 @@ function templateFormatString({ args = [], d3locale, fallback, labels = {}, opts if (labels[key] === undefined) return ''; value = labels[key]; } else { - for (const obj of args) { + for (const obj of data) { if (!obj) continue; if (obj.hasOwnProperty(key)) { value = obj[key]; @@ -1184,7 +1184,7 @@ function templateFormatString({ args = [], d3locale, fallback, labels = {}, opts if (format) { var fmt; if (format[0] === ':') { - fmt = d3locale ? d3locale.numberFormat : lib.numberFormat; + fmt = locale ? locale.numberFormat : lib.numberFormat; if (value !== '') { // e.g. skip missing data on heatmap value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value); @@ -1192,7 +1192,7 @@ function templateFormatString({ args = [], d3locale, fallback, labels = {}, opts } if (format[0] === '|') { - fmt = d3locale ? d3locale.timeFormat : utcFormat; + fmt = locale ? locale.timeFormat : utcFormat; var ms = lib.dateTime2ms(value); value = lib.formatDate(ms, format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''), false, fmt); } diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 78b48f14b7b..37653f9c3a8 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -1074,11 +1074,11 @@ function calcTexttemplate(fullLayout, cd, index, xa, ya) { var customdata = Lib.castOption(trace, cdi.i, 'customdata'); if (customdata) obj.customdata = customdata; return Lib.texttemplateString({ - args: [pt, obj, trace._meta], - d3locale: fullLayout._d3locale, + data: [pt, obj, trace._meta], fallback: trace.texttemplatefallback, labels: obj, - string: texttemplate + locale: fullLayout._d3locale, + template: texttemplate }); } diff --git a/src/traces/heatmap/plot.js b/src/traces/heatmap/plot.js index 8639b575e26..1d812bbd2df 100644 --- a/src/traces/heatmap/plot.js +++ b/src/traces/heatmap/plot.js @@ -451,11 +451,11 @@ module.exports = function (gd, plotinfo, cdheatmaps, heatmapLayer) { obj.text = theText; var _t = Lib.texttemplateString({ - args: [obj, trace._meta], - d3locale: gd._fullLayout._d3locale, + data: [obj, trace._meta], fallback: trace.texttemplatefallback, labels: obj, - string: texttemplate + locale: gd._fullLayout._d3locale, + template: texttemplate }); if (!_t) continue; diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 30fb980286c..d31ad0a7c32 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -1272,11 +1272,11 @@ function formatSliceLabel(gd, pt, cd0) { var ptTx = helpers.getFirstFilled(trace.text, pt.pts); if (isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; pt.text = Lib.texttemplateString({ - args: [obj, trace._meta], - d3locale: gd._fullLayout._d3locale, + data: [obj, trace._meta], fallback: trace.texttemplatefallback, labels: obj, - string: txt + locale: gd._fullLayout._d3locale, + template: txt }); } } diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 7f1e325d2fb..fafa4898216 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -258,11 +258,11 @@ function convertPlotlyOptions(scene, data) { var pointValues = {}; appendArrayPointValue(pointValues, data, i); text[i] = Lib.texttemplateString({ - args: [pointValues, d, data._meta], - d3locale, + data: [pointValues, d, data._meta], fallback: data.texttemplatefallback, labels, - string: txt(i) + locale: d3locale, + template: txt(i) }); } } diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 643105a853d..22e3f48db31 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -143,11 +143,11 @@ function convertTextStyle(gd, trace) { appendArrayPointValue(pointValues, trace, i); optsOut.text.push( Lib.texttemplateString({ - args: [pointValues, d, trace._meta], - d3locale, + data: [pointValues, d, trace._meta], fallback: trace.texttemplatefallback, labels, - string: txt(i) + locale: d3locale, + template: txt(i) }) ); } diff --git a/src/traces/scattermap/convert.js b/src/traces/scattermap/convert.js index c66d807b696..c2bfce0c56d 100644 --- a/src/traces/scattermap/convert.js +++ b/src/traces/scattermap/convert.js @@ -301,11 +301,11 @@ function makeSymbolGeoJSON(calcTrace, gd) { var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); text = Lib.texttemplateString({ - args: [pointValues, calcPt, trace._meta], - d3locale: fullLayout._d3locale, + data: [pointValues, calcPt, trace._meta], fallback: trace.texttemplatefallback, labels, - string: tt + locale: fullLayout._d3locale, + template: tt }); } else { text = fillText(i); diff --git a/src/traces/scattermapbox/convert.js b/src/traces/scattermapbox/convert.js index 370d5fbad50..380324bbefb 100644 --- a/src/traces/scattermapbox/convert.js +++ b/src/traces/scattermapbox/convert.js @@ -301,11 +301,11 @@ function makeSymbolGeoJSON(calcTrace, gd) { var pointValues = {}; appendArrayPointValue(pointValues, trace, calcPt.i); text = Lib.texttemplateString({ - args: [pointValues, calcPt, trace._meta], - d3locale: fullLayout._d3locale, + data: [pointValues, calcPt, trace._meta], fallback: trace.texttemplatefallback, labels, - string: tt + locale: fullLayout._d3locale, + template: tt }); } else { text = fillText(i); diff --git a/src/traces/sunburst/plot.js b/src/traces/sunburst/plot.js index 412f05adc79..3708e2b8136 100644 --- a/src/traces/sunburst/plot.js +++ b/src/traces/sunburst/plot.js @@ -633,11 +633,11 @@ exports.formatSliceLabel = function (pt, entry, trace, cd, fullLayout) { if (Lib.isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; obj.customdata = Lib.castOption(trace, cdi.i, 'customdata'); return Lib.texttemplateString({ - args: [obj, trace._meta], - d3locale: fullLayout._d3locale, + data: [obj, trace._meta], fallback: trace.texttemplatefallback, labels: obj, - string: txt + locale: fullLayout._d3locale, + template: txt }); }; diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index ac505363a22..25f2806938b 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2525,9 +2525,9 @@ describe('Test lib.js:', function () { it('evaluates attributes', function () { expect( Lib.hovertemplateString({ - args: [{ bar: 'baz' }], + data: [{ bar: 'baz' }], fallback: '', - string: 'foo %{bar}' + template: 'foo %{bar}' }) ).toEqual('foo baz'); }); @@ -2535,9 +2535,9 @@ describe('Test lib.js:', function () { it('evaluates attributes with a dot in their name', function () { expect( Lib.hovertemplateString({ - args: [{ 'marker.size': 12 }, { marker: { size: 14 } }], + data: [{ 'marker.size': 12 }, { marker: { size: 14 } }], fallback: '', - string: '%{marker.size}' + template: '%{marker.size}' }) ).toEqual('12'); }); @@ -2545,9 +2545,9 @@ describe('Test lib.js:', function () { it('evaluates nested properties', function () { expect( Lib.hovertemplateString({ - args: [{ bar: { baz: 'asdf' } }], + data: [{ bar: { baz: 'asdf' } }], fallback: '', - string: 'foo %{bar.baz}' + template: 'foo %{bar.baz}' }) ).toEqual('foo asdf'); }); @@ -2555,9 +2555,9 @@ describe('Test lib.js:', function () { it('evaluates array nested properties', function () { expect( Lib.hovertemplateString({ - args: [{ bar: [{ baz: 'asdf' }] }], + data: [{ bar: [{ baz: 'asdf' }] }], fallback: '', - string: 'foo %{bar[0].baz}' + template: 'foo %{bar[0].baz}' }) ).toEqual('foo asdf'); }); @@ -2565,9 +2565,9 @@ describe('Test lib.js:', function () { it('should work with the number *0*', function () { expect( Lib.hovertemplateString({ - args: [{ group: 0 }], + data: [{ group: 0 }], fallback: '', - string: '%{group}' + template: '%{group}' }) ).toEqual('0'); }); @@ -2575,9 +2575,9 @@ describe('Test lib.js:', function () { it('should work with the number *0* (nested case)', function () { expect( Lib.hovertemplateString({ - args: [{ x: { y: 0 } }], + data: [{ x: { y: 0 } }], fallback: '', - string: '%{x.y}' + template: '%{x.y}' }) ).toEqual('0'); }); @@ -2585,9 +2585,9 @@ describe('Test lib.js:', function () { it('preserves null and NaN', function () { expect( Lib.hovertemplateString({ - args: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + data: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], fallback: '', - string: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' + template: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' }) ).toEqual('null NaN null NaN null NaN'); }); @@ -2595,9 +2595,9 @@ describe('Test lib.js:', function () { it('subtitutes multiple matches', function () { expect( Lib.hovertemplateString({ - args: [{ group: 'asdf', trace: 'jkl;' }], + data: [{ group: 'asdf', trace: 'jkl;' }], fallback: '', - string: 'foo %{group} %{trace}' + template: 'foo %{group} %{trace}' }) ).toEqual('foo asdf jkl;'); }); @@ -2605,9 +2605,9 @@ describe('Test lib.js:', function () { it('replaces missing matches with fallback value', function () { expect( Lib.hovertemplateString({ - args: [{ group: 1 }], + data: [{ group: 1 }], fallback: '', - string: 'foo %{group} %{trace}' + template: 'foo %{group} %{trace}' }) ).toEqual('foo 1 '); }); @@ -2619,34 +2619,34 @@ describe('Test lib.js:', function () { // Simple key expect( Lib.hovertemplateString({ - args: [obj1, obj2], + data: [obj1, obj2], fallback: '', - string: 'foo %{a}' + template: 'foo %{a}' }) ).toEqual('foo first'); expect( Lib.hovertemplateString({ - args: [obj2, obj1], + data: [obj2, obj1], fallback: '', - string: 'foo %{a}' + template: 'foo %{a}' }) ).toEqual('foo second'); // Nested Keys expect( Lib.hovertemplateString({ - args: [obj1, obj2], + data: [obj1, obj2], fallback: '', - string: 'foo %{foo.bar}' + template: 'foo %{foo.bar}' }) ).toEqual('foo bar'); // Nested keys with 0 expect( Lib.hovertemplateString({ - args: [{ y: 0 }, { y: 1 }], + data: [{ y: 0 }, { y: 1 }], fallback: '', - string: 'y: %{y}' + template: 'y: %{y}' }) ).toEqual('y: 0'); }); @@ -2654,23 +2654,23 @@ describe('Test lib.js:', function () { it('formats numbers using d3-format mini-language when `:`', function () { expect( Lib.hovertemplateString({ - args: [{ a: 0.123 }], + data: [{ a: 0.123 }], fallback: '', - string: 'a: %{a:.0%}' + template: 'a: %{a:.0%}' }) ).toEqual('a: 12%'); expect( Lib.hovertemplateString({ - args: [{ a: 0.123 }], + data: [{ a: 0.123 }], fallback: '', - string: 'a: %{a:0.2%}' + template: 'a: %{a:0.2%}' }) ).toEqual('a: 12.30%'); expect( Lib.hovertemplateString({ - args: [{ b: 43 }], + data: [{ b: 43 }], fallback: '', - string: 'b: %{b:2.2f}' + template: 'b: %{b:2.2f}' }) ).toEqual('b: 43.00'); }); @@ -2678,16 +2678,16 @@ describe('Test lib.js:', function () { it('formats date using d3-time-format mini-language `|`', function () { expect( Lib.hovertemplateString({ - args: [{ a: '2019-05-22' }], + data: [{ a: '2019-05-22' }], fallback: '', - string: 'a: %{a|%A}' + template: 'a: %{a|%A}' }) ).toEqual('a: Wednesday'); expect( Lib.hovertemplateString({ - args: [{ x: '2019-01-01' }], + data: [{ x: '2019-01-01' }], fallback: '', - string: '%{x|%b %-d, %Y}' + template: '%{x|%b %-d, %Y}' }) ).toEqual('Jan 1, 2019'); }); @@ -2695,40 +2695,33 @@ describe('Test lib.js:', function () { it('looks for default label if no format is provided', function () { expect( Lib.hovertemplateString({ - args: [{ y: 0.123 }], + data: [{ y: 0.123 }], fallback: '', labels: { yLabel: '0.1' }, - string: 'y: %{y}' + template: 'y: %{y}' }) ).toEqual('y: 0.1'); }); it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); - Lib.hovertemplateString({ - fallback: '', - string: '%{idontexist}' - }); + Lib.hovertemplateString({ fallback: '', template: '%{idontexist}' }); expect(Lib.warn.calls.count()).toBe(1); for (var i = 0; i < 15; i++) { - Lib.hovertemplateString({ - fallback: '', - string: '%{idontexist}' - }); + Lib.hovertemplateString({ fallback: '', template: '%{idontexist}' }); } expect(Lib.warn.calls.count()).toBe(10); }); }); describe('texttemplateString', function () { - var locale = false; it('evaluates attributes', function () { expect( Lib.texttemplateString({ - args: [{ bar: 'baz' }], + data: [{ bar: 'baz' }], fallback: '', - string: 'foo %{bar}' + template: 'foo %{bar}' }) ).toEqual('foo baz'); }); @@ -2736,10 +2729,10 @@ describe('Test lib.js:', function () { it('looks for default label if no format is provided', function () { expect( Lib.texttemplateString({ - args: [{ y: 0.123 }], + data: [{ y: 0.123 }], fallback: '', labels: { yLabel: '0.1' }, - string: 'y: %{y}' + template: 'y: %{y}' }) ).toEqual('y: 0.1'); }); @@ -2747,20 +2740,20 @@ describe('Test lib.js:', function () { it('preserves null and NaN', function () { expect( Lib.texttemplateString({ - args: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + data: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], fallback: '', - string: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' + template: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' }) ).toEqual('null NaN null NaN null NaN'); }); it('warns user up to 10 times if a variable cannot be found', function () { spyOn(Lib, 'warn').and.callThrough(); - Lib.texttemplateString({ fallback: '', string: '%{idontexist}' }); + Lib.texttemplateString({ fallback: '', template: '%{idontexist}' }); expect(Lib.warn.calls.count()).toBe(1); for (var i = 0; i < 15; i++) { - Lib.texttemplateString({ fallback: '', string: '%{idontexist}' }); + Lib.texttemplateString({ fallback: '', template: '%{idontexist}' }); } expect(Lib.warn.calls.count()).toBe(11); }); From fb7a40c29e36ce117b6e870680d7b4b2d7559d81 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 9 Oct 2025 09:25:16 -0600 Subject: [PATCH 17/18] Add/update tests to check fallback value --- test/jasmine/tests/lib_test.js | 36 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 25f2806938b..c311d698ab8 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2602,16 +2602,6 @@ describe('Test lib.js:', function () { ).toEqual('foo asdf jkl;'); }); - it('replaces missing matches with fallback value', function () { - expect( - Lib.hovertemplateString({ - data: [{ group: 1 }], - fallback: '', - template: 'foo %{group} %{trace}' - }) - ).toEqual('foo 1 '); - }); - it('uses the value from the first object with the specified key', function () { var obj1 = { a: 'first' }; var obj2 = { a: 'second', foo: { bar: 'bar' } }; @@ -2711,7 +2701,19 @@ describe('Test lib.js:', function () { for (var i = 0; i < 15; i++) { Lib.hovertemplateString({ fallback: '', template: '%{idontexist}' }); } - expect(Lib.warn.calls.count()).toBe(10); + // Expect 11 since the suppression warning also calls Lib.warn + expect(Lib.warn.calls.count()).toBe(11); + }); + + // This test must come after the warning count since it will affect the count + it('replaces missing matches with fallback value', function () { + expect( + Lib.hovertemplateString({ + data: [{ group: 1 }], + fallback: 'Planet Express', + template: 'foo %{group} %{trace}' + }) + ).toEqual('foo 1 Planet Express'); }); }); @@ -2755,8 +2757,20 @@ describe('Test lib.js:', function () { for (var i = 0; i < 15; i++) { Lib.texttemplateString({ fallback: '', template: '%{idontexist}' }); } + // Expect 11 since the suppression warning also calls Lib.warn expect(Lib.warn.calls.count()).toBe(11); }); + + // This test must come after the warning count since it will affect the count + it('replaces missing matches with fallback value', function () { + expect( + Lib.texttemplateString({ + data: [{ group: 1 }], + fallback: 'Zoidberg', + template: 'foo %{group} %{trace}' + }) + ).toEqual('foo 1 Zoidberg'); + }); }); describe('relativeAttr()', function () { From faaae287d7c6ccde64b9f24e0eb7af03accc097e Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Thu, 9 Oct 2025 09:27:21 -0600 Subject: [PATCH 18/18] Add draftlog --- draftlogs/7577_add.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7577_add.md diff --git a/draftlogs/7577_add.md b/draftlogs/7577_add.md new file mode 100644 index 00000000000..86527206408 --- /dev/null +++ b/draftlogs/7577_add.md @@ -0,0 +1 @@ +- Add `hovertemplatefallback` and `texttemplatefallback` attributes [[#7577](https://github.com/plotly/plotly.js/pull/7577)]