Skip to content

Commit 7b2946a

Browse files
authored
Merge pull request #3685 from plotly/box-violin-pts-hovertemplate
Hovertemplate for box/violin points
2 parents 682eb66 + ea34d6c commit 7b2946a

File tree

10 files changed

+154
-7
lines changed

10 files changed

+154
-7
lines changed

Diff for: src/components/fx/hover.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -672,11 +672,15 @@ function _hover(gd, evt, subplot, noHoverEvent) {
672672
var pt = hoverData[itemnum];
673673
var eventData = helpers.makeEventData(pt, pt.trace, pt.cd);
674674

675-
var ht = false;
676-
if(pt.cd[pt.index] && pt.cd[pt.index].ht) ht = pt.cd[pt.index].ht;
677-
hoverData[itemnum].hovertemplate = ht || pt.trace.hovertemplate || false;
678-
hoverData[itemnum].eventData = [eventData];
675+
if(pt.hovertemplate !== false) {
676+
var ht = false;
677+
if(pt.cd[pt.index] && pt.cd[pt.index].ht) {
678+
ht = pt.cd[pt.index].ht;
679+
}
680+
pt.hovertemplate = ht || pt.trace.hovertemplate || false;
681+
}
679682

683+
pt.eventData = [eventData];
680684
newhoverdata.push(eventData);
681685
}
682686

Diff for: src/traces/box/attributes.js

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var scatterAttrs = require('../scatter/attributes');
1212
var barAttrs = require('../bar/attributes');
1313
var colorAttrs = require('../../components/color/attributes');
14+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1415
var extendFlat = require('../../lib/extend').extendFlat;
1516

1617
var scatterMarkerAttrs = scatterAttrs.marker;
@@ -76,6 +77,11 @@ module.exports = {
7677
hovertext: extendFlat({}, scatterAttrs.hovertext, {
7778
description: 'Same as `text`.'
7879
}),
80+
hovertemplate: hovertemplateAttrs({
81+
description: [
82+
'N.B. This only has an effect when hovering on points.'
83+
].join(' ')
84+
}),
7985
whiskerwidth: {
8086
valType: 'number',
8187
min: 0,

Diff for: src/traces/box/defaults.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) {
105105
delete traceOut.marker;
106106
}
107107

108-
coerce('hoveron');
108+
var hoveron = coerce('hoveron');
109+
if(hoveron === 'all' || hoveron.indexOf('points') !== -1) {
110+
coerce('hovertemplate');
111+
}
109112

110113
Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
111114
}

Diff for: src/traces/box/hover.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,15 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
176176
if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
177177
pointData2[vLetter + 'err'] = di.sd;
178178
}
179+
179180
// only keep name and spikes on the first item (median)
180181
pointData.name = '';
181182
pointData.spikeDistance = undefined;
182183
pointData[spikePosAttr] = undefined;
183184

185+
// no hovertemplate support yet
186+
pointData2.hovertemplate = false;
187+
184188
closeBoxData.push(pointData2);
185189
}
186190

@@ -242,7 +246,8 @@ function hoverOnPoints(pointData, xval, yval) {
242246
x1: xc + rad,
243247
y0: yc - rad,
244248
y1: yc + rad,
245-
spikeDistance: pointData.distance
249+
spikeDistance: pointData.distance,
250+
hovertemplate: trace.hovertemplate
246251
});
247252

248253
var pa;

Diff for: src/traces/scatter/hover.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
179179
y0: yAvg,
180180
y1: yAvg,
181181
color: color,
182-
hovertemplate: '%{name}'
182+
hovertemplate: false
183183
});
184184

185185
delete pointData.index;

Diff for: src/traces/violin/attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ module.exports = {
147147
marker: boxAttrs.marker,
148148
text: boxAttrs.text,
149149
hovertext: boxAttrs.hovertext,
150+
hovertemplate: boxAttrs.hovertemplate,
150151

151152
box: {
152153
visible: {

Diff for: src/traces/violin/hover.js

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLay
7171
closeBoxData[0].spikeDistance = undefined;
7272
closeBoxData[0][spikePosAttr] = undefined;
7373

74+
// no hovertemplate support yet
75+
kdePointData.hovertemplate = false;
76+
7477
closeData.push(kdePointData);
7578

7679
violinLineAttrs = {stroke: pointData.color};

Diff for: test/jasmine/tests/box_test.js

+47
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,40 @@ describe('Test boxes supplyDefaults', function() {
152152
expect(traceOut.text).toBeDefined();
153153
});
154154

155+
describe('should not coerce hovertemplate when *hoveron* does not contains *points* flag', function() {
156+
var ht = '--- %{y}';
157+
158+
it('- case hoveron:points', function() {
159+
traceIn = {
160+
y: [1, 1, 2],
161+
hoveron: 'points',
162+
hovertemplate: ht
163+
};
164+
supplyDefaults(traceIn, traceOut, defaultColor, {});
165+
expect(traceOut.hovertemplate).toBe(ht);
166+
});
167+
168+
it('- case hoveron:points+boxes', function() {
169+
traceIn = {
170+
y: [1, 1, 2],
171+
hoveron: 'points+boxes',
172+
hovertemplate: ht
173+
};
174+
supplyDefaults(traceIn, traceOut, defaultColor, {});
175+
expect(traceOut.hovertemplate).toBe(ht);
176+
});
177+
178+
it('- case hoveron:boxes', function() {
179+
traceIn = {
180+
y: [1, 1, 2],
181+
hoveron: 'boxes',
182+
hovertemplate: ht
183+
};
184+
supplyDefaults(traceIn, traceOut, defaultColor, {});
185+
expect(traceOut.hovertemplate).toBe(undefined);
186+
});
187+
});
188+
155189
it('should not include alignementgroup/offsetgroup when boxmode is not *group*', function() {
156190
var gd = {
157191
data: [{type: 'box', y: [1], alignmentgroup: 'a', offsetgroup: '1'}],
@@ -391,6 +425,19 @@ describe('Test box hover:', function() {
391425
pos: [202, 335],
392426
nums: '(2, 13.1)',
393427
name: ''
428+
}, {
429+
desc: 'with hovertemplate for points',
430+
patch: function(fig) {
431+
fig.data.forEach(function(trace) {
432+
trace.boxpoints = 'all';
433+
trace.hoveron = 'points';
434+
trace.hovertemplate = '%{y}<extra>pt #%{pointNumber}</extra>';
435+
});
436+
fig.layout.hovermode = 'closest';
437+
return fig;
438+
},
439+
nums: '0.6',
440+
name: 'pt #0'
394441
}].forEach(function(specs) {
395442
it('should generate correct hover labels ' + specs.desc, function(done) {
396443
run(specs).catch(failTest).then(done);

Diff for: test/jasmine/tests/hover_label_test.js

+32
Original file line numberDiff line numberDiff line change
@@ -1938,6 +1938,38 @@ describe('hover info', function() {
19381938
.catch(failTest)
19391939
.then(done);
19401940
});
1941+
1942+
it('should fallback to regular hover content when hoveron does not support hovertemplate', function(done) {
1943+
var gd = createGraphDiv();
1944+
var fig = Lib.extendDeep({}, require('@mocks/scatter_fill_self_next.json'));
1945+
1946+
fig.data.forEach(function(trace) {
1947+
trace.hoveron = 'points+fills';
1948+
trace.hovertemplate = '%{x} | %{y}';
1949+
});
1950+
1951+
fig.layout.hovermode = 'closest';
1952+
fig.layout.showlegend = false;
1953+
fig.layout.margin = {t: 0, b: 0, l: 0, r: 0};
1954+
1955+
Plotly.plot(gd, fig)
1956+
.then(function() { _hoverNatural(gd, 180, 200); })
1957+
.then(function() {
1958+
assertHoverLabelContent({
1959+
nums: 'trace 1',
1960+
name: ''
1961+
}, 'hovering on a fill');
1962+
})
1963+
.then(function() { _hoverNatural(gd, 50, 95); })
1964+
.then(function() {
1965+
assertHoverLabelContent({
1966+
nums: '0 | 5',
1967+
name: 'trace 1'
1968+
}, 'hovering on a pt');
1969+
})
1970+
.catch(failTest)
1971+
.then(done);
1972+
});
19411973
});
19421974

19431975
describe('hover info on stacked subplots', function() {

Diff for: test/jasmine/tests/violin_test.js

+46
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,38 @@ describe('Test violin defaults', function() {
160160
expect(traceOut.scalegroup).toBe('');
161161
});
162162

163+
it('should not coerce hovertemplate when *hoveron* does not contains *points* flag', function() {
164+
var ht = '--- %{y} ---';
165+
166+
_supply({
167+
y: [1, 2, 1],
168+
hoveron: 'points',
169+
hovertemplate: ht
170+
});
171+
expect(traceOut.hovertemplate).toBe(ht, 'hoveron:points');
172+
173+
_supply({
174+
y: [1, 2, 1],
175+
hoveron: 'kde',
176+
hovertemplate: ht
177+
});
178+
expect(traceOut.hovertemplate).toBe(undefined, 'hoveron:kde');
179+
180+
_supply({
181+
y: [1, 2, 1],
182+
hoveron: 'all',
183+
hovertemplate: ht
184+
});
185+
expect(traceOut.hovertemplate).toBe(ht, 'hoveron:all');
186+
187+
_supply({
188+
y: [1, 2, 1],
189+
hoveron: 'violins+points',
190+
hovertemplate: ht
191+
});
192+
expect(traceOut.hovertemplate).toBe(ht, 'hoveron:violins+points');
193+
});
194+
163195
it('should not include alignementgroup/offsetgroup when violinmode is not *group*', function() {
164196
var gd = {
165197
data: [{type: 'violin', y: [1], alignmentgroup: 'a', offsetgroup: '1'}],
@@ -591,6 +623,20 @@ describe('Test violin hover:', function() {
591623
pos: [417, 309],
592624
nums: '(14, 2)',
593625
name: ''
626+
}, {
627+
desc: 'with hovertemplate for points',
628+
patch: function(fig) {
629+
fig.data.forEach(function(trace) {
630+
trace.points = 'all';
631+
trace.hoveron = 'points';
632+
trace.hovertemplate = 'Sample pt %{pointNumber}: %{y:.3f}<extra></extra>';
633+
});
634+
fig.layout.hovermode = 'closest';
635+
return fig;
636+
},
637+
pos: [220, 200],
638+
nums: 'Sample pt 3: 0.900',
639+
name: ''
594640
}]
595641
.forEach(function(specs) {
596642
it('should generate correct hover labels ' + specs.desc, function(done) {

0 commit comments

Comments
 (0)