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)] diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index 3cb3b447eeb..38e8686d102 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,32 +1282,35 @@ 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 = {}; appendArrayPointValue(pointValues, trace, d.i); - var meta = trace._meta || {}; - text = Lib.texttemplateString(text, labels, fullLayout._d3locale, pointValues, d, meta); + text = Lib.texttemplateString({ + data: [pointValues, d, trace._meta], + fallback: trace.texttemplatefallback, + labels, + locale: fullLayout._d3locale, + template: text + }); } 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 +1329,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 +1349,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 +1390,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 +1419,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 +1461,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 +1485,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 +1503,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 +1515,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 +1567,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 +1576,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 +1591,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 +1623,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 +1638,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 +1657,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 +1673,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 +1689,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 +1720,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 +1738,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 +1755,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 +1775,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 +1794,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 +1826,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 +1849,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 +1862,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 +1888,7 @@ function getMarkerAngle(d, trace) { } } - if(trace._geo) { + if (trace._geo) { var lon = d.lonlat[0]; var lat = d.lonlat[1]; @@ -1803,38 +1902,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 +1933,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..1b4a2b46d27 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,10 +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) { - return d.trace.index === winningPoint.trace.index; - }); + if (multipleHoverPoints[winningPoint.trace.type]) { + hoverData = hoverData.filter((d) => d.trace.index === winningPoint.trace.index); } else { hoverData = [winningPoint]; } @@ -811,19 +811,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 +829,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 +849,18 @@ 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) { + 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 +885,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 +903,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 +949,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 +971,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 +986,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 +1008,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 +1022,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 +1054,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 +1065,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 +1084,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 +1130,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 +1146,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 +1185,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 +1231,12 @@ 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';}); + const groupedHoverData = hoverData.filter((data) => 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 +1244,22 @@ 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({ + data: + hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }], + fallback: item0.trace.hovertemplatefallback, + locale: fullLayout._d3locale, + template: unifiedhovertitleText + }); 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 +1277,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 +1294,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 +1328,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 +1399,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 +1416,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 +1439,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 +1492,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 +1503,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 +1515,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 +1525,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 +1565,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 +1584,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 +1596,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,38 +1617,32 @@ 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) { - d.name = Lib.templateString(d.name, 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); } 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,36 +1651,36 @@ 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) { - var labels = d.hovertemplateLabels || d; + const { hovertemplate = false } = d; + if (hovertemplate) { + const 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']; } - text = Lib.hovertemplateString( - hovertemplate, + text = Lib.hovertemplateString({ + data: [d.eventData[0] || {}, d.trace._meta], + fallback: d.trace.hovertemplatefallback, labels, - fullLayout._d3locale, - d.eventData[0] || {}, - d.trace._meta - ); + locale: fullLayout._d3locale, + template: 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 @@ -1670,35 +1719,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 +1762,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 +1777,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 +1793,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 +1834,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 +1895,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 +1904,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 +1915,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 +1925,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 +1937,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 +1952,7 @@ function getHoverLabelOffsets(hoverLabel, rotateLabels) { var offsetX = 0; var offsetY = hoverLabel.offset; - if(rotateLabels) { + if (rotateLabels) { offsetY *= -YSHIFTY; offsetX = hoverLabel.offset * YSHIFTX; } @@ -1902,12 +1967,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 +1985,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 +2012,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 +2065,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 +2104,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 +2130,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 +2142,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 +2204,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 +2217,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 +2251,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 +2266,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 +2280,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 +2293,7 @@ function createSpikelines(gd, closestPoints, opts) { } } - if(showX) { + if (showX) { var vLinePoint = closestPoints.vLinePoint; var vLinePointX, vLinePointY; @@ -2200,30 +2301,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 +2335,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 +2350,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 +2365,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 +2381,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 +2401,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 +2424,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 +2445,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 +2469,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 +2481,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; @@ -2407,6 +2510,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..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', { @@ -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,8 @@ 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) }), + texttemplatefallback: templatefallbackAttrs(), font: fontAttrs({ editType: 'calc+arraydraw', colorEditType: 'arraydraw', @@ -358,10 +340,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 +361,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 +381,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..728d803cc03 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,21 @@ 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'); + coerce('label.texttemplatefallback'); + } + 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..4c3cb3b49af 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, - {}, - gd._fullLayout._d3locale, - templateValues); + text = Lib.texttemplateStringForShapes({ + data: [templateValues], + fallback: options.label.texttemplatefallback, + locale: gd._fullLayout._d3locale, + template: options.label.texttemplate + }); } 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..30671801f26 100644 --- a/src/components/shapes/draw_newshape/attributes.js +++ b/src/components/shapes/draw_newshape/attributes.js @@ -5,235 +5,242 @@ 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({ - 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) } + ), + texttemplatefallback: templatefallbackAttrs(), + 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..9ade2c0fc53 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,8 @@ 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) { + coerce('newshape.label.texttemplatefallback'); + 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..218e0620f73 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,40 +1057,36 @@ 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 : ''; }); }; -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 @@ -1104,18 +1095,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, -}; -lib.texttemplateStringForShapes = function() { - return templateFormatString.apply(texttemplateWarningsForShapes, arguments); + parseMultDiv: true }; +lib.texttemplateStringForShapes = (params) => templateFormatString({ ...params, opts: texttemplateWarningsForShapes }); var TEMPLATE_STRING_FORMAT_SEPARATOR = /^[:|\|]/; /** @@ -1123,121 +1112,99 @@ 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.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 {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.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.template - 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; - if(isSpaceOther || isSpaceOtherSpace) key = key.substring(1); - if(isOtherSpace || isSpaceOtherSpace) key = key.substring(0, key.length - 1); +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); + 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; - if(opts.parseMultDiv) { + let parsedOp = null; + let parsedNumber = null; + if (opts.parseMultDiv) { var _match = multDivParser(key); key = _match.key; parsedOp = _match.op; parsedNumber = _match.number; } - var value; - if(hasOther) { + 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]; - if(!obj) continue; - if(obj.hasOwnProperty(key)) { + for (const obj of data) { + 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 && 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! Using fallback value.`); + if (count === max) lib.warn(`Too many '${name}' warnings - additional warnings will be suppressed`); opts.count++; - return match; + return fallback; } - if(format) { + if (parsedOp === '*') value *= parsedNumber; + if (parsedOp === '/') value /= parsedNumber; + + if (format) { var fmt; - if(format[0] === ':') { - fmt = d3locale ? d3locale.numberFormat : lib.numberFormat; - if(value !== '') { // e.g. skip missing data on heatmap + if (format[0] === ':') { + fmt = locale ? locale.numberFormat : lib.numberFormat; + if (value !== '') { + // e.g. skip missing data on heatmap value = fmt(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value); } } - if(format[0] === '|') { - fmt = d3locale ? d3locale.timeFormat : utcFormat; + if (format[0] === '|') { + fmt = locale ? locale.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 +1216,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 +1240,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 +1266,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 +1291,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 +1303,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 +1332,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 +1342,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 +1372,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 +1393,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..063753892c7 100644 --- a/src/plots/template_attributes.js +++ b/src/plots/template_attributes.js @@ -1,19 +1,13 @@ '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({ 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.'; 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.', @@ -24,8 +18,59 @@ function templateFormatStringDescription(opts) { } exports.templateFormatStringDescription = templateFormatStringDescription; -function shapeTemplateFormatStringDescription() { - return [ +function describeVariables({ description, keys = [] }) { + let descPart = description ? ' ' : ''; + if (keys.length > 0) { + const quotedKeys = keys.map((k) => `\`${k}\``); + descPart += 'Finally, the template string has access to '; + if (keys.length === 1) { + descPart += `variable ${quotedKeys[0]}`; + } else { + descPart += `variables ${quotedKeys.slice(0, -1).join(', ')} and ${quotedKeys.slice(-1)}.`; + } + } + + return descPart; +} + +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 } : {}) +}); + +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 = ({ editType = 'arraydraw', newshape }, extra = {}) => ({ + valType: 'string', + dflt: '', + editType, + description: [ + `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}".', 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{x0:$.2f}". See', @@ -38,100 +83,13 @@ function shapeTemplateFormatStringDescription() { '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 || []; - 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 '; - if(keys.length === 1) { - descPart = descPart + 'variable ' + quotedKeys[0]; - } else { - descPart = 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 = { - 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.', - descPart, - '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 = function(opts, extra) { - opts = opts || {}; - extra = extra || {}; - - var descPart = describeVariables(extra); - - var 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.', - descPart - ].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); - - 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; -}; + 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." +}); diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 04f778ec2aa..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'); @@ -21,38 +20,44 @@ var textFontAttrs = fontAttrs({ var scatterMarkerAttrs = scatterAttrs.marker; var scatterMarkerLineAttrs = scatterMarkerAttrs.line; -var markerLineWidth = extendFlat({}, - scatterMarkerLineAttrs.width, { dflt: 0 }); +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 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 +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', @@ -198,9 +201,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 +227,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..3ff7f51b7c7 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'); @@ -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, { @@ -55,8 +56,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 +69,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 +94,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 +121,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 +131,33 @@ 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'); + coerce('texttemplatefallback'); } - if(hasInside) { - if(moduleHasInsideanchor) coerce('insidetextanchor'); + if (hasInside) { + if (moduleHasInsideanchor) coerce('insidetextanchor'); } } @@ -163,5 +165,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..37653f9c3a8 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,21 @@ 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 || {}); + if (customdata) obj.customdata = customdata; + return Lib.texttemplateString({ + data: [pt, obj, trace._meta], + fallback: trace.texttemplatefallback, + labels: obj, + locale: fullLayout._d3locale, + template: texttemplate + }); } 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 +1105,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..a15127ee42c 100644 --- a/src/traces/barpolar/attributes.js +++ b/src/traces/barpolar/attributes.js @@ -1,11 +1,10 @@ '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'); - 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, { @@ -65,6 +59,7 @@ module.exports = { hoverinfo: scatterPolarAttrs.hoverinfo, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: barAttrs.selected, unselected: barAttrs.unselected diff --git a/src/traces/barpolar/defaults.js b/src/traces/barpolar/defaults.js index bca5588817b..de8b98d62f8 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; } @@ -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/attributes.js b/src/traces/box/attributes.js index 6acfb11b7db..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; @@ -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,20 @@ 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.' }), + hovertemplatefallback: templatefallbackAttrs(), 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..a7ea6ce01bb 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,8 +275,9 @@ 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'); + coerce('hovertemplatefallback'); } Lib.coerceSelectionMarkerOpacity(traceOut, coerce); @@ -297,14 +290,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..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'); @@ -10,82 +10,83 @@ 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(), + hovertemplatefallback: templatefallbackAttrs(), + 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..795167eb3ca 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,29 +28,30 @@ 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'); } coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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..dae30b0c5da 100644 --- a/src/traces/choroplethmap/attributes.js +++ b/src/traces/choroplethmap/attributes.js @@ -2,106 +2,105 @@ 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; -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'] }), + hovertemplatefallback: templatefallbackAttrs(), + 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..68a9dc2d430 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; @@ -30,12 +33,13 @@ 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'); + 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..00f89532c7b 100644 --- a/src/traces/choroplethmapbox/attributes.js +++ b/src/traces/choroplethmapbox/attributes.js @@ -2,106 +2,105 @@ 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; -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'] }), + hovertemplatefallback: templatefallbackAttrs(), + 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..68a9dc2d430 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; @@ -30,12 +33,13 @@ 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'); + 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..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'); @@ -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,8 @@ var attrs = { description: 'Same as `text`.' }, - hovertemplate: hovertemplateAttrs({editType: 'calc'}, {keys: ['norm']}), + hovertemplate: hovertemplateAttrs({ editType: 'calc' }, { keys: ['norm'] }), + hovertemplatefallback: templatefallbackAttrs(), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), @@ -162,18 +154,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..459be0f43f9 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,11 +50,12 @@ 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'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('uhoverformat'); coerce('vhoverformat'); coerce('whoverformat'); diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 191cfa7f17c..e28bab378a6 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -14,260 +14,251 @@ 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, + hovertemplatefallback: heatmapAttrs.hovertemplatefallback, + 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}, + }), + texttemplatefallback: heatmapAttrs.texttemplatefallback, + 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..220ff69d965 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; } @@ -34,21 +33,19 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hoverongaps'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); - 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..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'); @@ -29,54 +29,56 @@ 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(), + hovertemplatefallback: templatefallbackAttrs(), + 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..25d04a7f72d 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; } @@ -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'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/densitymapbox/attributes.js b/src/traces/densitymapbox/attributes.js index 7de62ad55ae..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'); @@ -29,54 +29,56 @@ 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(), + hovertemplatefallback: templatefallbackAttrs(), + 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..25d04a7f72d 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; } @@ -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'}); + colorscaleDefaults(traceIn, traceOut, layout, coerce, { prefix: '', cLetter: 'z' }); }; diff --git a/src/traces/funnel/attributes.js b/src/traces/funnel/attributes.js index 86a52007b13..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,9 +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'] @@ -49,14 +47,13 @@ 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, - 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 +67,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 +81,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 +96,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..d7c875518fb 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'); @@ -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, { @@ -43,7 +44,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 +53,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 +78,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..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; @@ -25,7 +24,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 +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'], @@ -77,16 +74,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 +90,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..949659d8c56 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; } @@ -39,13 +37,15 @@ 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'); + if (!textTemplate) textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); - if(textTemplate || (textInfo && textInfo !== 'none')) { + if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { moduleHasSelected: false, @@ -55,14 +55,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..009d74c256f 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -1,139 +1,133 @@ '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'); +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'); -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(), + hovertemplatefallback: templatefallbackAttrs(), + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['x', 'y', 'z', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), + 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..98c35bb980b 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; } @@ -28,13 +27,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); handleHeatmapLabelDefaults(coerce, 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/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/heatmap/plot.js b/src/traces/heatmap/plot.js index 2db0d0eb3b2..1d812bbd2df 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({ + data: [obj, trace._meta], + fallback: trace.texttemplatefallback, + labels: obj, + locale: gd._fullLayout._d3locale, + template: texttemplate + }); + 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..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'); @@ -13,16 +12,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 +28,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 +191,11 @@ module.exports = { ].join(' ') }, - hovertemplate: hovertemplateAttrs({}, { - keys: constants.eventDataKeys - }), + hovertemplate: hovertemplateAttrs({}, { keys: constants.eventDataKeys }), + hovertemplatefallback: templatefallbackAttrs(), - texttemplate: texttemplateAttrs({ - arrayOk: false, - editType: 'plot' - }, { - keys: ['label', 'value'] - }), + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['label', 'value'] }), + texttemplatefallback: templatefallbackAttrs(), textposition: extendFlat({}, barAttrs.textposition, { arrayOk: false diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index e381b0d7841..226d5b2bd2e 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'); } @@ -35,18 +35,18 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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 +57,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 +72,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..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; @@ -69,15 +68,12 @@ 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'] }), + hovertemplatefallback: templatefallbackAttrs(), + texttemplate: texttemplateAttrs({ arrayOk: false, editType: 'plot' }, { keys: ['z'] }), + texttemplatefallback: templatefallbackAttrs(), 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..3c085939fe0 100644 --- a/src/traces/histogram2d/defaults.js +++ b/src/traces/histogram2d/defaults.js @@ -8,18 +8,18 @@ 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'); + coerce('hovertemplatefallback'); handleHeatmapLabelDefaults(coerce, layout); diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index f1feef7e561..94b787e1524 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -7,45 +7,48 @@ 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, + hovertemplatefallback: histogram2dAttrs.hovertemplatefallback, + texttemplate: contourAttrs.texttemplate, + texttemplatefallback: contourAttrs.texttemplatefallback, + 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..ef5b647e586 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,15 @@ 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' - ) { + coerce('hovertemplatefallback'); + 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..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; @@ -37,7 +36,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 +47,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 +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, @@ -97,5 +92,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..4c4ad48332a 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,10 +41,12 @@ 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'); + coerce('texttemplatefallback'); + if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var hasPathbar = coerce('pathbar.visible'); @@ -62,12 +64,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 +81,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..2d9ce34163c 100644 --- a/src/traces/image/attributes.js +++ b/src/traces/image/attributes.js @@ -2,14 +2,14 @@ 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; 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,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, + zorder: zorder }); diff --git a/src/traces/image/defaults.js b/src/traces/image/defaults.js index 24131a669be..9a2efa00528 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; @@ -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/isosurface/attributes.js b/src/traces/isosurface/attributes.js index cd1be7240e0..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'); @@ -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,161 @@ 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(), + hovertemplatefallback: templatefallbackAttrs(), + 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..b5a8d4138c1 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -2,238 +2,238 @@ 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'); 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' }), + hovertemplatefallback: templatefallbackAttrs(), - 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..26cf1b475b3 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,29 +62,32 @@ 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); } coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js index a8a3663fc21..960c173440b 100644 --- a/src/traces/parcats/attributes.js +++ b/src/traces/parcats/attributes.js @@ -4,39 +4,37 @@ 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'}), - { - 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(' ') + } + ), + hovertemplatefallback: templatefallbackAttrs() +}); 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 +53,21 @@ 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(' ') + } + ), + hovertemplatefallback: templatefallbackAttrs(), arrangement: { valType: 'enumerated', @@ -119,9 +118,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..b967d6050aa 100644 --- a/src/traces/parcats/defaults.js +++ b/src/traces/parcats/defaults.js @@ -13,12 +13,13 @@ 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)) { - 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 +35,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 +62,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 +82,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; } @@ -90,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/attributes.js b/src/traces/pie/attributes.js index cd1c657346f..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; @@ -51,15 +50,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 +70,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 +78,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 +107,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 +133,22 @@ 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'] }), + hovertemplatefallback: templatefallbackAttrs(), + texttemplate: texttemplateAttrs({ editType: 'plot' }, { keys: ['label', 'color', 'value', 'percent', 'text'] }), + texttemplatefallback: templatefallbackAttrs(), 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 +178,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 +186,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 +194,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 +218,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 +228,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 +242,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 +264,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..f27c890de77 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; } @@ -85,13 +80,15 @@ 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'); + if (!textTemplate) textInfo = coerce('textinfo', Lib.isArrayOrTypedArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); - if(textTemplate || (textInfo && textInfo !== 'none')) { + if (textTemplate || (textInfo && textInfo !== 'none')) { var textposition = coerce('textposition'); handleText(traceIn, traceOut, layout, coerce, textposition, { moduleHasSelected: false, @@ -104,14 +101,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 +116,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..d31ad0a7c32 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,30 @@ 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; - pt.text = Lib.texttemplateString(txt, obj, gd._fullLayout._d3locale, obj, trace._meta || {}); + if (isValidTextValue(ptTx) || ptTx === '') obj.text = ptTx; + pt.text = Lib.texttemplateString({ + data: [obj, trace._meta], + fallback: trace.texttemplatefallback, + labels: obj, + locale: gd._fullLayout._d3locale, + template: txt + }); } } } 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..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; @@ -13,276 +13,279 @@ 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'] + } + ), + hovertemplatefallback: templatefallbackAttrs(), + 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'] + } + ), + hovertemplatefallback: templatefallbackAttrs(), + 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..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; @@ -46,9 +45,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 +79,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 +104,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 +218,14 @@ 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(), + texttemplatefallback: templatefallbackAttrs(), 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,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: { @@ -318,8 +307,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 +390,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 +433,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 +652,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..b32e5a8fe2a 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,35 @@ 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'); + coerce('texttemplatefallback'); 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 +70,28 @@ 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'); + 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'}); + 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..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'); @@ -18,25 +17,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 +49,133 @@ 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(), + texttemplatefallback: templatefallbackAttrs(), + 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(), + hovertemplatefallback: templatefallbackAttrs(), + + 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..fafa4898216 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]), @@ -263,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({ + data: [pointValues, d, data._meta], + fallback: data.texttemplatefallback, + labels, + locale: d3locale, + template: txt(i) + }); } } @@ -275,24 +274,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 +306,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 +328,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 +371,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 +396,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 +411,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 +428,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 +462,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 +485,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 +511,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..e99182fef69 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; } @@ -24,48 +24,50 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); 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'); + coerce('texttemplatefallback'); 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 +79,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..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; @@ -33,7 +32,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 +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.', @@ -63,8 +61,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 +82,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') ), @@ -119,5 +118,6 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), zorder: scatterAttrs.zorder }; diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index f1234ea192f..2b710791df7 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; } @@ -36,44 +36,48 @@ 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'; 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('hovertemplatefallback'); + } coerce('zorder'); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 4b14da5e813..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'); @@ -20,151 +19,157 @@ 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'] }), + texttemplatefallback: templatefallbackAttrs(), + 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(), + hovertemplatefallback: templatefallbackAttrs() + }, + 'calc', + 'nested' +); diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index f2a1ad9b9d8..88e20a17ff7 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; } @@ -58,24 +58,26 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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'); + coerce('texttemplatefallback'); 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..8f05e9f5cc4 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -16,99 +16,99 @@ 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; +attrs.hovertemplatefallback = scatterAttrs.hovertemplatefallback; attrs.texttemplate = scatterAttrs.texttemplate; +attrs.texttemplatefallback = scatterAttrs.texttemplatefallback; diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index ed4cac9b55e..22e3f48db31 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,26 +122,37 @@ 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); - var meta = trace._meta || {}; - optsOut.text.push(Lib.texttemplateString(txt(i), labels, d3locale, pointValues, d, meta)); + optsOut.text.push( + Lib.texttemplateString({ + data: [pointValues, d, trace._meta], + fallback: trace.texttemplatefallback, + labels, + locale: d3locale, + template: txt(i) + }) + ); } } 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 +160,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 +171,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 +184,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 +196,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 +214,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 +243,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 +264,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 +278,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 +312,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 +326,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 +347,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 +361,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 +405,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 +421,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 +431,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 +447,7 @@ function convertErrorBarStyle(trace, target, plotGlPixelRatio) { color: target.color }; - if(target.copy_ystyle) { + if (target.copy_ystyle) { optsOut = trace.error_y; } @@ -441,7 +462,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 +473,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 +503,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 +519,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 +547,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 +575,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 +598,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 +645,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 +696,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..efaecbb5e63 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; } @@ -37,25 +37,27 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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'); + coerce('texttemplatefallback'); handleTextDefaults(traceIn, traceOut, layout, coerce, { noFontShadow: true, noFontLineposition: true, - noFontTextcase: true, + noFontTextcase: true }); } @@ -63,13 +65,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..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'); @@ -16,167 +15,166 @@ 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'] }), + texttemplatefallback: templatefallbackAttrs(), + 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(), + hovertemplatefallback: templatefallbackAttrs() }, - - 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..c2bfce0c56d 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,41 +279,39 @@ function makeSymbolGeoJSON(calcTrace, gd) { var symbol = marker.symbol; var angle = marker.angle; - var fillSymbol = (symbol !== 'circle') ? - getFillFunc(symbol) : - blankFillFunc; + var fillSymbol = symbol !== 'circle' ? getFillFunc(symbol) : blankFillFunc; - var fillAngle = (angle !== 'auto') ? - getFillFunc(angle, true) : - 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); - var meta = trace._meta || {}; - text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); + text = Lib.texttemplateString({ + data: [pointValues, calcPt, trace._meta], + fallback: trace.texttemplatefallback, + labels, + locale: fullLayout._d3locale, + template: tt + }); } else { text = fillText(i); } - if(text) { + if (text) { text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); } @@ -335,19 +336,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 +365,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 +385,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..faeb083ac08 100644 --- a/src/traces/scattermap/defaults.js +++ b/src/traces/scattermap/defaults.js @@ -20,34 +20,36 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleLonLatDefaults(traceIn, traceOut, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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 +68,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..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'); @@ -16,167 +15,166 @@ 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'] }), + texttemplatefallback: templatefallbackAttrs(), + 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(), + hovertemplatefallback: templatefallbackAttrs() }, - - 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..380324bbefb 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,41 +279,39 @@ function makeSymbolGeoJSON(calcTrace, gd) { var symbol = marker.symbol; var angle = marker.angle; - var fillSymbol = (symbol !== 'circle') ? - getFillFunc(symbol) : - blankFillFunc; + var fillSymbol = symbol !== 'circle' ? getFillFunc(symbol) : blankFillFunc; - var fillAngle = (angle !== 'auto') ? - getFillFunc(angle, true) : - 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); - var meta = trace._meta || {}; - text = Lib.texttemplateString(tt, labels, fullLayout._d3locale, pointValues, calcPt, meta); + text = Lib.texttemplateString({ + data: [pointValues, calcPt, trace._meta], + fallback: trace.texttemplatefallback, + labels, + locale: fullLayout._d3locale, + template: tt + }); } else { text = fillText(i); } - if(text) { + if (text) { text = text.replace(NEWLINES, '').replace(BR_TAG_ALL, '\n'); } @@ -335,19 +336,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 +365,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 +385,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..faeb083ac08 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -20,34 +20,36 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } var len = handleLonLatDefaults(traceIn, traceOut, coerce); - if(!len) { + if (!len) { traceOut.visible = false; return; } coerce('text'); coerce('texttemplate'); + coerce('texttemplatefallback'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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 +68,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..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'); @@ -57,7 +56,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 +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: { @@ -93,7 +91,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, @@ -125,6 +123,7 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: scatterAttrs.selected, unselected: scatterAttrs.unselected diff --git a/src/traces/scatterpolar/defaults.js b/src/traces/scatterpolar/defaults.js index 15b529f3c0e..a3260367ce7 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,30 @@ 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}); + 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'); + coerce('texttemplatefallback'); 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 +58,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 +76,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 +94,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..80f896b4dbc 100644 --- a/src/traces/scatterpolargl/attributes.js +++ b/src/traces/scatterpolargl/attributes.js @@ -1,47 +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/scatterpolargl/defaults.js b/src/traces/scatterpolargl/defaults.js index b82262ebf40..7f095c4ea71 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,32 @@ 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}); + 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'); + coerce('texttemplatefallback'); 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..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,9 +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: { @@ -49,7 +47,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, @@ -76,6 +74,7 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), selected: scatterAttrs.selected, unselected: scatterAttrs.unselected diff --git a/src/traces/scattersmith/defaults.js b/src/traces/scattersmith/defaults.js index 622901b83c5..7765e0212e6 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,30 @@ 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}); + 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'); + coerce('texttemplatefallback'); 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 +57,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 +75,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..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'); @@ -61,7 +60,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 +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.', @@ -91,8 +89,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 +111,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') ), @@ -148,4 +147,5 @@ module.exports = { }), hoveron: scatterAttrs.hoveron, hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs() }; diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 0b7e3a0e630..6be1e2d0363 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,45 @@ 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); - 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'); + coerce('texttemplatefallback'); 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..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; @@ -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, { @@ -131,6 +131,7 @@ module.exports = { }), hovertemplate: hovertemplateAttrs(), + hovertemplatefallback: templatefallbackAttrs(), xhoverformat: axisHoverFormat('x'), yhoverformat: axisHoverFormat('y'), @@ -145,9 +146,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 +160,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..d3c4154b9ba 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; } @@ -33,10 +33,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); 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 +56,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 +74,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 +85,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 +96,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 +104,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 +123,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 +140,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 +159,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..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'); @@ -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,11 @@ 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'] } + ), + hovertemplatefallback: templatefallbackAttrs(), uhoverformat: axisHoverFormat('u', 1), vhoverformat: axisHoverFormat('v', 1), whoverformat: axisHoverFormat('w', 1), @@ -137,17 +127,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..f49de256e21 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,11 +52,12 @@ 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'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('uhoverformat'); coerce('vhoverformat'); coerce('whoverformat'); diff --git a/src/traces/sunburst/attributes.js b/src/traces/sunburst/attributes.js index 73a769b8009..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; @@ -15,16 +14,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 +51,7 @@ module.exports = { }, count: { valType: 'flaglist', - flags: [ - 'branches', - 'leaves' - ], + flags: ['branches', 'leaves'], dflt: 'leaves', editType: 'calc', description: [ @@ -72,7 +66,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 +81,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 +135,23 @@ 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']) }), + texttemplatefallback: templatefallbackAttrs(), 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 }), + hovertemplatefallback: templatefallbackAttrs(), textfont: pieAttrs.textfont, insidetextorientation: pieAttrs.insidetextorientation, @@ -188,7 +162,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 +171,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 +189,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..2439c817df6 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,22 +35,22 @@ 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'); + 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/sunburst/plot.js b/src/traces/sunburst/plot.js index 386dc1bf4ce..3708e2b8136 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,111 @@ 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 || {}); + return Lib.texttemplateString({ + data: [obj, trace._meta], + fallback: trace.texttemplatefallback, + labels: obj, + locale: fullLayout._d3locale, + template: txt + }); }; 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..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; @@ -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,192 @@ 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(), + hovertemplatefallback: templatefallbackAttrs(), - 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..9da3b2e3d26 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'); @@ -72,6 +66,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { coerce('text'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); coerce('xhoverformat'); coerce('yhoverformat'); coerce('zhoverformat'); @@ -89,29 +84,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 +121,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 +132,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..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; @@ -25,14 +24,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 +52,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 +63,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(' ') - }, - l: { - valType: 'number', - min: 0, - editType: 'plot', - description: [ - 'Sets the padding form the left (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' }, - 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 +152,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 +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, @@ -242,25 +202,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..b877b34bedd 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,10 +45,12 @@ 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'); + coerce('texttemplatefallback'); + if (!traceOut.texttemplate) coerce('textinfo', Lib.isArrayOrTypedArray(text) ? 'text+label' : 'label'); coerce('hovertext'); coerce('hovertemplate'); + coerce('hovertemplatefallback'); var hasPathbar = coerce('pathbar.visible'); @@ -66,12 +68,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 +94,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..fc1e1f35fb7 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(' ') }), @@ -153,6 +153,7 @@ module.exports = { text: boxAttrs.text, hovertext: boxAttrs.hovertext, hovertemplate: boxAttrs.hovertemplate, + hovertemplatefallback: boxAttrs.hovertemplatefallback, quartilemethod: boxAttrs.quartilemethod, @@ -161,9 +162,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 +172,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 +204,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..46f3959a9eb 100644 --- a/src/traces/volume/attributes.js +++ b/src/traces/volume/attributes.js @@ -8,65 +8,72 @@ 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, + hovertemplatefallback: isosurfaceAttrs.hovertemplatefallback + }, - 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..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'); @@ -29,7 +28,7 @@ function directionAttrs(dirTxt) { editType: 'style', description: 'Sets the line width of all ' + dirTxt + ' values.' }), - editType: 'style', + editType: 'style' }, editType: 'style' }, @@ -44,9 +43,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 +55,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 +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'] @@ -99,9 +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, @@ -123,9 +118,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 +130,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..203f7af7ce2 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'); @@ -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, { @@ -57,10 +58,10 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) { moduleHasInsideanchor: true }); - - if(traceOut.textposition !== 'none') { + if (traceOut.textposition !== 'none') { coerce('texttemplate'); - if(!traceOut.texttemplate) coerce('textinfo'); + coerce('texttemplatefallback'); + if (!traceOut.texttemplate) coerce('textinfo'); } handleDirection(coerce, 'increasing', INCREASING_COLOR); @@ -68,10 +69,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 +86,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/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' })); } }; diff --git a/test/image/baselines/icicle_with-without_values_template.png b/test/image/baselines/icicle_with-without_values_template.png index 32c22da1266..ef1efaa30d1 100644 Binary files a/test/image/baselines/icicle_with-without_values_template.png and b/test/image/baselines/icicle_with-without_values_template.png differ diff --git a/test/image/baselines/text_on_shapes_texttemplate.png b/test/image/baselines/text_on_shapes_texttemplate.png index c0b1b2e989f..73e262500bb 100644 Binary files a/test/image/baselines/text_on_shapes_texttemplate.png and b/test/image/baselines/text_on_shapes_texttemplate.png differ diff --git a/test/image/baselines/treemap_with-without_values_template.png b/test/image/baselines/treemap_with-without_values_template.png index 332c97f5b42..b6e39176ddd 100644 Binary files a/test/image/baselines/treemap_with-without_values_template.png and b/test/image/baselines/treemap_with-without_values_template.png differ diff --git a/test/jasmine/tests/icicle_test.js b/test/jasmine/tests/icicle_test.js index 7032478731c..2d3d9c180a1 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}' + [ + ' of ', + '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: ', // 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}' + [ + ' of ', + '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..c311d698ab8 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,209 +2474,356 @@ 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() { - var locale = false; - 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'); + describe('hovertemplateString', function () { + it('evaluates attributes', function () { + expect( + Lib.hovertemplateString({ + data: [{ bar: 'baz' }], + fallback: '', + template: 'foo %{bar}' + }) + ).toEqual('foo baz'); }); - it('evaluates nested properties', function() { - expect(Lib.hovertemplateString('foo %{bar.baz}', {}, locale, {bar: {baz: 'asdf'}})).toEqual('foo asdf'); + it('evaluates attributes with a dot in their name', function () { + expect( + Lib.hovertemplateString({ + data: [{ 'marker.size': 12 }, { marker: { size: 14 } }], + fallback: '', + template: '%{marker.size}' + }) + ).toEqual('12'); }); - it('evaluates array nested properties', function() { - expect(Lib.hovertemplateString('foo %{bar[0].baz}', {}, locale, {bar: [{baz: 'asdf'}]})).toEqual('foo asdf'); + it('evaluates nested properties', function () { + expect( + Lib.hovertemplateString({ + data: [{ bar: { baz: 'asdf' } }], + fallback: '', + template: 'foo %{bar.baz}' + }) + ).toEqual('foo asdf'); }); - it('should work with the number *0*', function() { - expect(Lib.hovertemplateString('%{group}', {}, locale, {group: 0})).toEqual('0'); + it('evaluates array nested properties', function () { + expect( + Lib.hovertemplateString({ + data: [{ bar: [{ baz: 'asdf' }] }], + fallback: '', + template: 'foo %{bar[0].baz}' + }) + ).toEqual('foo asdf'); }); - 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*', function () { + expect( + Lib.hovertemplateString({ + data: [{ group: 0 }], + fallback: '', + template: '%{group}' + }) + ).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('should work with the number *0* (nested case)', function () { + expect( + Lib.hovertemplateString({ + data: [{ x: { y: 0 } }], + fallback: '', + template: '%{x.y}' + }) + ).toEqual('0'); }); - it('subtitutes multiple matches', function() { - expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, {group: 'asdf', trace: 'jkl;'})).toEqual('foo asdf jkl;'); + it('preserves null and NaN', function () { + expect( + Lib.hovertemplateString({ + data: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + fallback: '', + template: '%{a} %{b} %{c.d} %{c.e} %{f[0]} %{f[1]}' + }) + ).toEqual('null NaN null NaN null NaN'); }); - it('replaces missing matches with template string', function() { - expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, locale, {group: 1})).toEqual('foo 1 %{trace}'); + it('subtitutes multiple matches', function () { + expect( + Lib.hovertemplateString({ + data: [{ group: 'asdf', trace: 'jkl;' }], + fallback: '', + template: 'foo %{group} %{trace}' + }) + ).toEqual('foo asdf jkl;'); }); - 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'); - expect(Lib.hovertemplateString('foo %{a}', {}, locale, obj2, obj1)).toEqual('foo second'); + expect( + Lib.hovertemplateString({ + data: [obj1, obj2], + fallback: '', + template: 'foo %{a}' + }) + ).toEqual('foo first'); + expect( + Lib.hovertemplateString({ + data: [obj2, obj1], + fallback: '', + template: 'foo %{a}' + }) + ).toEqual('foo second'); // Nested Keys - expect(Lib.hovertemplateString('foo %{foo.bar}', {}, locale, obj1, obj2)).toEqual('foo bar'); + expect( + Lib.hovertemplateString({ + data: [obj1, obj2], + fallback: '', + template: '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({ + data: [{ y: 0 }, { y: 1 }], + fallback: '', + template: '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'); + it('formats numbers using d3-format mini-language when `:`', function () { + expect( + Lib.hovertemplateString({ + data: [{ a: 0.123 }], + fallback: '', + template: 'a: %{a:.0%}' + }) + ).toEqual('a: 12%'); + expect( + Lib.hovertemplateString({ + data: [{ a: 0.123 }], + fallback: '', + template: 'a: %{a:0.2%}' + }) + ).toEqual('a: 12.30%'); + expect( + Lib.hovertemplateString({ + data: [{ b: 43 }], + fallback: '', + template: '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'); + it('formats date using d3-time-format mini-language `|`', function () { + expect( + Lib.hovertemplateString({ + data: [{ a: '2019-05-22' }], + fallback: '', + template: 'a: %{a|%A}' + }) + ).toEqual('a: Wednesday'); + expect( + Lib.hovertemplateString({ + data: [{ x: '2019-01-01' }], + fallback: '', + template: '%{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'); + it('looks for default label if no format is provided', function () { + expect( + Lib.hovertemplateString({ + data: [{ y: 0.123 }], + fallback: '', + labels: { yLabel: '0.1' }, + template: 'y: %{y}' + }) + ).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}', {}); + Lib.hovertemplateString({ fallback: '', template: '%{idontexist}' }); expect(Lib.warn.calls.count()).toBe(1); - for(var i = 0; i < 15; i++) { - Lib.hovertemplateString('%{idontexist}', {}); + 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); }); - it('does not error out when arguments are undefined', function() { - expect(function() { - Lib.hovertemplateString('y: %{y}', undefined, locale, undefined); - }).not.toThrow(); + // 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'); }); }); - describe('texttemplateString', function() { - var locale = false; - it('evaluates attributes', function() { - expect(Lib.texttemplateString('foo %{bar}', {}, locale, {bar: 'baz'})).toEqual('foo baz'); + describe('texttemplateString', function () { + it('evaluates attributes', function () { + expect( + Lib.texttemplateString({ + data: [{ bar: 'baz' }], + fallback: '', + template: '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'); + it('looks for default label if no format is provided', function () { + expect( + Lib.texttemplateString({ + data: [{ y: 0.123 }], + fallback: '', + labels: { yLabel: '0.1' }, + template: '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]} - )) - .toEqual('null NaN null NaN null NaN'); + it('preserves null and NaN', function () { + expect( + Lib.texttemplateString({ + data: [{ a: null, b: NaN, c: { d: null, e: NaN }, f: [null, NaN] }], + fallback: '', + 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() { + 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: '', template: '%{idontexist}' }); expect(Lib.warn.calls.count()).toBe(1); - for(var i = 0; i < 15; i++) { - Lib.texttemplateString('%{idontexist}', {}); + 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() { - 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 +2831,9 @@ describe('Test lib.js:', function() { }); }); - describe('sort', function() { + describe('sort', function () { var callCount; - beforeEach(function() { + beforeEach(function () { callCount = 0; }); @@ -2640,7 +2849,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 +2858,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 +2868,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 +2887,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 +2901,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 +2941,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 +2952,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 +2966,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 +2997,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 +3032,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 +3050,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 +3062,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 +3081,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 +3091,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 +3138,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 +3147,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 +3161,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 +3173,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..d082bfe9ebf 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}' + [ + ' of ', + '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: ', // 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}' + [ + ' of ', + '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}' + [ + ' of ', + '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..91f3d4104b7 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: ', // 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); }); }); 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",