Skip to content

Commit c036bf7

Browse files
committed
Merge branch 'master' into dropline
2 parents 6e243de + b32c21d commit c036bf7

File tree

15 files changed

+492
-151
lines changed

15 files changed

+492
-151
lines changed

build/plotcss.js

+1
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

+18-1
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);
@@ -82,6 +83,9 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
8283

8384
// xanchor, yanchor
8485
coerce(axLetter + 'anchor');
86+
87+
// xshift, yshift
88+
coerce(axLetter + 'shift');
8589
}
8690

8791
// if you have one coordinate you should have both
@@ -108,5 +112,18 @@ module.exports = function handleAnnotationDefaults(annIn, annOut, fullLayout, op
108112
annOut._yclick = (yClick === undefined) ? annOut.y : yClick;
109113
}
110114

115+
var hoverText = coerce('hovertext');
116+
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));
120+
Lib.coerceFont(coerce, 'hoverlabel.font', {
121+
family: constants.HOVERFONT,
122+
size: constants.HOVERFONTSIZE,
123+
color: hoverBorder
124+
});
125+
}
126+
coerce('captureevents', !!hoverText);
127+
111128
return annOut;
112129
};

src/components/annotations/attributes.js

+68-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ module.exports = {
180180
description: [
181181
'Sets a distance, in pixels, to move the arrowhead away from the',
182182
'position it is pointing at, for example to point at the edge of',
183-
'a marker independent of zoom.'
183+
'a marker independent of zoom. Note that this shortens the arrow',
184+
'from the `ax` / `ay` vector, in contrast to `xshift` / `yshift`',
185+
'which moves everything by this amount.'
184186
].join(' ')
185187
},
186188
ax: {
@@ -292,6 +294,15 @@ module.exports = {
292294
'corresponds to the closest side.'
293295
].join(' ')
294296
},
297+
xshift: {
298+
valType: 'number',
299+
dflt: 0,
300+
role: 'style',
301+
description: [
302+
'Shifts the position of the whole annotation and arrow to the',
303+
'right (positive) or left (negative) by this many pixels.'
304+
].join(' ')
305+
},
295306
yref: {
296307
valType: 'enumerated',
297308
values: [
@@ -342,6 +353,15 @@ module.exports = {
342353
'corresponds to the closest side.'
343354
].join(' ')
344355
},
356+
yshift: {
357+
valType: 'number',
358+
dflt: 0,
359+
role: 'style',
360+
description: [
361+
'Shifts the position of the whole annotation and arrow up',
362+
'(positive) or down (negative) by this many pixels.'
363+
].join(' ')
364+
},
345365
clicktoshow: {
346366
valType: 'enumerated',
347367
values: [false, 'onoff', 'onout'],
@@ -378,6 +398,53 @@ module.exports = {
378398
'is `yclick` rather than the annotation\'s `y` value.'
379399
].join(' ')
380400
},
401+
hovertext: {
402+
valType: 'string',
403+
role: 'info',
404+
description: [
405+
'Sets text to appear when hovering over this annotation.',
406+
'If omitted or blank, no hover label will appear.'
407+
].join(' ')
408+
},
409+
hoverlabel: {
410+
bgcolor: {
411+
valType: 'color',
412+
role: 'style',
413+
description: [
414+
'Sets the background color of the hover label.',
415+
'By default uses the annotation\'s `bgcolor` made opaque,',
416+
'or white if it was transparent.'
417+
].join(' ')
418+
},
419+
bordercolor: {
420+
valType: 'color',
421+
role: 'style',
422+
description: [
423+
'Sets the border color of the hover label.',
424+
'By default uses either dark grey or white, for maximum',
425+
'contrast with `hoverlabel.bgcolor`.'
426+
].join(' ')
427+
},
428+
font: extendFlat({}, fontAttrs, {
429+
description: [
430+
'Sets the hover label text font.',
431+
'By default uses the global hover font and size,',
432+
'with color from `hoverlabel.bordercolor`.'
433+
].join(' ')
434+
})
435+
},
436+
captureevents: {
437+
valType: 'boolean',
438+
role: 'info',
439+
description: [
440+
'Determines whether the annotation text box captures mouse move',
441+
'and click events, or allows those events to pass through to data',
442+
'points in the plot that may be behind the annotation. By default',
443+
'`captureevents` is *false* unless `hovertext` is provided.',
444+
'If you use the event `plotly_clickannotation` without `hovertext`',
445+
'you must explicitly enable `captureevents`.'
446+
].join(' ')
447+
},
381448

382449
_deprecated: {
383450
ref: {

src/components/annotations/calc_autorange.js

+16-8
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,17 @@ function annAutorange(gd) {
5050
ya = Axes.getFromId(gd, ann.yref),
5151
headSize = 3 * ann.arrowsize * ann.arrowwidth || 0;
5252

53+
var headPlus, headMinus;
54+
5355
if(xa && xa.autorange) {
56+
headPlus = headSize + ann.xshift;
57+
headMinus = headSize - ann.xshift;
58+
5459
if(ann.axref === ann.xref) {
5560
// expand for the arrowhead (padded by arrowhead)
5661
Axes.expand(xa, [xa.r2c(ann.x)], {
57-
ppadplus: headSize,
58-
ppadminus: headSize
62+
ppadplus: headPlus,
63+
ppadminus: headMinus
5964
});
6065
// again for the textbox (padded by textbox)
6166
Axes.expand(xa, [xa.r2c(ann.ax)], {
@@ -65,17 +70,20 @@ function annAutorange(gd) {
6570
}
6671
else {
6772
Axes.expand(xa, [xa.r2c(ann.x)], {
68-
ppadplus: Math.max(ann._xpadplus, headSize),
69-
ppadminus: Math.max(ann._xpadminus, headSize)
73+
ppadplus: Math.max(ann._xpadplus, headPlus),
74+
ppadminus: Math.max(ann._xpadminus, headMinus)
7075
});
7176
}
7277
}
7378

7479
if(ya && ya.autorange) {
80+
headPlus = headSize - ann.yshift;
81+
headMinus = headSize + ann.yshift;
82+
7583
if(ann.ayref === ann.yref) {
7684
Axes.expand(ya, [ya.r2c(ann.y)], {
77-
ppadplus: headSize,
78-
ppadminus: headSize
85+
ppadplus: headPlus,
86+
ppadminus: headMinus
7987
});
8088
Axes.expand(ya, [ya.r2c(ann.ay)], {
8189
ppadplus: ann._ypadplus,
@@ -84,8 +92,8 @@ function annAutorange(gd) {
8492
}
8593
else {
8694
Axes.expand(ya, [ya.r2c(ann.y)], {
87-
ppadplus: Math.max(ann._ypadplus, headSize),
88-
ppadminus: Math.max(ann._ypadminus, headSize)
95+
ppadplus: Math.max(ann._ypadplus, headPlus),
96+
ppadminus: Math.max(ann._ypadminus, headMinus)
8997
});
9098
}
9199
}

src/components/annotations/draw.js

+54-17
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', options.captureevents ? 'all' : null)
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,
@@ -205,6 +236,7 @@ function drawOne(gd, index) {
205236
// but this one is the positive total size
206237
annSize = Math.abs(annSizeFromWidth) + Math.abs(annSizeFromHeight),
207238
anchor = options[axLetter + 'anchor'],
239+
overallShift = options[axLetter + 'shift'] * (axLetter === 'x' ? 1 : -1),
208240
posPx = annPosPx[axLetter],
209241
basePx,
210242
textPadShift,
@@ -295,6 +327,9 @@ function drawOne(gd, index) {
295327
posPx.text -= shiftMinus;
296328
}
297329
}
330+
331+
posPx.tail += overallShift;
332+
posPx.head += overallShift;
298333
}
299334
else {
300335
// with no arrow, the text rotates and *then* we put the anchor
@@ -304,6 +339,10 @@ function drawOne(gd, index) {
304339
posPx.text = basePx + textShift;
305340
}
306341

342+
posPx.text += overallShift;
343+
textShift += overallShift;
344+
textPadShift += overallShift;
345+
307346
// padplus/minus are used by autorange
308347
options['_' + axLetter + 'padplus'] = (annSize / 2) + textPadShift;
309348
options['_' + axLetter + 'padminus'] = (annSize / 2) - textPadShift;
@@ -493,21 +532,17 @@ function drawOne(gd, index) {
493532

494533
update[annbase + '.x'] = xa ?
495534
xa.p2r(xa.r2p(options.x) + dx) :
496-
((headX + dx - gs.l) / gs.w);
535+
(options.x + (dx / gs.w));
497536
update[annbase + '.y'] = ya ?
498537
ya.p2r(ya.r2p(options.y) + dy) :
499-
(1 - ((headY + dy - gs.t) / gs.h));
538+
(options.y - (dy / gs.h));
500539

501540
if(options.axref === options.xref) {
502-
update[annbase + '.ax'] = xa ?
503-
xa.p2r(xa.r2p(options.ax) + dx) :
504-
((headX + dx - gs.l) / gs.w);
541+
update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx);
505542
}
506543

507544
if(options.ayref === options.yref) {
508-
update[annbase + '.ay'] = ya ?
509-
ya.p2r(ya.r2p(options.ay) + dy) :
510-
(1 - ((headY + dy - gs.t) / gs.h));
545+
update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy);
511546
}
512547

513548
arrowGroup.attr('transform', 'translate(' + dx + ',' + dy + ')');
@@ -563,7 +598,8 @@ function drawOne(gd, index) {
563598
if(xa) update[annbase + '.x'] = options.x + dx / xa._m;
564599
else {
565600
var widthFraction = options._xsize / gs.w,
566-
xLeft = options.x + options._xshift / gs.w - widthFraction / 2;
601+
xLeft = options.x + (options._xshift - options.xshift) / gs.w -
602+
widthFraction / 2;
567603

568604
update[annbase + '.x'] = dragElement.align(xLeft + dx / gs.w,
569605
widthFraction, 0, 1, options.xanchor);
@@ -572,7 +608,8 @@ function drawOne(gd, index) {
572608
if(ya) update[annbase + '.y'] = options.y + dy / ya._m;
573609
else {
574610
var heightFraction = options._ysize / gs.h,
575-
yBottom = options.y - options._yshift / gs.h - heightFraction / 2;
611+
yBottom = options.y - (options._yshift + options.yshift) / gs.h -
612+
heightFraction / 2;
576613

577614
update[annbase + '.y'] = dragElement.align(yBottom - dy / gs.h,
578615
heightFraction, 0, 1, options.yanchor);

src/components/color/index.js

+20-6
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ var color = module.exports = {};
1616

1717
var colorAttrs = require('./attributes');
1818
color.defaults = colorAttrs.defaults;
19-
color.defaultLine = colorAttrs.defaultLine;
19+
var defaultLine = color.defaultLine = colorAttrs.defaultLine;
2020
color.lightLine = colorAttrs.lightLine;
21-
color.background = colorAttrs.background;
21+
var background = 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) + ', ' +
@@ -43,7 +47,7 @@ color.combine = function(front, back) {
4347
var fc = tinycolor(front).toRgb();
4448
if(fc.a === 1) return tinycolor(front).toRgbString();
4549

46-
var bc = tinycolor(back || color.background).toRgb(),
50+
var bc = tinycolor(back || background).toRgb(),
4751
bcflat = bc.a === 1 ? bc : {
4852
r: 255 * (1 - bc.a) + bc.r * bc.a,
4953
g: 255 * (1 - bc.a) + bc.g * bc.a,
@@ -57,12 +61,22 @@ 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+
*/
6072
color.contrast = function(cstr, lightAmount, darkAmount) {
6173
var tc = tinycolor(cstr);
6274

63-
var newColor = tc.isLight() ?
64-
tc.darken(darkAmount) :
65-
tc.lighten(lightAmount);
75+
if(tc.getAlpha() !== 1) tc = tinycolor(color.combine(cstr, background));
76+
77+
var newColor = tc.isDark() ?
78+
(lightAmount ? tc.lighten(lightAmount) : background) :
79+
(darkAmount ? tc.darken(darkAmount) : defaultLine);
6680

6781
return newColor.toString();
6882
};

0 commit comments

Comments
 (0)