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: [{