Skip to content
34 changes: 25 additions & 9 deletions src/components/fx/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
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
55 changes: 55 additions & 0 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,13 +350,67 @@ 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);
for(var i = 0; i < imax; i++) cd[i][cdAttr] = traceAttr[i];
}
};

/** 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
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
Loading