Skip to content

Commit c53ae17

Browse files
authored
Merge pull request #1582 from plotly/hoverlabel-custom
Custom trace hover labels
2 parents 9d149c3 + 3f52a89 commit c53ae17

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2674
-1590
lines changed

src/components/annotations/annotation_defaults.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
var Lib = require('../../lib');
1313
var Color = require('../color');
1414
var Axes = require('../../plots/cartesian/axes');
15-
var constants = require('../../plots/cartesian/constants');
1615

1716
var attributes = require('./attributes');
1817

@@ -113,14 +112,21 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
113112
}
114113

115114
var hoverText = coerce('hovertext');
115+
var globalHoverLabel = fullLayout.hoverlabel || {};
116+
116117
if(hoverText) {
117-
var hoverBG = coerce('hoverlabel.bgcolor',
118-
Color.opacity(bgColor) ? Color.rgb(bgColor) : Color.defaultLine);
119-
var hoverBorder = coerce('hoverlabel.bordercolor', Color.contrast(hoverBG));
118+
var hoverBG = coerce('hoverlabel.bgcolor', globalHoverLabel.bgcolor ||
119+
(Color.opacity(bgColor) ? Color.rgb(bgColor) : Color.defaultLine)
120+
);
121+
122+
var hoverBorder = coerce('hoverlabel.bordercolor', globalHoverLabel.bordercolor ||
123+
Color.contrast(hoverBG)
124+
);
125+
120126
Lib.coerceFont(coerce, 'hoverlabel.font', {
121-
family: constants.HOVERFONT,
122-
size: constants.HOVERFONTSIZE,
123-
color: hoverBorder
127+
family: globalHoverLabel.font.family,
128+
size: globalHoverLabel.font.size,
129+
color: globalHoverLabel.font.color || hoverBorder
124130
});
125131
}
126132
coerce('captureevents', !!hoverText);

src/components/annotations/draw.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ var Plotly = require('../../plotly');
1515
var Plots = require('../../plots/plots');
1616
var Lib = require('../../lib');
1717
var Axes = require('../../plots/cartesian/axes');
18-
var Fx = require('../../plots/cartesian/graph_interact');
1918
var Color = require('../color');
2019
var Drawing = require('../drawing');
20+
var Fx = require('../fx');
2121
var svgTextUtils = require('../../lib/svg_text_utils');
2222
var setCursor = require('../../lib/setcursor');
2323
var dragElement = require('../dragelement');

src/components/fx/attributes.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var extendFlat = require('../../lib/extend').extendFlat;
12+
var fontAttrs = require('../../plots/font_attributes');
13+
14+
module.exports = {
15+
hoverlabel: {
16+
bgcolor: {
17+
valType: 'color',
18+
role: 'style',
19+
arrayOk: true,
20+
description: [
21+
'Sets the background color of the hover labels for this trace'
22+
].join(' ')
23+
},
24+
bordercolor: {
25+
valType: 'color',
26+
role: 'style',
27+
arrayOk: true,
28+
description: [
29+
'Sets the border color of the hover labels for this trace.'
30+
].join(' ')
31+
},
32+
font: {
33+
family: extendFlat({}, fontAttrs.family, { arrayOk: true }),
34+
size: extendFlat({}, fontAttrs.size, { arrayOk: true }),
35+
color: extendFlat({}, fontAttrs.color, { arrayOk: true })
36+
}
37+
}
38+
};

src/components/fx/calc.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var Registry = require('../../registry');
13+
14+
module.exports = function calc(gd) {
15+
var calcdata = gd.calcdata;
16+
17+
for(var i = 0; i < calcdata.length; i++) {
18+
var cd = calcdata[i];
19+
var trace = cd[0].trace;
20+
21+
if(!trace.hoverlabel) continue;
22+
23+
var mergeFn = Registry.traceIs(trace, '2dMap') ? paste : Lib.mergeArray;
24+
25+
mergeFn(trace.hoverlabel.bgcolor, cd, 'hbg');
26+
mergeFn(trace.hoverlabel.bordercolor, cd, 'hbc');
27+
mergeFn(trace.hoverlabel.font.size, cd, 'hts');
28+
mergeFn(trace.hoverlabel.font.color, cd, 'htc');
29+
mergeFn(trace.hoverlabel.font.family, cd, 'htf');
30+
}
31+
};
32+
33+
function paste(traceAttr, cd, cdAttr) {
34+
if(Array.isArray(traceAttr)) {
35+
cd[0][cdAttr] = traceAttr;
36+
}
37+
}

