diff --git a/src/components/fx/calc.js b/src/components/fx/calc.js index 5d93ccb07d9..38433e3094f 100644 --- a/src/components/fx/calc.js +++ b/src/components/fx/calc.js @@ -13,25 +13,41 @@ var Registry = require('../../registry'); module.exports = function calc(gd) { var calcdata = gd.calcdata; + var fullLayout = gd._fullLayout; + + function makeCoerceHoverInfo(trace) { + return function(val) { + return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout); + }; + } for(var i = 0; i < calcdata.length; i++) { var cd = calcdata[i]; var trace = cd[0].trace; - if(!trace.hoverlabel) continue; + // don't include hover calc fields for pie traces + // as calcdata items might be sorted by value and + // won't match the data array order. + if(Registry.traceIs(trace, 'pie')) continue; + + var fillFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.fillArray; - var mergeFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.mergeArray; + fillFn(trace.hoverinfo, cd, 'hi', makeCoerceHoverInfo(trace)); - mergeFn(trace.hoverlabel.bgcolor, cd, 'hbg'); - mergeFn(trace.hoverlabel.bordercolor, cd, 'hbc'); - mergeFn(trace.hoverlabel.font.size, cd, 'hts'); - mergeFn(trace.hoverlabel.font.color, cd, 'htc'); - mergeFn(trace.hoverlabel.font.family, cd, 'htf'); + if(!trace.hoverlabel) continue; + + fillFn(trace.hoverlabel.bgcolor, cd, 'hbg'); + fillFn(trace.hoverlabel.bordercolor, cd, 'hbc'); + fillFn(trace.hoverlabel.font.size, cd, 'hts'); + fillFn(trace.hoverlabel.font.color, cd, 'htc'); + fillFn(trace.hoverlabel.font.family, cd, 'htf'); } }; -function paste(traceAttr, cd, cdAttr) { +function paste(traceAttr, cd, cdAttr, fn) { + fn = fn || Lib.identity; + if(Array.isArray(traceAttr)) { - cd[0][cdAttr] = traceAttr; + cd[0][cdAttr] = fn(traceAttr); } } diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index bc5edff9235..b9ebbd59491 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -354,11 +354,11 @@ function _hover(gd, evt, subplot) { trace: trace, xa: xaArray[subploti], ya: yaArray[subploti], - name: (gd.data.length > 1 || trace.hoverinfo.indexOf('name') !== -1) ? trace.name : undefined, // point properties - override all of these index: false, // point index in trace - only used by plotly.js hoverdata consumers distance: Math.min(distance, constants.MAXDIST), // pixel distance or pseudo-distance color: Color.defaultLine, // trace color + name: trace.name, x0: undefined, x1: undefined, y0: undefined, @@ -558,7 +558,7 @@ function createHoverText(hoverData, opts) { // to have common labels var i, traceHoverinfo; for(i = 0; i < hoverData.length; i++) { - traceHoverinfo = hoverData[i].trace.hoverinfo; + traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo; var parts = traceHoverinfo.split('+'); if(parts.indexOf('all') === -1 && parts.indexOf(hovermode) === -1) { @@ -724,7 +724,9 @@ function createHoverText(hoverData, opts) { else if(d.yLabel === undefined) text = d.xLabel; else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; - if(d.text && !Array.isArray(d.text)) text += (text ? '
' : '') + d.text; + if(d.text && !Array.isArray(d.text)) { + text += (text ? '
' : '') + d.text; + } // if 'text' is empty at this point, // put 'name' in main label and don't show secondary label @@ -1056,6 +1058,30 @@ function cleanPoint(d, hovermode) { var cd0 = d.cd[0]; var cd = d.cd[d.index] || {}; + function fill(key, calcKey, traceKey) { + var val; + + if(cd[calcKey]) { + val = cd[calcKey]; + } else if(cd0[calcKey]) { + var arr = cd0[calcKey]; + if(Array.isArray(arr) && Array.isArray(arr[d.index[0]])) { + val = arr[d.index[0]][d.index[1]]; + } + } else { + val = Lib.nestedProperty(trace, traceKey).get(); + } + + if(val) d[key] = val; + } + + fill('hoverinfo', 'hi', 'hoverinfo'); + fill('color', 'hbg', 'hoverlabel.bgcolor'); + fill('borderColor', 'hbc', 'hoverlabel.bordercolor'); + fill('fontFamily', 'htf', 'hoverlabel.font.family'); + fill('fontSize', 'hts', 'hoverlabel.font.size'); + fill('fontColor', 'htc', 'hoverlabel.font.color'); + d.posref = hovermode === 'y' ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2; // then constrain all the positions to be on the plot @@ -1123,7 +1149,7 @@ function cleanPoint(d, hovermode) { if(hovermode === 'y') d.distance += 1; } - var infomode = d.trace.hoverinfo; + var infomode = d.hoverinfo || d.trace.hoverinfo; if(infomode !== 'all') { infomode = infomode.split('+'); if(infomode.indexOf('x') === -1) d.xLabel = undefined; @@ -1133,29 +1159,6 @@ function cleanPoint(d, hovermode) { if(infomode.indexOf('name') === -1) d.name = undefined; } - function fill(key, calcKey, traceKey) { - var val; - - if(cd[calcKey]) { - val = cd[calcKey]; - } else if(cd0[calcKey]) { - var arr = cd0[calcKey]; - if(Array.isArray(arr) && Array.isArray(arr[d.index[0]])) { - val = arr[d.index[0]][d.index[1]]; - } - } else { - val = Lib.nestedProperty(trace, traceKey).get(); - } - - if(val) d[key] = val; - } - - fill('color', 'hbg', 'hoverlabel.bgcolor'); - fill('borderColor', 'hbc', 'hoverlabel.bordercolor'); - fill('fontFamily', 'htf', 'hoverlabel.font.family'); - fill('fontSize', 'hts', 'hoverlabel.font.size'); - fill('fontColor', 'htc', 'hoverlabel.font.color'); - return d; } diff --git a/src/components/fx/index.js b/src/components/fx/index.js index 545548bcbbd..bec718443c8 100644 --- a/src/components/fx/index.js +++ b/src/components/fx/index.js @@ -35,7 +35,9 @@ module.exports = { getDistanceFunction: helpers.getDistanceFunction, getClosest: helpers.getClosest, inbox: helpers.inbox, + castHoverOption: castHoverOption, + castHoverinfo: castHoverinfo, hover: require('./hover').hover, unhover: dragElement.unhover, @@ -57,18 +59,16 @@ function loneUnhover(containerOrSelection) { selection.selectAll('.spikeline').remove(); } -// Handler for trace-wide vs per-point hover label options +// helpers for traces that use Fx.loneHover + function castHoverOption(trace, ptNumber, attr) { - var labelOpts = trace.hoverlabel || {}; - var val = Lib.nestedProperty(labelOpts, attr).get(); - - if(Array.isArray(val)) { - if(Array.isArray(ptNumber) && Array.isArray(val[ptNumber[0]])) { - return val[ptNumber[0]][ptNumber[1]]; - } else { - return val[ptNumber]; - } - } else { - return val; + return Lib.castOption(trace, ptNumber, 'hoverlabel.' + attr); +} + +function castHoverinfo(trace, fullLayout, ptNumber) { + function _coerce(val) { + return Lib.coerceHoverinfo({hoverinfo: val}, {_module: trace._module}, fullLayout); } + + return Lib.castOption(trace, ptNumber, 'hoverinfo', _coerce); } diff --git a/src/lib/coerce.js b/src/lib/coerce.js index f3ef35d6598..667fdbf0dc1 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -12,6 +12,7 @@ var isNumeric = require('fast-isnumeric'); var tinycolor = require('tinycolor2'); +var baseTraceAttrs = require('../plots/attributes'); var getColorscale = require('../components/colorscale/get_scale'); var colorscaleNames = Object.keys(require('../components/colorscale/scales')); var nestedProperty = require('./nested_property'); @@ -196,7 +197,7 @@ exports.valObjects = { 'Values in `extras` cannot be combined.' ].join(' '), requiredOpts: ['flags'], - otherOpts: ['dflt', 'extras'], + otherOpts: ['dflt', 'extras', 'arrayOk'], coerceFunction: function(v, propOut, dflt, opts) { if(typeof v !== 'string') { propOut.set(dflt); @@ -338,6 +339,35 @@ exports.coerceFont = function(coerce, attr, dfltObj) { return out; }; +/** Coerce shortcut for 'hoverinfo' + * handling 1-vs-multi-trace dflt logic + * + * @param {object} traceIn : user trace object + * @param {object} traceOut : full trace object (requires _module ref) + * @param {object} layoutOut : full layout object (require _dataLength ref) + * @return {any} : the coerced value + */ +exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) { + var moduleAttrs = traceOut._module.attributes; + var attrs = moduleAttrs.hoverinfo ? + {hoverinfo: moduleAttrs.hoverinfo} : + baseTraceAttrs; + + var valObj = attrs.hoverinfo; + var dflt; + + if(layoutOut._dataLength === 1) { + var flags = valObj.dflt === 'all' ? + valObj.flags.slice() : + valObj.dflt.split('+'); + + flags.splice(flags.indexOf('name'), 1); + dflt = flags.join('+'); + } + + return exports.coerce(traceIn, traceOut, attrs, 'hoverinfo', dflt); +}; + exports.validate = function(value, opts) { var valObject = exports.valObjects[opts.valType]; diff --git a/src/lib/index.js b/src/lib/index.js index 815b627cc83..85e6fb7299f 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -31,6 +31,7 @@ lib.valObjects = coerceModule.valObjects; lib.coerce = coerceModule.coerce; lib.coerce2 = coerceModule.coerce2; lib.coerceFont = coerceModule.coerceFont; +lib.coerceHoverinfo = coerceModule.coerceHoverinfo; lib.validate = coerceModule.validate; var datesModule = require('./dates'); @@ -349,6 +350,15 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) { } }; +/** merges calcdata field (given by cdAttr) with traceAttr values + * + * N.B. Loop over minimum of cd.length and traceAttr.length + * i.e. it does not try to fill in beyond traceAttr.length-1 + * + * @param {array} traceAttr : trace attribute + * @param {object} cd : calcdata trace + * @param {string} cdAttr : calcdata key + */ lib.mergeArray = function(traceAttr, cd, cdAttr) { if(Array.isArray(traceAttr)) { var imax = Math.min(traceAttr.length, cd.length); @@ -356,6 +366,51 @@ lib.mergeArray = function(traceAttr, cd, cdAttr) { } }; +/** fills calcdata field (given by cdAttr) with traceAttr values + * or function of traceAttr values (e.g. some fallback) + * + * N.B. Loops over all cd items. + * + * @param {array} traceAttr : trace attribute + * @param {object} cd : calcdata trace + * @param {string} cdAttr : calcdata key + * @param {function} [fn] : optional function to apply to each array item + */ +lib.fillArray = function(traceAttr, cd, cdAttr, fn) { + fn = fn || lib.identity; + + if(Array.isArray(traceAttr)) { + for(var i = 0; i < cd.length; i++) { + cd[i][cdAttr] = fn(traceAttr[i]); + } + } +}; + +/** Handler for trace-wide vs per-point options + * + * @param {object} trace : (full) trace object + * @param {number} ptNumber : index of the point in question + * @param {string} astr : attribute string + * @param {function} [fn] : optional function to apply to each array item + * + * @return {any} + */ +lib.castOption = function(trace, ptNumber, astr, fn) { + fn = fn || lib.identity; + + var val = lib.nestedProperty(trace, astr).get(); + + if(Array.isArray(val)) { + if(Array.isArray(ptNumber) && Array.isArray(val[ptNumber[0]])) { + return fn(val[ptNumber[0]][ptNumber[1]]); + } else { + return fn(val[ptNumber]); + } + } else { + return val; + } +}; + /** Returns target as set by 'target' transform attribute * * @param {object} trace : full trace object diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js index 26c03943d6a..f07b0508061 100644 --- a/src/plot_api/helpers.js +++ b/src/plot_api/helpers.js @@ -415,7 +415,7 @@ exports.swapXYData = function(trace) { Lib.swapAttrs(trace, ['error_?.color', 'error_?.thickness', 'error_?.width']); } } - if(trace.hoverinfo) { + if(typeof trace.hoverinfo === 'string') { var hoverInfoParts = trace.hoverinfo.split('+'); for(i = 0; i < hoverInfoParts.length; i++) { if(hoverInfoParts[i] === 'x') hoverInfoParts[i] = 'y'; diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 5109c15ba24..0c88857c714 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -156,6 +156,7 @@ exports.findArrayAttributes = function(trace) { return stack.join('.'); } + exports.crawl(baseAttributes, callback); exports.crawl(trace._module.attributes, callback); if(trace.transforms) { diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 54066762e8a..43ce263b79d 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -74,6 +74,7 @@ module.exports = { role: 'info', flags: ['x', 'y', 'z', 'text', 'name'], extras: ['all', 'none', 'skip'], + arrayOk: true, dflt: 'all', description: [ 'Determines which trace information appear on hover.', diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 68052f8da55..65e854f5e96 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -623,8 +623,11 @@ proto.draw = function() { // also it's important to copy, otherwise data is lost by the time event data is read this.emitPointAction(nextSelection, 'plotly_hover'); - var hoverinfo = selection.hoverinfo; - if(hoverinfo !== 'all') { + var trace = this.fullData[selection.trace.index] || {}; + var ptNumber = selection.pointIndex; + var hoverinfo = Fx.castHoverinfo(trace, fullLayout, ptNumber); + + if(hoverinfo && hoverinfo !== 'all') { var parts = hoverinfo.split('+'); if(parts.indexOf('x') === -1) selection.traceCoord[0] = undefined; if(parts.indexOf('y') === -1) selection.traceCoord[1] = undefined; @@ -633,9 +636,6 @@ proto.draw = function() { if(parts.indexOf('name') === -1) selection.name = undefined; } - var trace = this.fullData[selection.trace.index] || {}; - var ptNumber = selection.pointIndex; - Fx.loneHover({ x: selection.screenCoord[0], y: selection.screenCoord[1], diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index fae61f66832..db6c2e7dbbb 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -67,8 +67,8 @@ function render(scene) { if(lastPicked !== null) { var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate); trace = lastPicked.data; - var hoverinfo = trace.hoverinfo; var ptNumber = selection.index; + var hoverinfo = Fx.castHoverinfo(trace, scene.fullLayout, ptNumber); var xVal = formatter('xaxis', selection.traceCoordinate[0]), yVal = formatter('yaxis', selection.traceCoordinate[1]), diff --git a/src/plots/plots.js b/src/plots/plots.js index c4f18cec787..ae79bcf7af5 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -865,9 +865,6 @@ plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInInde var _module = plots.getModule(traceOut); traceOut._module = _module; - // gets overwritten in pie, geo and ternary modules - coerce('hoverinfo', (layout._dataLength === 1) ? 'x+y+z+text' : undefined); - if(plots.traceIs(traceOut, 'showLegend')) { coerce('showlegend'); coerce('legendgroup'); @@ -880,7 +877,10 @@ plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInInde // TODO add per-base-plot-module trace defaults step - if(_module) _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); + if(_module) { + _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); + Lib.coerceHoverinfo(traceIn, traceOut, layout); + } if(!plots.traceIs(traceOut, 'noOpacity')) coerce('opacity'); diff --git a/src/traces/choropleth/defaults.js b/src/traces/choropleth/defaults.js index d4dbfa057d5..9b279725c69 100644 --- a/src/traces/choropleth/defaults.js +++ b/src/traces/choropleth/defaults.js @@ -48,6 +48,4 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} ); - - coerce('hoverinfo', (layout._dataLength === 1) ? 'location+z+text' : undefined); }; diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 26f68f03d0a..7984ed7ce73 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -47,8 +47,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); coerce('hovertext'); - coerce('hoverinfo', (layout._dataLength === 1) ? 'label+text+value+percent' : undefined); - if(textInfo && textInfo !== 'none') { var textPosition = coerce('textposition'), hasBoth = Array.isArray(textPosition) || textPosition === 'auto', diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 322618e271d..0d12b8f5b67 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -89,9 +89,9 @@ module.exports = function plot(gd, cdpie) { evt.originalEvent = d3.event; // in case fullLayout or fullData has changed without a replot - var fullLayout2 = gd._fullLayout, - trace2 = gd._fullData[trace.index], - hoverinfo = trace2.hoverinfo; + var fullLayout2 = gd._fullLayout; + var trace2 = gd._fullData[trace.index]; + var hoverinfo = Fx.castHoverinfo(trace2, fullLayout2, pt.i); if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name'; @@ -124,8 +124,6 @@ module.exports = function plot(gd, cdpie) { if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)); if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); - var hoverLabelOpts = trace2.hoverlabel; - Fx.loneHover({ x0: hoverCenterX - rInscribed * cd0.r, x1: hoverCenterX + rInscribed * cd0.r, @@ -133,11 +131,11 @@ module.exports = function plot(gd, cdpie) { text: thisText.join('
'), name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined, idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right', - color: pt.hbg || hoverLabelOpts.bgcolor || pt.color, - borderColor: pt.hbc || hoverLabelOpts.bordercolor, - fontFamily: pt.htf || hoverLabelOpts.font.family, - fontSize: pt.hts || hoverLabelOpts.font.size, - fontColor: pt.htc || hoverLabelOpts.font.color + color: Fx.castHoverOption(trace, pt.i, 'bgcolor') || pt.color, + borderColor: Fx.castHoverOption(trace, pt.i, 'bordercolor'), + fontFamily: Fx.castHoverOption(trace, pt.i, 'font.family'), + fontSize: Fx.castHoverOption(trace, pt.i, 'font.size'), + fontColor: Fx.castHoverOption(trace, pt.i, 'font.color') }, { container: fullLayout2._hoverlayer.node(), outerContainer: fullLayout2._paper.node() diff --git a/src/traces/sankey/defaults.js b/src/traces/sankey/defaults.js index adfcf5db1ae..4dc59a5f1ab 100644 --- a/src/traces/sankey/defaults.js +++ b/src/traces/sankey/defaults.js @@ -15,7 +15,6 @@ var Color = require('../../components/color'); var tinycolor = require('tinycolor2'); module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } @@ -45,8 +44,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout 'rgba(0, 0, 0, 0.2)'; })); - coerce('hoverinfo', layout._dataLength === 1 ? 'label+text+value+percent' : undefined); - coerce('domain.x'); coerce('domain.y'); coerce('orientation'); diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index a3cc8c7f348..06f73f20fe2 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -117,7 +117,7 @@ module.exports = { textfont: scatterAttrs.textfont, textposition: scatterAttrs.textposition, hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { - flags: ['a', 'b', 'c', 'text', 'name'] + flags: ['a', 'b', 'text', 'name'] }), hoveron: scatterAttrs.hoveron, }; diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index 88b70930c66..40415bef2c3 100644 --- a/src/traces/scattercarpet/defaults.js +++ b/src/traces/scattercarpet/defaults.js @@ -82,8 +82,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+text' : undefined); - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index 54b90bbedad..0fe802cfcf8 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -52,8 +52,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } - - coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+location+text' : undefined); }; function handleLonLatLocDefaults(traceIn, traceOut, coerce) { diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index f7a51c65e95..4ff10674e2e 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -63,8 +63,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(traceOut.fill !== 'none') { handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); } - - coerce('hoverinfo', (layout._dataLength === 1) ? 'lon+lat+text' : undefined); }; function handleLonLatDefaults(traceIn, traceOut, coerce) { diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 1fd088b06d7..5a3b8dd3e4f 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -95,8 +95,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce); } - coerce('hoverinfo', (layout._dataLength === 1) ? 'a+b+c+text' : undefined); - if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 37067efedef..95b7ee5f6f1 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -145,6 +145,25 @@ describe('Test hover and click interactions', function() { expect(text.style('fill')).toEqual(expected.fontColor, 'font.color'); } + function assertHoveLabelContent(expected) { + var label = expected.label; + + if(label === undefined) return; + + var g = d3.select('.hovertext'); + + if(label === null) { + expect(g.size()).toBe(0); + } else { + var lines = g.selectAll('text.nums'); + + expect(lines.size()).toBe(label.length); + lines.each(function(_, i) { + expect(d3.select(this).text()).toEqual(label[i]); + }); + } + } + // returns basic hover/click/unhover runner for one xy position function makeRunner(pos, expected, opts) { opts = opts || {}; @@ -162,6 +181,7 @@ describe('Test hover and click interactions', function() { .then(function(eventData) { assertEventData(eventData, expected); assertHoverLabelStyle(d3.select('g.hovertext'), expected); + assertHoveLabelContent(expected); }) .then(_click) .then(function(eventData) { @@ -196,6 +216,7 @@ describe('Test hover and click interactions', function() { color: 'yellow' } }; + _mock.data[0].hoverinfo = _mock.data[0].x.map(function(_, i) { return i % 2 ? 'y' : 'x'; }); _mock.data[0].hoverlabel = { bgcolor: 'blue', bordercolor: _mock.data[0].x.map(function(_, i) { return i % 2 ? 'red' : 'green'; }) @@ -204,6 +225,7 @@ describe('Test hover and click interactions', function() { var run = makeRunner([655, 317], { x: 15.772, y: 0.387, + label: ['0.387'], curveNumber: 0, pointNumber: 33, bgColor: 'rgb(0, 0, 255)', diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 4372ccf6392..18eb06b8358 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -180,7 +180,23 @@ describe('Test gl3d plots', function() { .then(_hover) .then(function() { assertHoverLabelStyle('rgb(0, 128, 0)', 'rgb(255, 255, 0)', 20, 'Roboto', 'rgb(0, 255, 255)'); + + return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'y', null]]); }) + .then(_hover) + .then(function() { + var label = d3.selectAll('g.hovertext'); + + expect(label.size()).toEqual(1); + expect(label.select('text').text()).toEqual('c'); + + return Plotly.restyle(gd, 'hoverinfo', [[null, null, 'dont+know', null]]); + }) + .then(_hover) + .then(function() { + assertHoverText('x: 二 6, 2017', 'y: c', 'z: 100k', 'Clementine'); + }) + .catch(fail) .then(done); }); @@ -207,6 +223,11 @@ describe('Test gl3d plots', function() { assertHoverLabelStyle('rgb(68, 68, 68)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'); Plotly.restyle(gd, { + 'hoverinfo': [[ + ['all', 'all', 'all'], + ['all', 'all', 'y'], + ['all', 'all', 'all'] + ]], 'hoverlabel.bgcolor': 'white', 'hoverlabel.font.size': 9, 'hoverlabel.font.color': [[ @@ -219,6 +240,11 @@ describe('Test gl3d plots', function() { .then(_hover) .then(function() { assertHoverLabelStyle('rgb(255, 255, 255)', 'rgb(68, 68, 68)', 9, 'Arial', 'rgb(0, 255, 255)'); + + var label = d3.selectAll('g.hovertext'); + + expect(label.size()).toEqual(1); + expect(label.select('text').text()).toEqual('2'); }) .then(done); }); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 694156e6339..00b99793c63 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -154,7 +154,7 @@ describe('hover info', function() { expect(d3.selectAll('g.axistext').size()).toEqual(1); expect(d3.selectAll('g.hovertext').size()).toEqual(1); expect(d3.selectAll('g.axistext').select('text').html()).toEqual('0.388'); - expect(d3.selectAll('g.hovertext').select('text').selectAll('tspan').size()).toEqual(2); + expect(d3.selectAll('g.hovertext').select('text.nums').selectAll('tspan').size()).toEqual(2); expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][0].innerHTML).toEqual('1'); expect(d3.selectAll('g.hovertext').selectAll('tspan')[0][1].innerHTML).toEqual('hover text'); }); @@ -1034,14 +1034,18 @@ describe('Test hover label custom styling:', function() { function assertLabel(className, expectation) { var g = d3.select('g.' + className); - var path = g.select('path'); - expect(path.style('fill')).toEqual(expectation.path[0], 'bgcolor'); - expect(path.style('stroke')).toEqual(expectation.path[1], 'bordercolor'); - - var text = g.select({hovertext: 'text.nums', axistext: 'text'}[className]); - expect(parseInt(text.style('font-size'))).toEqual(expectation.text[0], 'font.size'); - expect(text.style('font-family').split(',')[0]).toEqual(expectation.text[1], 'font.family'); - expect(text.style('fill')).toEqual(expectation.text[2], 'font.color'); + if(expectation === null) { + expect(g.size()).toBe(0); + } else { + var path = g.select('path'); + expect(path.style('fill')).toEqual(expectation.path[0], 'bgcolor'); + expect(path.style('stroke')).toEqual(expectation.path[1], 'bordercolor'); + + var text = g.select({hovertext: 'text.nums', axistext: 'text'}[className]); + expect(parseInt(text.style('font-size'))).toEqual(expectation.text[0], 'font.size'); + expect(text.style('font-family').split(',')[0]).toEqual(expectation.text[1], 'font.family'); + expect(text.style('fill')).toEqual(expectation.text[2], 'font.color'); + } } function assertPtLabel(expectation) { @@ -1112,8 +1116,41 @@ describe('Test hover label custom styling:', function() { text: [13, 'Arial', 'rgb(255, 255, 255)'] }); + // test arrayOk case + return Plotly.restyle(gd, 'hoverinfo', [['skip', 'name', 'x']]); + }) + .then(function() { + _hover(gd, { xval: gd._fullData[0].x[0] }); + + assertPtLabel(null); + assertCommonLabel(null); + }) + .then(function() { + _hover(gd, { xval: gd._fullData[0].x[1] }); + + assertPtLabel({ + path: ['rgb(255, 255, 255)', 'rgb(68, 68, 68)'], + text: [20, 'Arial', 'rgb(0, 128, 0)'] + }); + assertCommonLabel(null); + }) + .then(function() { + _hover(gd, { xval: gd._fullData[0].x[2] }); + + assertPtLabel(null); + assertCommonLabel({ + path: ['rgb(255, 255, 255)', 'rgb(255, 255, 255)'], + text: [13, 'Arial', 'rgb(255, 255, 255)'] + }); + // test base case - return Plotly.update(gd, { hoverlabel: null }, { hoverlabel: null }); + return Plotly.update(gd, { + hoverlabel: null, + // all these items should be display as 'all' + hoverinfo: [['i+dont+what+im+doing', null, undefined]] + }, { + hoverlabel: null + }); }) .then(function() { _hover(gd, { xval: gd._fullData[0].x[0] }); @@ -1150,6 +1187,44 @@ describe('Test hover label custom styling:', function() { path: ['rgb(68, 68, 68)', 'rgb(255, 255, 255)'], text: [13, 'Arial', 'rgb(255, 255, 255)'] }); + + // test insufficient arrayOk case + return Plotly.restyle(gd, 'hoverinfo', [['none']]); + }) + .then(function() { + expect(gd.calcdata[0].map(function(o) { return o.hi; })).toEqual( + ['none', 'x+y+z+text', 'x+y+z+text'], + 'should fill calcdata item with correct default' + ); + + _hover(gd, { xval: gd._fullData[0].x[0] }); + + assertPtLabel(null); + assertCommonLabel(null); + }) + .then(function() { + _hover(gd, { xval: gd._fullData[0].x[1] }); + + assertPtLabel({ + path: ['rgb(0, 0, 0)', 'rgb(255, 255, 255)'], + text: [13, 'Arial', 'rgb(255, 255, 255)'] + }); + assertCommonLabel({ + path: ['rgb(68, 68, 68)', 'rgb(255, 255, 255)'], + text: [13, 'Arial', 'rgb(255, 255, 255)'] + }); + }) + .then(function() { + _hover(gd, { xval: gd._fullData[0].x[2] }); + + assertPtLabel({ + path: ['rgb(0, 255, 255)', 'rgb(68, 68, 68)'], + text: [13, 'Arial', 'rgb(68, 68, 68)'] + }); + assertCommonLabel({ + path: ['rgb(68, 68, 68)', 'rgb(255, 255, 255)'], + text: [13, 'Arial', 'rgb(255, 255, 255)'] + }); }) .catch(fail) .then(done); diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js index 464ece53993..1a28dcb6dab 100644 --- a/test/jasmine/tests/hover_pie_test.js +++ b/test/jasmine/tests/hover_pie_test.js @@ -242,9 +242,9 @@ describe('pie hovering', function() { assertLabel(['4', 'SUP', '5', '33.3%']); return Plotly.restyle(gd, { - 'hoverlabel.bgcolor': [['red', 'green', 'blue']], + 'hoverlabel.bgcolor': [['red', 'green', 'blue', 'yellow', 'red']], 'hoverlabel.bordercolor': 'yellow', - 'hoverlabel.font.size': [[15, 20, 30]], + 'hoverlabel.font.size': [[15, 20, 30, 20, 15]], 'hoverlabel.font.family': 'Roboto', 'hoverlabel.font.color': 'blue' }); @@ -255,6 +255,18 @@ describe('pie hovering', function() { ['4', 'SUP', '5', '33.3%'], ['rgb(255, 0, 0)', 'rgb(255, 255, 0)', 15, 'Roboto', 'rgb(0, 0, 255)'] ); + + return Plotly.restyle(gd, 'hoverinfo', [[null, null, null, null, 'label+percent']]); + }) + .then(_hover) + .then(function() { + assertLabel(['4', '33.3%']); + + return Plotly.restyle(gd, 'hoverinfo', [[null, null, null, null, 'dont+know+what+im-doing']]); + }) + .then(_hover) + .then(function() { + assertLabel(['4', 'SUP', '5', '33.3%']); }) .catch(fail) .then(done); diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index 1193cfe2c2c..34c5e764370 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -1,4 +1,5 @@ var Plotly = require('@lib'); +var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); var ScatterTernary = require('@src/traces/scatterternary'); @@ -135,28 +136,33 @@ describe('scatterternary defaults', function() { expect(traceOut.b).toEqual([1]); expect(traceOut.c).toEqual([1]); }); + it('should include \'name\' in \'hoverinfo\' default if multi trace graph', function() { traceIn = { + type: 'scatterternary', a: [1, 2, 3], b: [1, 2, 3], c: [1, 2, 3] }; - layout._dataLength = 2; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.hoverinfo).toBe('all'); + var gd = {data: [traceIn, {}]}; + Plots.supplyDefaults(gd); + + expect(gd._fullData[0].hoverinfo).toBe('all'); }); it('should not include \'name\' in \'hoverinfo\' default if single trace graph', function() { traceIn = { + type: 'scatterternary', a: [1, 2, 3], b: [1, 2, 3], c: [1, 2, 3] }; - layout._dataLength = 1; - supplyDefaults(traceIn, traceOut, defaultColor, layout); - expect(traceOut.hoverinfo).toBe('a+b+c+text'); + var gd = {data: [traceIn]}; + Plots.supplyDefaults(gd); + + expect(gd._fullData[0].hoverinfo).toBe('a+b+c+text'); }); it('should correctly assign \'hoveron\' default', function() { diff --git a/test/jasmine/tests/transform_filter_test.js b/test/jasmine/tests/transform_filter_test.js index 71c7748c5aa..d5bfe093c35 100644 --- a/test/jasmine/tests/transform_filter_test.js +++ b/test/jasmine/tests/transform_filter_test.js @@ -262,6 +262,25 @@ describe('filter transforms calc:', function() { expect(out[0].marker.color).toEqual([0.3, 0.3, 0.4]); }); + it('filters should handle array on base trace attributes', function() { + var out = _transform([Lib.extendDeep({}, base, { + hoverinfo: ['x', 'y', 'text', 'name', 'none', 'skip', 'all'], + hoverlabel: { + bgcolor: ['red', 'green', 'blue', 'black', 'yellow', 'cyan', 'pink'], + }, + transforms: [{ + type: 'filter', + operation: '>', + value: 0 + }] + })]); + + expect(out[0].x).toEqual([1, 2, 3]); + expect(out[0].y).toEqual([2, 3, 1]); + expect(out[0].hoverinfo).toEqual(['none', 'skip', 'all']); + expect(out[0].hoverlabel.bgcolor).toEqual(['yellow', 'cyan', 'pink']); + }); + it('filters should skip if *enabled* is false', function() { var out = _transform([Lib.extendDeep({}, base, { transforms: [{