Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 58966ed

Browse filesBrowse files
committed
annotation hover text
1 parent b4318ad commit 58966ed
Copy full SHA for 58966ed

File tree

8 files changed

+340
-81
lines changed
Filter options

8 files changed

+340
-81
lines changed

‎build/plotcss.js

Copy file name to clipboardExpand all lines: build/plotcss.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var rules = {
1414
"X svg a:hover": "fill:#3c6dc5;",
1515
"X .main-svg": "position:absolute;top:0;left:0;pointer-events:none;",
1616
"X .main-svg .draglayer": "pointer-events:all;",
17+
"X .cursor-default": "cursor:default;",
1718
"X .cursor-pointer": "cursor:pointer;",
1819
"X .cursor-crosshair": "cursor:crosshair;",
1920
"X .cursor-move": "cursor:move;",

‎src/components/annotations/annotation_defaults.js

Copy file name to clipboardExpand all lines: src/components/annotations/annotation_defaults.js
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
var Lib = require('../../lib');
1313
var Color = require('../color');
1414
var Axes = require('../../plots/cartesian/axes');
15+
var constants = require('../../plots/cartesian/constants');
1516

1617
var attributes = require('./attributes');
1718

@@ -30,7 +31,7 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
3031
if(!(visible || clickToShow)) return annOut;
3132

3233
coerce('opacity');
33-
coerce('bgcolor');
34+
var bgColor = coerce('bgcolor');
3435

3536
var borderColor = coerce('bordercolor'),
3637
borderOpacity = Color.opacity(borderColor);
@@ -108,5 +109,17 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
108109
annOut._yclick = (yClick === undefined) ? annOut.y : yClick;
109110
}
110111

112+
var hoverText = coerce('hovertext');
113+
if(hoverText) {
114+
var hoverBG = coerce('hoverlabel.bgcolor',
115+
Color.opacity(bgColor) ? Color.rgb(bgColor) : Color.defaultLine);
116+
var hoverBorder = coerce('hoverlabel.bordercolor', Color.contrast(hoverBG));
117+
Lib.coerceFont(coerce, 'hoverlabel.font', {
118+
family: constants.HOVERFONT,
119+
size: constants.HOVERFONTSIZE,
120+
color: hoverBorder
121+
});
122+
}
123+
111124
return annOut;
112125
};

‎src/components/annotations/attributes.js

Copy file name to clipboardExpand all lines: src/components/annotations/attributes.js
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,41 @@ module.exports = {
378378
'is `yclick` rather than the annotation\'s `y` value.'
379379
].join(' ')
380380
},
381+
hovertext: {
382+
valType: 'string',
383+
role: 'info',
384+
description: [
385+
'Sets text to appear when hovering over this annotation.',
386+
'If omitted or blank, no hover label will appear.'
387+
].join(' ')
388+
},
389+
hoverlabel: {
390+
bgcolor: {
391+
valType: 'color',
392+
role: 'style',
393+
description: [
394+
'Sets the background color of the hover label.',
395+
'By default uses the annotation\'s `bgcolor` made opaque,',
396+
'or white if it was transparent.'
397+
].join(' ')
398+
},
399+
bordercolor: {
400+
valType: 'color',
401+
role: 'style',
402+
description: [
403+
'Sets the border color of the hover label.',
404+
'By default uses either dark grey or white, for maximum',
405+
'contrast with `hoverlabel.bgcolor`.'
406+
].join(' ')
407+
},
408+
font: extendFlat({}, fontAttrs, {
409+
description: [
410+
'Sets the hover label text font.',
411+
'By default uses the global hover font and size,',
412+
'with color from `hoverlabel.bordercolor`.'
413+
].join(' ')
414+
})
415+
},
381416