src/components/fx/click.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Registry = require('../../registry');
12+
13+
module.exports = function click(gd, evt) {
14+
var annotationsDone = Registry.getComponentMethod('annotations', 'onClick')(gd, gd._hoverdata);
15+
16+
function emitClick() { gd.emit('plotly_click', {points: gd._hoverdata, event: evt}); }
17+
18+
if(gd._hoverdata && evt && evt.target) {
19+
if(annotationsDone && annotationsDone.then) {
20+
annotationsDone.then(emitClick);
21+
}
22+
else emitClick();
23+
24+
// why do we get a double event without this???
25+
if(evt.stopImmediatePropagation) evt.stopImmediatePropagation();
26+
}
27+
};

src/components/fx/constants.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = {
12+
// max pixels away from mouse to allow a point to highlight
13+
MAXDIST: 20,
14+
15+
// hover labels for multiple horizontal bars get tilted by this angle
16+
YANGLE: 60,
17+
18+
// size and display constants for hover text
19+
20+
// pixel size of hover arrows
21+
HOVERARROWSIZE: 6,
22+
// pixels padding around text
23+
HOVERTEXTPAD: 3,
24+
// hover font
25+
HOVERFONTSIZE: 13,
26+
HOVERFONT: 'Arial, sans-serif',
27+
28+
// minimum time (msec) between hover calls
29+
HOVERMINTIME: 50
30+
};

src/components/fx/defaults.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var attributes = require('./attributes');
13+
var handleHoverLabelDefaults = require('./hoverlabel_defaults');
14+
15+
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
16+
function coerce(attr, dflt) {
17+
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
18+
}
19+
20+
handleHoverLabelDefaults(traceIn, traceOut, coerce, layout.hoverlabel);
21+
};

src/components/fx/helpers.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var constants = require('./constants');
12+
13+
// look for either subplot or xaxis and yaxis attributes
14+
exports.getSubplot = function getSubplot(trace) {
15+
return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo;
16+
};
17+
18+
// convenience functions for mapping all relevant axes
19+
exports.flat = function flat(subplots, v) {
20+
var out = new Array(subplots.length);
21+
for(var i = 0; i < subplots.length; i++) {
22+
out[i] = v;
23+
}
24+
return out;
25+
};
26+
27+
exports.p2c = function p2c(axArray, v) {
28+
var out = new Array(axArray.length);
29+
for(var i = 0; i < axArray.length; i++) {
30+
out[i] = axArray[i].p2c(v);
31+
}
32+
return out;
33+
};
34+
35+
exports.getDistanceFunction = function getDistanceFunction(mode, dx, dy, dxy) {
36+
if(mode === 'closest') return dxy || quadrature(dx, dy);
37+
return mode === 'x' ? dx : dy;
38+
};
39+
40+
exports.getClosest = function getClosest(cd, distfn, pointData) {
41+
// do we already have a point number? (array mode only)
42+
if(pointData.index !== false) {
43+
if(pointData.index >= 0 && pointData.index < cd.length) {
44+
pointData.distance = 0;
45+
}
46+
else pointData.index = false;
47+
}
48+
else {
49+
// apply the distance function to each data point
50+
// this is the longest loop... if this bogs down, we may need
51+
// to create pre-sorted data (by x or y), not sure how to
52+
// do this for 'closest'
53+
for(var i = 0; i < cd.length; i++) {
54+
var newDistance = distfn(cd[i]);
55+
if(newDistance <= pointData.distance) {
56+
pointData.index = i;
57+
pointData.distance = newDistance;
58+
}
59+
}
60+
}
61+
return pointData;
62+
};
63+
64+
// for bar charts and others with finite-size objects: you must be inside
65+
// it to see its hover info, so distance is infinite outside.
66+
// But make distance inside be at least 1/4 MAXDIST, and a little bigger
67+
// for bigger bars, to prioritize scatter and smaller bars over big bars
68+
//
69+
// note that for closest mode, two inbox's will get added in quadrature
70+
// args are (signed) difference from the two opposite edges
71+
// count one edge as in, so that over continuous ranges you never get a gap
72+
exports.inbox = function inbox(v0, v1) {
73+
if(v0 * v1 < 0 || v0 === 0) {
74+
return constants.MAXDIST * (0.6 - 0.3 / Math.max(3, Math.abs(v0 - v1)));
75+
}
76+
return Infinity;
77+
};
78+
79+
function quadrature(dx, dy) {
80+
return function(di) {
81+
var x = dx(di),
82+
y = dy(di);
83+
return Math.sqrt(x * x + y * y);
84+
};
85+
}

0 commit comments

Comments
 (0)