Skip to content
22 changes: 19 additions & 3 deletions src/components/fx/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,29 @@ 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 mergeFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.mergeArray;

mergeFn(trace.hoverinfo, cd, 'hi', makeCoerceHoverInfo(trace));

if(!trace.hoverlabel) continue;

mergeFn(trace.hoverlabel.bgcolor, cd, 'hbg');
mergeFn(trace.hoverlabel.bordercolor, cd, 'hbc');
mergeFn(trace.hoverlabel.font.size, cd, 'hts');
Expand All @@ -30,8 +44,10 @@ module.exports = function calc(gd) {
}
};

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);
}
}
57 changes: 30 additions & 27 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 ? '<br>' : '') + d.text;
if(d.text && !Array.isArray(d.text)) {
text += (text ? '<br>' : '') + d.text;
}

// if 'text' is empty at this point,
// put 'name' in main label and don't show secondary label
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
24 changes: 12 additions & 12 deletions src/components/fx/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
}
32 changes: 31 additions & 1 deletion src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Copy link
Contributor Author

@etpinard etpinard Jun 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope nobody minds this abstraction. The hoverinfo dflt is ✨ : special ✨ , so I think it deserves its own coerce function.

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];

Expand Down
41 changes: 39 additions & 2 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -349,10 +350,46 @@ lib.noneOrAll = function(containerIn, containerOut, attrList) {
}
};

lib.mergeArray = function(traceAttr, cd, cdAttr) {
/** merge data array into calcdata 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.mergeArray = function(traceAttr, cd, cdAttr, fn) {
fn = fn || lib.identity;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐎 I wonder whether it would be worth avoiding fn entirely if it's not provided? Either
cd[i][cdAttr] = fn ? fn(traceAttr[i]) : traceAttr[i];
or even

if(fn) {
    for(i = 0; i < cd.length; i++) cd[i][cdAttr] = fn(traceAttr[i]);
}
else {
    for(i = 0; i < cd.length; i++) cd[i][cdAttr] = traceAttr[i];
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't make that much of a difference (at least in Chrome 58)

image

https://gist.github.com/etpinard/4a5c7983a619efb4c98cb927529b9af6

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, thanks for checking.


if(Array.isArray(traceAttr)) {
var imax = Math.min(traceAttr.length, cd.length);
for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i];
for(var i = 0; i < imax; 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;
}
};

Expand Down
2 changes: 1 addition & 1 deletion src/plot_api/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions src/plot_api/plot_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ exports.findArrayAttributes = function(trace) {
return stack.join('.');
}

exports.crawl(baseAttributes, callback);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha nice, that's a lot simpler than I was afraid it might be!

exports.crawl(trace._module.attributes, callback);

if(trace.transforms) {
Expand Down
1 change: 1 addition & 0 deletions src/plots/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
10 changes: 5 additions & 5 deletions src/plots/gl2d/scene2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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],
Expand Down
2 changes: 1 addition & 1 deletion src/plots/gl3d/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down
8 changes: 4 additions & 4 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');

Expand Down
2 changes: 0 additions & 2 deletions src/traces/choropleth/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
2 changes: 0 additions & 2 deletions src/traces/pie/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading