diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 0cb5df93b86..76ada0f47b0 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -672,11 +672,15 @@ function _hover(gd, evt, subplot, noHoverEvent) { var pt = hoverData[itemnum]; var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); - var ht = false; - if(pt.cd[pt.index] && pt.cd[pt.index].ht) ht = pt.cd[pt.index].ht; - hoverData[itemnum].hovertemplate = ht || pt.trace.hovertemplate || false; - hoverData[itemnum].eventData = [eventData]; + if(pt.hovertemplate !== false) { + var ht = false; + if(pt.cd[pt.index] && pt.cd[pt.index].ht) { + ht = pt.cd[pt.index].ht; + } + pt.hovertemplate = ht || pt.trace.hovertemplate || false; + } + pt.eventData = [eventData]; newhoverdata.push(eventData); } diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js index 3a8a1f3c4c8..66476cb1f26 100644 --- a/src/traces/box/attributes.js +++ b/src/traces/box/attributes.js @@ -11,6 +11,7 @@ var scatterAttrs = require('../scatter/attributes'); var barAttrs = require('../bar/attributes'); var colorAttrs = require('../../components/color/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var scatterMarkerAttrs = scatterAttrs.marker; @@ -76,6 +77,11 @@ module.exports = { hovertext: extendFlat({}, scatterAttrs.hovertext, { description: 'Same as `text`.' }), + hovertemplate: hovertemplateAttrs({ + description: [ + 'N.B. This only has an effect when hovering on points.' + ].join(' ') + }), whiskerwidth: { valType: 'number', min: 0, diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index e3d61a37b69..3bba43eb33d 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -105,7 +105,10 @@ function handlePointsDefaults(traceIn, traceOut, coerce, opts) { delete traceOut.marker; } - coerce('hoveron'); + var hoveron = coerce('hoveron'); + if(hoveron === 'all' || hoveron.indexOf('points') !== -1) { + coerce('hovertemplate'); + } Lib.coerceSelectionMarkerOpacity(traceOut, coerce); } diff --git a/src/traces/box/hover.js b/src/traces/box/hover.js index e2cc11969a6..c97d040db43 100644 --- a/src/traces/box/hover.js +++ b/src/traces/box/hover.js @@ -176,11 +176,15 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) { if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { pointData2[vLetter + 'err'] = di.sd; } + // only keep name and spikes on the first item (median) pointData.name = ''; pointData.spikeDistance = undefined; pointData[spikePosAttr] = undefined; + // no hovertemplate support yet + pointData2.hovertemplate = false; + closeBoxData.push(pointData2); } @@ -242,7 +246,8 @@ function hoverOnPoints(pointData, xval, yval) { x1: xc + rad, y0: yc - rad, y1: yc + rad, - spikeDistance: pointData.distance + spikeDistance: pointData.distance, + hovertemplate: trace.hovertemplate }); var pa; diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index d82509e1c98..c1c9410eb5e 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -179,7 +179,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { y0: yAvg, y1: yAvg, color: color, - hovertemplate: '%{name}' + hovertemplate: false }); delete pointData.index; diff --git a/src/traces/violin/attributes.js b/src/traces/violin/attributes.js index 2b7e08a713f..af5f76d44f1 100644 --- a/src/traces/violin/attributes.js +++ b/src/traces/violin/attributes.js @@ -147,6 +147,7 @@ module.exports = { marker: boxAttrs.marker, text: boxAttrs.text, hovertext: boxAttrs.hovertext, + hovertemplate: boxAttrs.hovertemplate, box: { visible: { diff --git a/src/traces/violin/hover.js b/src/traces/violin/hover.js index cb70d91a63e..184e41744c7 100644 --- a/src/traces/violin/hover.js +++ b/src/traces/violin/hover.js @@ -71,6 +71,9 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLay closeBoxData[0].spikeDistance = undefined; closeBoxData[0][spikePosAttr] = undefined; + // no hovertemplate support yet + kdePointData.hovertemplate = false; + closeData.push(kdePointData); violinLineAttrs = {stroke: pointData.color}; diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js index 527b540c806..86b2bc6e552 100644 --- a/test/jasmine/tests/box_test.js +++ b/test/jasmine/tests/box_test.js @@ -152,6 +152,40 @@ describe('Test boxes supplyDefaults', function() { expect(traceOut.text).toBeDefined(); }); + describe('should not coerce hovertemplate when *hoveron* does not contains *points* flag', function() { + var ht = '--- %{y}'; + + it('- case hoveron:points', function() { + traceIn = { + y: [1, 1, 2], + hoveron: 'points', + hovertemplate: ht + }; + supplyDefaults(traceIn, traceOut, defaultColor, {}); + expect(traceOut.hovertemplate).toBe(ht); + }); + + it('- case hoveron:points+boxes', function() { + traceIn = { + y: [1, 1, 2], + hoveron: 'points+boxes', + hovertemplate: ht + }; + supplyDefaults(traceIn, traceOut, defaultColor, {}); + expect(traceOut.hovertemplate).toBe(ht); + }); + + it('- case hoveron:boxes', function() { + traceIn = { + y: [1, 1, 2], + hoveron: 'boxes', + hovertemplate: ht + }; + supplyDefaults(traceIn, traceOut, defaultColor, {}); + expect(traceOut.hovertemplate).toBe(undefined); + }); + }); + it('should not include alignementgroup/offsetgroup when boxmode is not *group*', function() { var gd = { data: [{type: 'box', y: [1], alignmentgroup: 'a', offsetgroup: '1'}], @@ -391,6 +425,19 @@ describe('Test box hover:', function() { pos: [202, 335], nums: '(2, 13.1)', name: '' + }, { + desc: 'with hovertemplate for points', + patch: function(fig) { + fig.data.forEach(function(trace) { + trace.boxpoints = 'all'; + trace.hoveron = 'points'; + trace.hovertemplate = '%{y}pt #%{pointNumber}'; + }); + fig.layout.hovermode = 'closest'; + return fig; + }, + nums: '0.6', + name: 'pt #0' }].forEach(function(specs) { it('should generate correct hover labels ' + specs.desc, function(done) { run(specs).catch(failTest).then(done); diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 89792ed50ae..973a458e2c0 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1938,6 +1938,38 @@ describe('hover info', function() { .catch(failTest) .then(done); }); + + it('should fallback to regular hover content when hoveron does not support hovertemplate', function(done) { + var gd = createGraphDiv(); + var fig = Lib.extendDeep({}, require('@mocks/scatter_fill_self_next.json')); + + fig.data.forEach(function(trace) { + trace.hoveron = 'points+fills'; + trace.hovertemplate = '%{x} | %{y}'; + }); + + fig.layout.hovermode = 'closest'; + fig.layout.showlegend = false; + fig.layout.margin = {t: 0, b: 0, l: 0, r: 0}; + + Plotly.plot(gd, fig) + .then(function() { _hoverNatural(gd, 180, 200); }) + .then(function() { + assertHoverLabelContent({ + nums: 'trace 1', + name: '' + }, 'hovering on a fill'); + }) + .then(function() { _hoverNatural(gd, 50, 95); }) + .then(function() { + assertHoverLabelContent({ + nums: '0 | 5', + name: 'trace 1' + }, 'hovering on a pt'); + }) + .catch(failTest) + .then(done); + }); }); describe('hover info on stacked subplots', function() { diff --git a/test/jasmine/tests/violin_test.js b/test/jasmine/tests/violin_test.js index 231e26a19f9..80bfa26d8a5 100644 --- a/test/jasmine/tests/violin_test.js +++ b/test/jasmine/tests/violin_test.js @@ -160,6 +160,38 @@ describe('Test violin defaults', function() { expect(traceOut.scalegroup).toBe(''); }); + it('should not coerce hovertemplate when *hoveron* does not contains *points* flag', function() { + var ht = '--- %{y} ---'; + + _supply({ + y: [1, 2, 1], + hoveron: 'points', + hovertemplate: ht + }); + expect(traceOut.hovertemplate).toBe(ht, 'hoveron:points'); + + _supply({ + y: [1, 2, 1], + hoveron: 'kde', + hovertemplate: ht + }); + expect(traceOut.hovertemplate).toBe(undefined, 'hoveron:kde'); + + _supply({ + y: [1, 2, 1], + hoveron: 'all', + hovertemplate: ht + }); + expect(traceOut.hovertemplate).toBe(ht, 'hoveron:all'); + + _supply({ + y: [1, 2, 1], + hoveron: 'violins+points', + hovertemplate: ht + }); + expect(traceOut.hovertemplate).toBe(ht, 'hoveron:violins+points'); + }); + it('should not include alignementgroup/offsetgroup when violinmode is not *group*', function() { var gd = { data: [{type: 'violin', y: [1], alignmentgroup: 'a', offsetgroup: '1'}], @@ -591,6 +623,20 @@ describe('Test violin hover:', function() { pos: [417, 309], nums: '(14, 2)', name: '' + }, { + desc: 'with hovertemplate for points', + patch: function(fig) { + fig.data.forEach(function(trace) { + trace.points = 'all'; + trace.hoveron = 'points'; + trace.hovertemplate = 'Sample pt %{pointNumber}: %{y:.3f}'; + }); + fig.layout.hovermode = 'closest'; + return fig; + }, + pos: [220, 200], + nums: 'Sample pt 3: 0.900', + name: '' }] .forEach(function(specs) { it('should generate correct hover labels ' + specs.desc, function(done) {