382417
_deprecated: {
383418
ref: {

‎src/components/annotations/draw.js

Copy file name to clipboardExpand all lines: src/components/annotations/draw.js
+38-7Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ 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');
1819
var Color = require('../color');
1920
var Drawing = require('../drawing');
2021
var svgTextUtils = require('../../lib/svg_text_utils');
@@ -96,7 +97,16 @@ function drawOne(gd, index) {
9697
var annGroup = fullLayout._infolayer.append('g')
9798
.classed('annotation', true)
9899
.attr('data-index', String(index))
99-
.style('opacity', options.opacity)
100+
.style('opacity', options.opacity);
101+
102+
// another group for text+background so that they can rotate together
103+
var annTextGroup = annGroup.append('g')
104+
.classed('annotation-text-g', true)
105+
.attr('data-index', String(index));
106+
107+
var annTextGroupInner = annTextGroup.append('g')
108+
.style('pointer-events', 'all')
109+
.call(setCursor, 'default')
100110
.on('click', function() {
101111
gd._dragging = false;
102112
gd.emit('plotly_clickannotation', {
@@ -106,12 +116,33 @@ function drawOne(gd, index) {
106116
});
107117
});
108118

109-
// another group for text+background so that they can rotate together
110-
var annTextGroup = annGroup.append('g')
111-
.classed('annotation-text-g', true)
112-
.attr('data-index', String(index));
113-
114-
var annTextGroupInner = annTextGroup.append('g');
119+
if(options.hovertext) {
120+
annTextGroupInner
121+
.on('mouseover', function() {
122+
var hoverOptions = options.hoverlabel;
123+
var hoverFont = hoverOptions.font;
124+
var bBox = this.getBoundingClientRect();
125+
var bBoxRef = gd.getBoundingClientRect();
126+
127+
Fx.loneHover({
128+
x0: bBox.left - bBoxRef.left,
129+
x1: bBox.right - bBoxRef.left,
130+
y: (bBox.top + bBox.bottom) / 2 - bBoxRef.top,
131+
text: options.hovertext,
132+
color: hoverOptions.bgcolor,
133+
borderColor: hoverOptions.bordercolor,
134+
fontFamily: hoverFont.family,
135+
fontSize: hoverFont.size,
136+
fontColor: hoverFont.color
137+
}, {
138+
container: fullLayout._hoverlayer.node(),
139+
outerContainer: fullLayout._paper.node()
140+
});
141+
})
142+
.on('mouseout', function() {
143+
Fx.loneUnhover(fullLayout._hoverlayer.node());
144+
});
145+
}
115146

116147
var borderwidth = options.borderwidth,
117148
borderpad = options.borderpad,

‎src/components/color/index.js

Copy file name to clipboardExpand all lines: src/components/color/index.js
+18-3Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ color.defaultLine = colorAttrs.defaultLine;
2020
color.lightLine = colorAttrs.lightLine;
2121
color.background = colorAttrs.background;
2222

23+
/*
24+
* tinyRGB: turn a tinycolor into an rgb string, but
25+
* unlike the built-in tinycolor.toRgbString this never includes alpha
26+
*/
2327
color.tinyRGB = function(tc) {
2428
var c = tc.toRgb();
2529
return 'rgb(' + Math.round(c.r) + ', ' +
@@ -57,12 +61,23 @@ color.combine = function(front, back) {
5761
return tinycolor(fcflat).toRgbString();
5862
};
5963

64+
/*
65+
* Create a color that contrasts with cstr.
66+
*
67+
* If cstr is a dark color, we lighten it; if it's light, we darken.
68+
*
69+
* If lightAmount / darkAmount are used, we adjust by these percentages,
70+
* otherwise we go all the way to white or black.
71+
* TODO: black is what we've always done for hover, but should it be #444 instead?
72+
*/
6073
color.contrast = function(cstr, lightAmount, darkAmount) {
6174
var tc = tinycolor(cstr);
6275

63-
var newColor = tc.isLight() ?
64-
tc.darken(darkAmount) :
65-
tc.lighten(lightAmount);
76+
if(tc.getAlpha() !== 1) tc = tinycolor(color.combine(cstr, '#fff'));
77+
78+
var newColor = tc.isDark() ?
79+
(lightAmount ? tc.lighten(lightAmount) : '#fff') :
80+
(darkAmount ? tc.darken(darkAmount) : '#000');
6681

6782
return newColor.toString();
6883
};

‎src/css/_cursor.scss

Copy file name to clipboardExpand all lines: src/css/_cursor.scss
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.cursor-default { cursor: default; }
12
.cursor-pointer { cursor: pointer; }
23
.cursor-crosshair { cursor: crosshair; }
34
.cursor-move { cursor: move; }

‎src/plots/cartesian/graph_interact.js

Copy file name to clipboardExpand all lines: src/plots/cartesian/graph_interact.js
+54-28Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
'use strict';
1111

1212
var d3 = require('d3');
13-
var tinycolor = require('tinycolor2');
1413
var isNumeric = require('fast-isnumeric');
1514

1615
var Lib = require('../../lib');
@@ -246,9 +245,7 @@ function quadrature(dx, dy) {
246245

247246
// size and display constants for hover text
248247
var HOVERARROWSIZE = constants.HOVERARROWSIZE,
249-
HOVERTEXTPAD = constants.HOVERTEXTPAD,
250-
HOVERFONTSIZE = constants.HOVERFONTSIZE,
251-
HOVERFONT = constants.HOVERFONT;
248+
HOVERTEXTPAD = constants.HOVERTEXTPAD;
252249

253250
// fx.hover: highlight data on hover
254251
// evt can be a mousemove event, or an object with data about what points
@@ -755,23 +752,36 @@ function cleanPoint(d, hovermode) {
755752
return d;
756753
}
757754

755+
/*
756+
* Draw a single hover item in a pre-existing svg container somewhere
757+
* hoverItem should have keys:
758+
* - x and y (or x0, x1, y0, and y1):
759+
* the pixel position to mark, relative to opts.container
760+
* - xLabel, yLabel, zLabel, text, and name:
761+
* info to go in the label
762+
* - color:
763+
* the background color for the label.
764+
* - idealAlign (optional):
765+
* 'left' or 'right' for which side of the x/y box to try to put this on first
766+
* - borderColor (optional):
767+
* color for the border, defaults to strongest contrast with color
768+
* - fontFamily (optional):
769+
* string, the font for this label, defaults to constants.HOVERFONT
770+
* - fontSize (optional):
771+
* the label font size, defaults to constants.HOVERFONTSIZE
772+
* - fontColor (optional):
773+
* defaults to borderColor
774+
* opts should have keys:
775+
* - bgColor:
776+
* the background color this is against, used if the trace is
777+
* non-opaque, and for the name, which goes outside the box
778+
* - container:
779+
* a dom <svg> element - must be big enough to contain the whole
780+
* hover label
781+
* - outerContainer:
782+
* TODO: what exactly is container vs outerContainer?
783+
*/
758784
fx.loneHover = function(hoverItem, opts) {
759-
// draw a single hover item in a pre-existing svg container somewhere
760-
// hoverItem should have keys:
761-
// - x and y (or x0, x1, y0, and y1):
762-
// the pixel position to mark, relative to opts.container
763-
// - xLabel, yLabel, zLabel, text, and name:
764-
// info to go in the label
765-
// - color:
766-
// the background color for the label. text & outline color will
767-
// be chosen black or white to contrast with this
768-
// opts should have keys:
769-
// - bgColor:
770-
// the background color this is against, used if the trace is
771-
// non-opaque, and for the name, which goes outside the box
772-
// - container:
773-
// a dom <svg> element - must be big enough to contain the whole
774-
// hover label
775785
var pointData = {
776786
color: hoverItem.color || Color.defaultLine,
777787
x0: hoverItem.x0 || hoverItem.x || 0,
@@ -785,6 +795,12 @@ fx.loneHover = function(hoverItem, opts) {
785795
name: hoverItem.name,
786796
idealAlign: hoverItem.idealAlign,
787797

798+
// optional extra bits of styling
799+
borderColor: hoverItem.borderColor,
800+
fontFamily: hoverItem.fontFamily,
801+
fontSize: hoverItem.fontSize,
802+
fontColor: hoverItem.fontColor,
803+
788804
// filler to make createHoverText happy
789805
trace: {
790806
index: 0,
@@ -830,6 +846,12 @@ function createHoverText(hoverData, opts) {
830846
container = opts.container,
831847
outerContainer = opts.outerContainer,
832848

849+
// opts.fontFamily/Size are used for the common label
850+
// and as defaults for each hover label, though the individual labels
851+
// can override this.
852+
fontFamily = opts.fontFamily || constants.HOVERFONT,
853+
fontSize = opts.fontSize || constants.HOVERFONTSIZE,
854+
833855
c0 = hoverData[0],
834856
xa = c0.xa,
835857
ya = c0.ya,
@@ -874,7 +896,7 @@ function createHoverText(hoverData, opts) {
874896
lpath.enter().append('path')
875897
.style({fill: Color.defaultLine, 'stroke-width': '1px', stroke: Color.background});
876898
ltext.enter().append('text')
877-
.call(Drawing.font, HOVERFONT, HOVERFONTSIZE, Color.background)
899+
.call(Drawing.font, fontFamily, fontSize, Color.background)
878900
// prohibit tex interpretation until we can handle
879901
// tex and regular text together
880902
.attr('data-notex', 1);
@@ -955,13 +977,12 @@ function createHoverText(hoverData, opts) {
955977
// trace name label (rect and text.name)
956978
g.append('rect')
957979
.call(Color.fill, Color.addOpacity(bgColor, 0.8));
958-
g.append('text').classed('name', true)
959-
.call(Drawing.font, HOVERFONT, HOVERFONTSIZE);
980+
g.append('text').classed('name', true);
960981
// trace data label (path and text.nums)
961982
g.append('path')
962983
.style('stroke-width', '1px');
963984
g.append('text').classed('nums', true)
964-
.call(Drawing.font, HOVERFONT, HOVERFONTSIZE);
985+
.call(Drawing.font, fontFamily, fontSize);
965986
});
966987
hoverLabels.exit().remove();
967988

@@ -977,8 +998,7 @@ function createHoverText(hoverData, opts) {
977998
traceColor = Color.combine(baseColor, bgColor),
978999

9791000
// find a contrasting color for border and text
980-
contrastColor = tinycolor(traceColor).getBrightness() > 128 ?
981-
'#000' : Color.background;
1001+
contrastColor = d.borderColor || Color.contrast(traceColor);
9821002

9831003
// to get custom 'name' labels pass cleanPoint
9841004
if(d.nameOverride !== undefined) d.name = d.nameOverride;
@@ -1023,7 +1043,10 @@ function createHoverText(hoverData, opts) {
10231043

10241044
// main label
10251045
var tx = g.select('text.nums')
1026-
.style('fill', contrastColor)
1046+
.call(Drawing.font,
1047+
d.fontFamily || fontFamily,
1048+
d.fontSize || fontSize,
1049+
d.fontColor || contrastColor)
10271050
.call(Drawing.setPosition, 0, 0)
10281051
.text(text)
10291052
.attr('data-notex', 1)
@@ -1036,7 +1059,10 @@ function createHoverText(hoverData, opts) {
10361059

10371060
// secondary label for non-empty 'name'
10381061
if(name && name !== text) {
1039-
tx2.style('fill', traceColor)
1062+
tx2.call(Drawing.font,
1063+
d.fontFamily || fontFamily,
1064+
d.fontSize || fontSize,
1065+
traceColor)
10401066
.text(name)
10411067
.call(Drawing.setPosition, 0, 0)
10421068
.attr('data-notex', 1)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.