From b49e13fb6c3b0d4678c849807a52250dd1cfdb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Feb 2019 17:51:13 -0500 Subject: [PATCH 1/6] add hovertemplate to gl3d traces - i.e. scatter3d, surface, mesh3d, cone, streamtube and isosurface! --- src/components/fx/hovertemplate_attributes.js | 2 +- src/plots/gl3d/scene.js | 100 ++++++++++-------- src/traces/cone/attributes.js | 4 +- src/traces/cone/defaults.js | 1 + src/traces/cone/index.js | 4 + src/traces/isosurface/attributes.js | 2 + src/traces/isosurface/defaults.js | 2 +- src/traces/mesh3d/attributes.js | 2 + src/traces/mesh3d/defaults.js | 1 + src/traces/scatter3d/attributes.js | 2 + src/traces/scatter3d/defaults.js | 1 + src/traces/streamtube/attributes.js | 10 +- src/traces/streamtube/defaults.js | 1 + src/traces/surface/attributes.js | 2 + src/traces/surface/defaults.js | 1 + test/jasmine/tests/cone_test.js | 10 ++ test/jasmine/tests/gl3d_plot_interact_test.js | 16 +++ test/jasmine/tests/isosurface_test.js | 11 ++ test/jasmine/tests/streamtube_test.js | 8 ++ 19 files changed, 134 insertions(+), 46 deletions(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 8c5f8003464..9ccd77f28a9 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -32,7 +32,7 @@ module.exports = function(opts, extra) { role: 'info', dflt: '', arrayOk: true, - editType: 'none', + editType: opts.editType || 'none', description: [ 'Template string used for rendering the information that appear on hover box.', 'Note that this will override `hoverinfo`.', diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 94a7ef40362..2a857407e8d 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -71,18 +71,21 @@ function render(scene) { var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate); trace = lastPicked.data; var ptNumber = selection.index; - var hoverinfo = Fx.castHoverinfo(trace, scene.fullLayout, ptNumber); - var hoverinfoParts = hoverinfo.split('+'); - var isHoverinfoAll = hoverinfo === 'all'; - var xVal = formatter('xaxis', selection.traceCoordinate[0]); - var yVal = formatter('yaxis', selection.traceCoordinate[1]); - var zVal = formatter('zaxis', selection.traceCoordinate[2]); + var labels = { + xLabel: formatter('xaxis', selection.traceCoordinate[0]), + yLabel: formatter('yaxis', selection.traceCoordinate[1]), + zLabel: formatter('zaxis', selection.traceCoordinate[2]) + }; + + var hoverinfo = Fx.castHoverinfo(trace, scene.fullLayout, ptNumber); + var hoverinfoParts = (hoverinfo || '').split('+'); + var isHoverinfoAll = hoverinfo && hoverinfo === 'all'; - if(!isHoverinfoAll) { - if(hoverinfoParts.indexOf('x') === -1) xVal = undefined; - if(hoverinfoParts.indexOf('y') === -1) yVal = undefined; - if(hoverinfoParts.indexOf('z') === -1) zVal = undefined; + if(!trace.hovertemplate && !isHoverinfoAll) { + if(hoverinfoParts.indexOf('x') === -1) labels.xLabel = undefined; + if(hoverinfoParts.indexOf('y') === -1) labels.yLabel = undefined; + if(hoverinfoParts.indexOf('z') === -1) labels.zLabel = undefined; if(hoverinfoParts.indexOf('text') === -1) selection.textLabel = undefined; if(hoverinfoParts.indexOf('name') === -1) lastPicked.name = undefined; } @@ -91,27 +94,38 @@ function render(scene) { var vectorTx = []; if(trace.type === 'cone' || trace.type === 'streamtube') { + labels.uLabel = formatter('xaxis', selection.traceCoordinate[3]); if(isHoverinfoAll || hoverinfoParts.indexOf('u') !== -1) { - vectorTx.push('u: ' + formatter('xaxis', selection.traceCoordinate[3])); + vectorTx.push('u: ' + labels.uLabel); } + + labels.vLabel = formatter('yaxis', selection.traceCoordinate[4]); if(isHoverinfoAll || hoverinfoParts.indexOf('v') !== -1) { - vectorTx.push('v: ' + formatter('yaxis', selection.traceCoordinate[4])); + vectorTx.push('v: ' + labels.vLabel); } + + labels.wLabel = formatter('zaxis', selection.traceCoordinate[5]); if(isHoverinfoAll || hoverinfoParts.indexOf('w') !== -1) { - vectorTx.push('w: ' + formatter('zaxis', selection.traceCoordinate[5])); + vectorTx.push('w: ' + labels.wLabel); } + + labels.normLabel = selection.traceCoordinate[6].toPrecision(3); if(isHoverinfoAll || hoverinfoParts.indexOf('norm') !== -1) { - vectorTx.push('norm: ' + selection.traceCoordinate[6].toPrecision(3)); + vectorTx.push('norm: ' + labels.normLabel); } - if(trace.type === 'streamtube' && (isHoverinfoAll || hoverinfoParts.indexOf('divergence') !== -1)) { - vectorTx.push('divergence: ' + selection.traceCoordinate[7].toPrecision(3)); + if(trace.type === 'streamtube') { + labels.divergenceLabel = selection.traceCoordinate[7].toPrecision(3); + if(isHoverinfoAll || hoverinfoParts.indexOf('divergence') !== -1) { + vectorTx.push('divergence: ' + labels.divergenceLabel); + } } if(selection.textLabel) { vectorTx.push(selection.textLabel); } tx = vectorTx.join('
'); } else if(trace.type === 'isosurface') { - vectorTx.push('value: ' + Axes.tickText(scene.mockAxis, scene.mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text); + labels.valueLabel = Axes.tickText(scene.mockAxis, scene.mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text; + vectorTx.push('value: ' + labels.valueLabel); if(selection.textLabel) { vectorTx.push(selection.textLabel); } @@ -120,27 +134,6 @@ function render(scene) { tx = selection.textLabel; } - if(scene.fullSceneLayout.hovermode) { - Fx.loneHover({ - x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width, - y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height, - xLabel: xVal, - yLabel: yVal, - zLabel: zVal, - text: tx, - name: lastPicked.name, - color: Fx.castHoverOption(trace, ptNumber, 'bgcolor') || lastPicked.color, - borderColor: Fx.castHoverOption(trace, ptNumber, 'bordercolor'), - fontFamily: Fx.castHoverOption(trace, ptNumber, 'font.family'), - fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'), - fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color') - }, { - container: svgContainer, - gd: scene.graphDiv - }); - } - - // TODO not sure if streamtube x/y/z should be emitted as x/y/z var pointData = { x: selection.traceCoordinate[0], y: selection.traceCoordinate[1], @@ -151,18 +144,41 @@ function render(scene) { pointNumber: ptNumber }; + Fx.appendArrayPointValue(pointData, trace, ptNumber); + if(trace._module.eventData) { pointData = trace._module.eventData(pointData, selection, trace, {}, ptNumber); } - Fx.appendArrayPointValue(pointData, trace, ptNumber); - var eventData = {points: [pointData]}; + if(scene.fullSceneLayout.hovermode) { + Fx.loneHover({ + trace: trace, + x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width, + y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height, + xLabel: labels.xLabel, + yLabel: labels.yLabel, + zLabel: labels.zLabel, + text: tx, + name: lastPicked.name, + color: Fx.castHoverOption(trace, ptNumber, 'bgcolor') || lastPicked.color, + borderColor: Fx.castHoverOption(trace, ptNumber, 'bordercolor'), + fontFamily: Fx.castHoverOption(trace, ptNumber, 'font.family'), + fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'), + fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color'), + hovertemplate: Lib.castOption(trace, ptNumber, 'hovertemplate'), + hovertemplateLabels: Lib.extendFlat({}, pointData, labels), + eventData: [pointData] + }, { + container: svgContainer, + gd: scene.graphDiv + }); + } + if(selection.buttons && selection.distance < 5) { scene.graphDiv.emit('plotly_click', eventData); - } - else { + } else { scene.graphDiv.emit('plotly_hover', eventData); } diff --git a/src/traces/cone/attributes.js b/src/traces/cone/attributes.js index 0acbae90447..e2a2f062fe2 100644 --- a/src/traces/cone/attributes.js +++ b/src/traces/cone/attributes.js @@ -10,6 +10,7 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var mesh3dAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -157,7 +158,8 @@ var attrs = { 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', 'these elements will be seen in the hover labels.' ].join(' ') - } + }, + hovertemplate: hovertemplateAttrs({editType: 'calc'}, {keys: ['norm']}) }; extendFlat(attrs, colorscaleAttrs('', { diff --git a/src/traces/cone/defaults.js b/src/traces/cone/defaults.js index 6a5ec9e1fcc..7237edba166 100644 --- a/src/traces/cone/defaults.js +++ b/src/traces/cone/defaults.js @@ -52,6 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); coerce('text'); + coerce('hovertemplate'); // disable 1D transforms (for now) traceOut._length = null; diff --git a/src/traces/cone/index.js b/src/traces/cone/index.js index d08a37e5200..e786cb9d47e 100644 --- a/src/traces/cone/index.js +++ b/src/traces/cone/index.js @@ -22,6 +22,10 @@ module.exports = { }, calc: require('./calc'), plot: require('./convert'), + eventData: function(out, pt) { + out.norm = pt.traceCoordinate[6]; + return out; + }, meta: { description: [ diff --git a/src/traces/isosurface/attributes.js b/src/traces/isosurface/attributes.js index 8a0216b9bff..b89254692bc 100644 --- a/src/traces/isosurface/attributes.js +++ b/src/traces/isosurface/attributes.js @@ -10,6 +10,7 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var surfaceAtts = require('../surface/attributes'); var meshAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -226,6 +227,7 @@ var attrs = module.exports = overrideAll(extendFlat({ 'these elements will be seen in the hover labels.' ].join(' ') }, + hovertemplate: hovertemplateAttrs() }, colorscaleAttrs('', { diff --git a/src/traces/isosurface/defaults.js b/src/traces/isosurface/defaults.js index 2c7de188cdd..fc01b830dc1 100644 --- a/src/traces/isosurface/defaults.js +++ b/src/traces/isosurface/defaults.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var Registry = require('../../registry'); @@ -85,6 +84,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // Coerce remaining properties [ 'text', + 'hovertemplate', 'lighting.ambient', 'lighting.diffuse', 'lighting.specular', diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index 9a6f642475a..dea5596c3c0 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -10,6 +10,7 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var surfaceAtts = require('../surface/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -89,6 +90,7 @@ module.exports = extendFlat({ 'these elements will be seen in the hover labels.' ].join(' ') }, + hovertemplate: hovertemplateAttrs({editType: 'calc'}), delaunayaxis: { valType: 'enumerated', diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index 952f8cd73c5..c46204a11c2 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -88,6 +88,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertemplate'); // disable 1D transforms // x/y/z should match lengths, and i/j/k should match as well, but diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 9c4e2910711..d02559bf30c 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -10,6 +10,7 @@ var scatterAttrs = require('../scatter/attributes'); var colorAttributes = require('../../components/colorscale/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var baseAttrs = require('../../plots/attributes'); var DASHES = require('../../constants/gl3d_dashes'); @@ -94,6 +95,7 @@ var attrs = module.exports = overrideAll({ 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }), + hovertemplate: hovertemplateAttrs(), mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? {dflt: 'lines+markers'}), diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index acf3345ea82..0b20888395a 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); + coerce('hovertemplate'); coerce('mode'); if(subTypes.hasLines(traceOut)) { diff --git a/src/traces/streamtube/attributes.js b/src/traces/streamtube/attributes.js index e7ee464ff2c..4dfbd303912 100644 --- a/src/traces/streamtube/attributes.js +++ b/src/traces/streamtube/attributes.js @@ -10,6 +10,7 @@ var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var mesh3dAttrs = require('../mesh3d/attributes'); var baseAttrs = require('../../plots/attributes'); @@ -130,7 +131,14 @@ var attrs = { 'this text element will be seen in all hover labels.', 'Note that streamtube traces do not support array `text` values.' ].join(' ') - } + }, + hovertemplate: hovertemplateAttrs({editType: 'calc'}, { + keys: [ + 'tubex', 'tubey', 'tubez', + 'tubeu', 'tubev', 'tubew', + 'norm', 'divergence' + ] + }) }; extendFlat(attrs, colorscaleAttrs('', { diff --git a/src/traces/streamtube/defaults.js b/src/traces/streamtube/defaults.js index ffc6cd2d04c..e8bae6160d5 100644 --- a/src/traces/streamtube/defaults.js +++ b/src/traces/streamtube/defaults.js @@ -53,6 +53,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'}); coerce('text'); + coerce('hovertemplate'); // disable 1D transforms (for now) // x/y/z and u/v/w have matching lengths, diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index b12b1555333..fd19ffd3295 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -11,6 +11,7 @@ var Color = require('../../components/color'); var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var baseAttrs = require('../../plots/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -123,6 +124,7 @@ var attrs = module.exports = overrideAll(extendFlat({ 'these elements will be seen in the hover labels.' ].join(' ') }, + hovertemplate: hovertemplateAttrs(), surfacecolor: { valType: 'data_array', diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index 54c0ffc5033..3ca1cf856db 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -42,6 +42,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); coerce('text'); + coerce('hovertemplate'); // Coerce remaining properties [ diff --git a/test/jasmine/tests/cone_test.js b/test/jasmine/tests/cone_test.js index 333e26c2b3d..f43b106666b 100644 --- a/test/jasmine/tests/cone_test.js +++ b/test/jasmine/tests/cone_test.js @@ -297,6 +297,16 @@ describe('Test cone interactions', function() { 'norm: 3.00' ].join('\n') }); + + return Plotly.restyle(gd, 'hovertemplate', 'NORM : %{norm}
at %{x},%{y},%{z}LOOKOUT'); + }) + .then(delay(20)) + .then(_hover) + .then(function() { + assertHoverLabelContent({ + name: 'LOOKOUT', + nums: 'NORM : 3.00\nat 2,2,2' + }); }) .catch(failTest) .then(done); diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 382616c048f..91c881f5faf 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -242,6 +242,11 @@ describe('Test gl3d plots', function() { }) .then(function() { assertHoverText(null, null, '100k'); + + return Plotly.restyle(gd, 'hovertemplate', 'THIS Y -- %{y}'); + }) + .then(function() { + assertHoverText(null, null, null, 'THIS Y -- c'); }) .catch(failTest) .then(done); @@ -339,6 +344,11 @@ describe('Test gl3d plots', function() { }) .then(function() { assertHoverText(null, null, null, 'yo!'); + + return Plotly.restyle(gd, 'hovertemplate', '!!! %{z} !!!'); + }) + .then(function() { + assertHoverText(null, null, null, '!!! 43 !!!'); }) .then(done); }); @@ -418,6 +428,12 @@ describe('Test gl3d plots', function() { .then(function() { assertHoverText(null, null, null, 'yo!'); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', '%{x}-%{y}-%{z}'); + }) + .then(function() { + assertHoverText(null, null, null, '3-4-5'); + }) .catch(failTest) .then(done); }); diff --git a/test/jasmine/tests/isosurface_test.js b/test/jasmine/tests/isosurface_test.js index b745fbbba49..3b821063aa8 100644 --- a/test/jasmine/tests/isosurface_test.js +++ b/test/jasmine/tests/isosurface_test.js @@ -398,6 +398,17 @@ describe('Test isosurface', function() { ].join('\n') }); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', '%{value}
(%{x},%{y},%{z})!!'); + }) + .then(delay(20)) + .then(_hover4) + .then(function() { + assertHoverLabelContent({ + nums: '−1.3\n(0.4,100μ,−4)', + name: '!!' + }); + }) .catch(failTest) .then(done); }); diff --git a/test/jasmine/tests/streamtube_test.js b/test/jasmine/tests/streamtube_test.js index 647fb49f5a2..1316e90c92e 100644 --- a/test/jasmine/tests/streamtube_test.js +++ b/test/jasmine/tests/streamtube_test.js @@ -395,6 +395,14 @@ describe('@noCI Test streamtube hover', function() { ].join('\n'), name: 'TUBE!' }); + + return Plotly.restyle(gd, 'hovertemplate', '∇·F = %{divergence:.3f}TUBE'); + }) + .then(function() { + assertHoverLabelContent({ + nums: '∇·F = 0.465', + name: 'TUBE' + }); }) .catch(failTest) .then(done); From 0ab5b6f470c180de82c7c357142f1d043fd01615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Feb 2019 18:01:34 -0500 Subject: [PATCH 2/6] add hovertemplate to splom traces --- src/traces/splom/attributes.js | 2 ++ src/traces/splom/defaults.js | 1 + test/jasmine/tests/splom_test.js | 12 ++++++++++++ 3 files changed, 15 insertions(+) diff --git a/src/traces/splom/attributes.js b/src/traces/splom/attributes.js index 38ca5188545..79dff01442a 100644 --- a/src/traces/splom/attributes.js +++ b/src/traces/splom/attributes.js @@ -10,6 +10,7 @@ var scatterAttrs = require('../scatter/attributes'); var colorAttrs = require('../../components/colorscale/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var scatterGlAttrs = require('../scattergl/attributes'); var cartesianIdRegex = require('../../plots/cartesian/constants').idRegex; var templatedArray = require('../../plot_api/plot_template').templatedArray; @@ -123,6 +124,7 @@ module.exports = { 'this trace\'s (x,y) coordinates.' ].join(' ') }), + hovertemplate: hovertemplateAttrs(), marker: markerAttrs, diff --git a/src/traces/splom/defaults.js b/src/traces/splom/defaults.js index d363d299964..1a268cef365 100644 --- a/src/traces/splom/defaults.js +++ b/src/traces/splom/defaults.js @@ -39,6 +39,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertemplate'); handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); diff --git a/test/jasmine/tests/splom_test.js b/test/jasmine/tests/splom_test.js index 3e09444efd9..815671aa976 100644 --- a/test/jasmine/tests/splom_test.js +++ b/test/jasmine/tests/splom_test.js @@ -1360,6 +1360,18 @@ describe('Test splom hover:', function() { nums: 'Apr 2003', axis: 'Jan 2000', evtPts: [{x: '2000-01-01', y: '2003-04-21', pointNumber: 0}] + }, { + desc: 'with a hovertemplate', + patch: function(fig) { + fig.data.forEach(function(t) { + t.hovertemplate = '%{x}|%{y}pt %{pointNumber}'; + }); + fig.layout.hovermode = 'closest'; + return fig; + }, + nums: '2.6|7.7', + name: 'pt 18', + evtPts: [{x: 2.6, y: 7.7, pointNumber: 18, curveNumber: 2}] }]; specs.forEach(function(s) { From b9cffc1a2087403742bc096f304e9111c9e9440b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Feb 2019 10:42:20 -0500 Subject: [PATCH 3/6] add hovertemplate to heatmap, contour, histogram2d, histogram2dcontour --- src/traces/contour/attributes.js | 3 +- src/traces/contour/defaults.js | 3 +- src/traces/heatmap/attributes.js | 3 ++ src/traces/heatmap/defaults.js | 1 + src/traces/histogram/event_data.js | 3 ++ src/traces/histogram2d/attributes.js | 4 +- src/traces/histogram2d/defaults.js | 1 + src/traces/histogram2dcontour/attributes.js | 3 +- src/traces/histogram2dcontour/defaults.js | 1 + src/traces/scattercarpet/attributes.js | 2 + test/jasmine/tests/hover_label_test.js | 59 +++++++++++++++++++++ 11 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 9edef92e3b6..ff3ba73bec6 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -35,6 +35,7 @@ module.exports = extendFlat({ xtype: heatmapAttrs.xtype, ytype: heatmapAttrs.ytype, zhoverformat: heatmapAttrs.zhoverformat, + hovertemplate: heatmapAttrs.hovertemplate, connectgaps: heatmapAttrs.connectgaps, @@ -247,7 +248,7 @@ module.exports = extendFlat({ ].join(' ') }), editType: 'plot' - } + }, }, colorscaleAttrs('', { cLetter: 'z', diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js index 650205e17be..aa6cb7c8842 100644 --- a/src/traces/contour/defaults.js +++ b/src/traces/contour/defaults.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var Lib = require('../../lib'); @@ -34,6 +33,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertemplate'); + var isConstraint = (coerce('contours.type') === 'constraint'); coerce('connectgaps', Lib.isArray1D(traceOut.z)); diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index ce13169b456..9bf083d8098 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -9,6 +9,7 @@ 'use strict'; var scatterAttrs = require('../scatter/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); @@ -111,6 +112,8 @@ module.exports = extendFlat({ 'https://github.com/d3/d3-format/blob/master/README.md#locale_format' ].join(' ') }, + hovertemplate: hovertemplateAttrs() +}, { transforms: undefined }, colorscaleAttrs('', { diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js index 80e34a22fbc..6e2933cbc91 100644 --- a/src/traces/heatmap/defaults.js +++ b/src/traces/heatmap/defaults.js @@ -29,6 +29,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertemplate'); handleStyleDefaults(traceIn, traceOut, coerce, layout); diff --git a/src/traces/histogram/event_data.js b/src/traces/histogram/event_data.js index c46921426e3..ab5ac4f41a3 100644 --- a/src/traces/histogram/event_data.js +++ b/src/traces/histogram/event_data.js @@ -13,6 +13,9 @@ module.exports = function eventData(out, pt, trace, cd, pointNumber) { out.x = 'xVal' in pt ? pt.xVal : pt.x; out.y = 'yVal' in pt ? pt.yVal : pt.y; + // for 2d histograms + if('zLabelVal' in pt) out.z = pt.zLabelVal; + if(pt.xa) out.xaxis = pt.xa; if(pt.ya) out.yaxis = pt.ya; diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 34d8fb47ee7..72befadf1fd 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -11,6 +11,7 @@ var histogramAttrs = require('../histogram/attributes'); var makeBinAttrs = require('../histogram/bin_attributes'); var heatmapAttrs = require('../heatmap/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); @@ -47,7 +48,8 @@ module.exports = extendFlat( xgap: heatmapAttrs.xgap, ygap: heatmapAttrs.ygap, zsmooth: heatmapAttrs.zsmooth, - zhoverformat: heatmapAttrs.zhoverformat + zhoverformat: heatmapAttrs.zhoverformat, + hovertemplate: hovertemplateAttrs({}, {keys: 'z'}) }, colorscaleAttrs('', { cLetter: 'z', diff --git a/src/traces/histogram2d/defaults.js b/src/traces/histogram2d/defaults.js index 8483915ea2e..3f0ea3f206c 100644 --- a/src/traces/histogram2d/defaults.js +++ b/src/traces/histogram2d/defaults.js @@ -29,4 +29,5 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout colorscaleDefaults( traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'} ); + coerce('hovertemplate'); }; diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index e89b969fdf7..2dfe6dc89c2 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -34,7 +34,8 @@ module.exports = extendFlat({ ncontours: contourAttrs.ncontours, contours: contourAttrs.contours, line: contourAttrs.line, - zhoverformat: histogram2dAttrs.zhoverformat + zhoverformat: histogram2dAttrs.zhoverformat, + hovertemplate: histogram2dAttrs.hovertemplate }, colorscaleAttrs('', { cLetter: 'z', diff --git a/src/traces/histogram2dcontour/defaults.js b/src/traces/histogram2dcontour/defaults.js index b8e40231075..a4792f149f7 100644 --- a/src/traces/histogram2dcontour/defaults.js +++ b/src/traces/histogram2dcontour/defaults.js @@ -31,4 +31,5 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout handleContoursDefaults(traceIn, traceOut, coerce, coerce2); handleStyleDefaults(traceIn, traceOut, coerce, layout); + coerce('hovertemplate'); }; diff --git a/src/traces/scattercarpet/attributes.js b/src/traces/scattercarpet/attributes.js index 20d61eaae3d..594b8407d05 100644 --- a/src/traces/scattercarpet/attributes.js +++ b/src/traces/scattercarpet/attributes.js @@ -10,6 +10,7 @@ var scatterAttrs = require('../scatter/attributes'); var plotAttrs = require('../../plots/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); @@ -118,4 +119,5 @@ module.exports = { flags: ['a', 'b', 'text', 'name'] }), hoveron: scatterAttrs.hoveron, + hovertemplate: hovertemplateAttrs() }; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 5d025b8c1d8..93d6feb0c58 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -766,6 +766,22 @@ describe('hover info', function() { name: 'one' }); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', '(%{x},%{y}) -- %{z}trace %{data.name}'); + }) + .then(function() { + _hover(gd, 250, 50); + assertHoverLabelContent({ + nums: '(1,3) -- 2', + name: 'trace two' + }); + + _hover(gd, 250, 300); + assertHoverLabelContent({ + nums: '(1,1) -- 5.56', + name: 'trace one' + }); + }) .catch(failTest) .then(done); }); @@ -874,6 +890,22 @@ describe('hover info', function() { name: 'one' }); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', 'f(%{x:.3f},%{y:.3f})=%{z}'); + }) + .then(function() { + _hover(gd, 250, 100); + assertHoverLabelContent({ + nums: 'f(1.000,3.000)=2', + name: 'two' + }); + + _hover(gd, 250, 300); + assertHoverLabelContent({ + nums: 'f(1.000,1.000)=5.56', + name: 'one' + }); + }) .catch(failTest) .then(done); }); @@ -912,6 +944,22 @@ describe('hover info', function() { name: 'one' }); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', 'f(%{x:.1f}, %{y:.1f})=%{z}'); + }) + .then(function() { + _hover(gd, 250, 50); + assertHoverLabelContent({ + nums: 'f(1.0, 3.0)=2', + name: '' + }); + + _hover(gd, 250, 270); + assertHoverLabelContent({ + nums: 'f(1.0, 1.0)=5.56', + name: '' + }); + }) .catch(failTest) .then(done); }); @@ -2182,6 +2230,17 @@ describe('Hover on multicategory axes', function() { }); expect(eventData.x).toEqual(['2017', 'q3']); }) + .then(function() { + return Plotly.restyle(gd, 'hovertemplate', '%{z} @ %{x} | %{y}'); + }) + .then(function() { _hover(200, 200); }) + .then(function() { + assertHoverLabelContent({ + nums: '2.303 @ 2017 - q3 | Group 3 - A', + name: 'w/ 2d z' + }); + expect(eventData.x).toEqual(['2017', 'q3']); + }) .catch(failTest) .then(done); }); From eac9b3bad79eb3c656d9ed2808aac2a574c2a511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Mon, 11 Feb 2019 12:05:34 -0500 Subject: [PATCH 4/6] add hovertemplate to scattercarpet traces --- src/components/fx/hover.js | 2 +- src/traces/scattercarpet/defaults.js | 4 +++- src/traces/scattercarpet/event_data.js | 1 + src/traces/scattercarpet/hover.js | 31 +++++++++++++++----------- test/jasmine/tests/carpet_test.js | 22 ++++++++++++++++++ 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 9c15427cd1c..337a657cf9b 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -940,7 +940,7 @@ function createHoverText(hoverData, opts, gd) { text = d[(hovermode === 'x' ? 'y' : 'x') + 'Label'] || ''; } else if(d.xLabel === undefined) { - if(d.yLabel !== undefined) text = d.yLabel; + if(d.yLabel !== undefined && d.trace.type !== 'scattercarpet') text = d.yLabel; } else if(d.yLabel === undefined) text = d.xLabel; else text = '(' + d.xLabel + ', ' + d.yLabel + ')'; diff --git a/src/traces/scattercarpet/defaults.js b/src/traces/scattercarpet/defaults.js index c330e773b96..61d28ba75b5 100644 --- a/src/traces/scattercarpet/defaults.js +++ b/src/traces/scattercarpet/defaults.js @@ -78,7 +78,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') { dfltHoverOn.push('fills'); } - coerce('hoveron', dfltHoverOn.join('+') || 'points'); + + var hoverOn = coerce('hoveron', dfltHoverOn.join('+') || 'points'); + if(hoverOn !== 'fills') coerce('hovertemplate'); Lib.coerceSelectionMarkerOpacity(traceOut, coerce); }; diff --git a/src/traces/scattercarpet/event_data.js b/src/traces/scattercarpet/event_data.js index a1ab85a1b6e..7cb05de5ded 100644 --- a/src/traces/scattercarpet/event_data.js +++ b/src/traces/scattercarpet/event_data.js @@ -13,6 +13,7 @@ module.exports = function eventData(out, pt, trace, cd, pointNumber) { out.a = cdi.a; out.b = cdi.b; + out.y = cdi.y; return out; }; diff --git a/src/traces/scattercarpet/hover.js b/src/traces/scattercarpet/hover.js index f8b47447a39..16882588ca8 100644 --- a/src/traces/scattercarpet/hover.js +++ b/src/traces/scattercarpet/hover.js @@ -48,8 +48,15 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { var trace = newPointData.trace; var carpet = trace._carpet; - var hoverinfo = cdi.hi || trace.hoverinfo; - var parts = hoverinfo.split('+'); + + var ij = carpet.ab2ij([cdi.a, cdi.b]); + var i0 = Math.floor(ij[0]); + var ti = ij[0] - i0; + var j0 = Math.floor(ij[1]); + var tj = ij[1] - j0; + var xy = carpet.evalxy([], i0, j0, ti, tj); + newPointData.yLabel = xy[1].toFixed(3); + var text = []; function textPart(ax, val) { @@ -64,21 +71,19 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { text.push(prefix + ': ' + val.toFixed(3) + ax.labelsuffix); } - if(parts.indexOf('all') !== -1) parts = ['a', 'b']; - if(parts.indexOf('a') !== -1) textPart(carpet.aaxis, cdi.a); - if(parts.indexOf('b') !== -1) textPart(carpet.baxis, cdi.b); - var ij = carpet.ab2ij([cdi.a, cdi.b]); - var i0 = Math.floor(ij[0]); - var ti = ij[0] - i0; + if(!trace.hovertemplate) { + var hoverinfo = cdi.hi || trace.hoverinfo; + var parts = hoverinfo.split('+'); - var j0 = Math.floor(ij[1]); - var tj = ij[1] - j0; + if(parts.indexOf('all') !== -1) parts = ['a', 'b']; + if(parts.indexOf('a') !== -1) textPart(carpet.aaxis, cdi.a); + if(parts.indexOf('b') !== -1) textPart(carpet.baxis, cdi.b); - var xy = carpet.evalxy([], i0, j0, ti, tj); - text.push('y: ' + xy[1].toFixed(3)); + text.push('y: ' + newPointData.yLabel); - newPointData.extraText = text.join('
'); + newPointData.extraText = text.join('
'); + } return scatterPointData; }; diff --git a/test/jasmine/tests/carpet_test.js b/test/jasmine/tests/carpet_test.js index bc8954643a6..ffc385f940c 100644 --- a/test/jasmine/tests/carpet_test.js +++ b/test/jasmine/tests/carpet_test.js @@ -678,6 +678,28 @@ describe('scattercarpet hover labels', function() { ) .then(done); }); + + it('should generate hover label with *hovertemplate*', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/scattercarpet.json')); + fig.data[5].hovertemplate = 'f(%{a}, %{b}) = %{y}scattercarpet #%{curveNumber}'; + + run( + [200, 200], fig, + [['f(0.2, 3.5) = 2.900'], 'scattercarpet #5'] + ) + .then(done); + }); + + it('should generate hover label with arrayOk *hovertemplate*', function(done) { + var fig = Lib.extendDeep({}, require('@mocks/scattercarpet.json')); + fig.data[5].hovertemplate = ['', '', '', 'f(%{a}, %{b}) = %{y:.1f}pt #%{pointNumber}']; + + run( + [200, 200], fig, + [['f(0.2, 3.5) = 3.0'], 'pt #3'] + ) + .then(done); + }); }); describe('contourcarpet plotting & editing', function() { From d25b7eb3a402c2860d9f254646afc6cf156421ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Feb 2019 12:33:31 -0500 Subject: [PATCH 5/6] add `hovertemplate` and `line.hovertemplate` to parcats traces --- src/components/fx/hovertemplate_attributes.js | 5 +- src/traces/parcats/attributes.js | 30 +++++- src/traces/parcats/defaults.js | 4 +- src/traces/parcats/parcats.js | 99 ++++++++++++++----- test/jasmine/tests/parcats_test.js | 72 ++++++++++++++ 5 files changed, 181 insertions(+), 29 deletions(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 9ccd77f28a9..094af37dad1 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -31,7 +31,6 @@ module.exports = function(opts, extra) { valType: 'string', role: 'info', dflt: '', - arrayOk: true, editType: opts.editType || 'none', description: [ 'Template string used for rendering the information that appear on hover box.', @@ -46,5 +45,9 @@ module.exports = function(opts, extra) { ].join(' ') }; + if(opts.arrayOk !== false) { + hovertemplate.arrayOk = true; + } + return hovertemplate; }; diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js index 0e06784df39..98b6ada66cd 100644 --- a/src/traces/parcats/attributes.js +++ b/src/traces/parcats/attributes.js @@ -12,6 +12,7 @@ var extendFlat = require('../../lib/extend').extendFlat; var plotAttrs = require('../../plots/attributes'); var fontAttrs = require('../../plots/font_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var domainAttrs = require('../../plots/domain').attributes; var scatterAttrs = require('../scatter/attributes'); var scatterLineAttrs = scatterAttrs.line; @@ -34,16 +35,26 @@ var line = extendFlat({ 'If `linear`, paths are composed of straight lines.', 'If `hspline`, paths are composed of horizontal curved splines' ].join(' ') - } + }, + + hovertemplate: hovertemplateAttrs({ + editType: 'plot', + arrayOk: false + }, { + keys: ['count', 'probability'], + description: [ + 'This value here applies when hovering over lines.' + ].join(' ') + }) }); module.exports = { domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}), + hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['count', 'probability'], editType: 'plot', arrayOk: false - // plotAttrs.hoverinfo description is appropriate }), hoveron: { valType: 'enumerated', @@ -58,6 +69,21 @@ module.exports = { 'If `dimension`, hover interactions take place across all categories per dimension.' ].join(' ') }, + hovertemplate: hovertemplateAttrs({ + editType: 'plot', + arrayOk: false + }, { + keys: [ + 'count', 'probability', 'category', + 'categorycount', 'colorcount', 'bandcolorcount' + ], + description: [ + 'This value here applies when hovering over dimensions.', + 'Note tath `*categorycount`, *colorcount* and *bandcolorcount*', + 'are only available when `hoveron` contains the *color* flag' + ].join(' ') + }), + arrangement: { valType: 'enumerated', values: ['perpendicular', 'freeform', 'fixed'], diff --git a/src/traces/parcats/defaults.js b/src/traces/parcats/defaults.js index 3cdfb817680..7d22714cab6 100644 --- a/src/traces/parcats/defaults.js +++ b/src/traces/parcats/defaults.js @@ -18,8 +18,9 @@ var attributes = require('./attributes'); var mergeLength = require('../parcoords/merge_length'); function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) { - coerce('line.shape'); + coerce('line.hovertemplate'); + var lineColor = coerce('line.color', layout.colorway[0]); if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) { if(lineColor.length) { @@ -96,6 +97,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout mergeLength(traceOut, dimensions, 'values', len); coerce('hoveron'); + coerce('hovertemplate'); coerce('arrangement'); coerce('bundlecolors'); coerce('sortpaths'); diff --git a/src/traces/parcats/parcats.js b/src/traces/parcats/parcats.js index 36783ccf3a1..96734034ec5 100644 --- a/src/traces/parcats/parcats.js +++ b/src/traces/parcats/parcats.js @@ -413,6 +413,7 @@ function mouseoverPath(d) { // Label var gd = d.parcatsViewModel.graphDiv; + var trace = d.parcatsViewModel.trace; var fullLayout = gd._fullLayout; var rootBBox = fullLayout._paperdiv.node().getBoundingClientRect(); var graphDivBBox = d.parcatsViewModel.graphDiv.getBoundingClientRect(); @@ -438,19 +439,27 @@ function mouseoverPath(d) { var textColor = tinycolor.mostReadable(d.model.color, ['black', 'white']); + var count = d.model.count; + var prob = count / d.parcatsViewModel.model.count; + var labels = { + countLabel: count, + probabilityLabel: prob.toFixed(3) + }; + // Build hover text var hovertextParts = []; if(d.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) { - hovertextParts.push(['Count:', d.model.count].join(' ')); + hovertextParts.push(['Count:', labels.countLabel].join(' ')); } if(d.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) { - hovertextParts.push(['P:', (d.model.count / d.parcatsViewModel.model.count).toFixed(3)].join(' ')); + hovertextParts.push(['P:', labels.probabilityLabel].join(' ')); } var hovertext = hovertextParts.join('
'); var mouseX = d3.mouse(gd)[0]; Fx.loneHover({ + trace: trace, x: hoverCenterX - rootBBox.left + graphDivBBox.left, y: hoverCenterY - rootBBox.top + graphDivBBox.top, text: hovertext, @@ -459,7 +468,15 @@ function mouseoverPath(d) { fontFamily: 'Monaco, "Courier New", monospace', fontSize: 10, fontColor: textColor, - idealAlign: mouseX < hoverCenterX ? 'right' : 'left' + idealAlign: mouseX < hoverCenterX ? 'right' : 'left', + hovertemplate: (trace.line || {}).hovertemplate, + hovertemplateLabels: labels, + eventData: [{ + data: trace._input, + fullData: trace, + count: count, + probability: prob + }] }, { container: fullLayout._hoverlayer.node(), outerContainer: fullLayout._paper.node(), @@ -715,6 +732,7 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { var catViewModel = rectSelection.datum(); var parcatsViewModel = catViewModel.parcatsViewModel; var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd]; + var trace = parcatsViewModel.trace; // Positions var hoverCenterY = rectBoundingBox.top + rectBoundingBox.height / 2; @@ -732,19 +750,27 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { hoverLabelIdealAlign = 'right'; } + var count = catViewModel.model.count; + var catLabel = catViewModel.model.categoryLabel; + var prob = count / catViewModel.parcatsViewModel.model.count; + var labels = { + countLabel: count, + categoryLabel: catLabel, + probabilityLabel: prob.toFixed(3) + }; + // Hover label text var hoverinfoParts = []; if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) { - hoverinfoParts.push(['Count:', catViewModel.model.count].join(' ')); + hoverinfoParts.push(['Count:', labels.countLabel].join(' ')); } if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) { - hoverinfoParts.push([ - 'P(' + catViewModel.model.categoryLabel + '):', - (catViewModel.model.count / catViewModel.parcatsViewModel.model.count).toFixed(3)].join(' ')); + hoverinfoParts.push(['P(' + labels.categoryLabel + '):', labels.probabilityLabel].join(' ')); } var hovertext = hoverinfoParts.join('
'); return { + trace: trace, x: hoverCenterX - rootBBox.left, y: hoverCenterY - rootBBox.top, text: hovertext, @@ -753,7 +779,16 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) { fontFamily: 'Monaco, "Courier New", monospace', fontSize: 12, fontColor: 'black', - idealAlign: hoverLabelIdealAlign + idealAlign: hoverLabelIdealAlign, + hovertemplate: trace.hovertemplate, + hovertemplateLabels: labels, + eventData: [{ + data: trace._input, + fullData: trace, + count: count, + category: catLabel, + probability: prob + }] }; } @@ -800,6 +835,7 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) { var catViewModel = bandViewModel.categoryViewModel; var parcatsViewModel = catViewModel.parcatsViewModel; var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd]; + var trace = parcatsViewModel.trace; // positions var hoverCenterY = bandBoundingBox.y + bandBoundingBox.height / 2; @@ -840,26 +876,25 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) { } }); + var pColorAndCat = bandColorCount / totalCount; + var pCatGivenColor = bandColorCount / colorCount; + var pColorGivenCat = bandColorCount / catCount; + + var labels = { + countLabel: totalCount, + categoryLabel: catLabel, + probabilityLabel: pColorAndCat.toFixed(3) + }; + // Hover label text var hoverinfoParts = []; if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) { - hoverinfoParts.push(['Count:', bandColorCount].join(' ')); + hoverinfoParts.push(['Count:', labels.countLabel].join(' ')); } if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) { - var pColorAndCatLable = 'P(color ∩ ' + catLabel + '): '; - var pColorAndCatValue = (bandColorCount / totalCount).toFixed(3); - var pColorAndCatRow = pColorAndCatLable + pColorAndCatValue; - hoverinfoParts.push(pColorAndCatRow); - - var pCatGivenColorLabel = 'P(' + catLabel + ' | color): '; - var pCatGivenColorValue = (bandColorCount / colorCount).toFixed(3); - var pCatGivenColorRow = pCatGivenColorLabel + pCatGivenColorValue; - hoverinfoParts.push(pCatGivenColorRow); - - var pColorGivenCatLabel = 'P(color | ' + catLabel + '): '; - var pColorGivenCatValue = (bandColorCount / catCount).toFixed(3); - var pColorGivenCatRow = pColorGivenCatLabel + pColorGivenCatValue; - hoverinfoParts.push(pColorGivenCatRow); + hoverinfoParts.push('P(color ∩ ' + catLabel + '): ' + labels.probabilityLabel); + hoverinfoParts.push('P(' + catLabel + ' | color): ' + pCatGivenColor.toFixed(3)); + hoverinfoParts.push('P(color | ' + catLabel + '): ' + pColorGivenCat.toFixed(3)); } var hovertext = hoverinfoParts.join('
'); @@ -868,6 +903,7 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) { var textColor = tinycolor.mostReadable(bandViewModel.color, ['black', 'white']); return { + trace: trace, x: hoverCenterX - rootBBox.left, y: hoverCenterY - rootBBox.top, // name: 'NAME', @@ -877,7 +913,19 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) { fontFamily: 'Monaco, "Courier New", monospace', fontColor: textColor, fontSize: 10, - idealAlign: hoverLabelIdealAlign + idealAlign: hoverLabelIdealAlign, + hovertemplate: trace.hovertemplate, + hovertemplateLabels: labels, + eventData: [{ + data: trace._input, + fullData: trace, + category: catLabel, + count: totalCount, + probability: pColorAndCat, + categorycount: catCount, + colorcount: colorCount, + bandcolorcount: bandColorCount + }] }; } @@ -1475,12 +1523,13 @@ function createParcatsViewModel(graphDiv, layout, wrappedParcatsModel) { if(trace.hoverinfo === 'all') { hoverinfoItems = ['count', 'probability']; } else { - hoverinfoItems = trace.hoverinfo.split('+'); + hoverinfoItems = (trace.hoverinfo || '').split('+'); } // Construct parcatsViewModel // -------------------------- var parcatsViewModel = { + trace: trace, key: trace.uid, model: parcatsModel, x: traceX, diff --git a/test/jasmine/tests/parcats_test.js b/test/jasmine/tests/parcats_test.js index 674555bccb7..8570f3ed7e0 100644 --- a/test/jasmine/tests/parcats_test.js +++ b/test/jasmine/tests/parcats_test.js @@ -1,5 +1,6 @@ var Plotly = require('@lib/index'); var Lib = require('@src/lib'); + var d3 = require('d3'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -8,6 +9,9 @@ var mouseEvent = require('../assets/mouse_event'); var click = require('../assets/click'); var delay = require('../assets/delay'); +var customAssertions = require('../assets/custom_assertions'); +var assertHoverLabelContent = customAssertions.assertHoverLabelContent; + var CALLBACK_DELAY = 500; // Testing constants @@ -1726,3 +1730,71 @@ describe('Hover events with hoveron color', function() { .then(done); }); }); + +describe('Parcats hover:', function() { + var gd; + + afterEach(destroyGraphDiv); + + function run(s, done) { + gd = createGraphDiv(); + + var fig = Lib.extendDeep({}, + s.mock || require('@mocks/parcats_basic.json') + ); + if(s.patch) fig = s.patch(fig); + + return Plotly.plot(gd, fig).then(function() { + mouseEvent('mousemove', s.pos[0], s.pos[1]); + mouseEvent('mouseover', s.pos[0], s.pos[1]); + + setTimeout(function() { + assertHoverLabelContent(s); + done(); + }, CALLBACK_DELAY); + + }) + .catch(failTest); + } + + var dimPos = [320, 310]; + var linePos = [272, 415]; + + var specs = [{ + desc: 'basic - on dimension', + pos: dimPos, + nums: 'Count: 9', + name: '' + }, { + desc: 'basic - on line', + pos: linePos, + nums: 'Count: 1', + name: '' + }, { + desc: 'with hovetemplate - on dimension', + pos: dimPos, + patch: function(fig) { + fig.data[0].hovertemplate = 'probz=%{probability:.1f}LOOK'; + return fig; + }, + nums: 'probz=1.0', + name: 'LOOK' + }, { + desc: 'with hovertemplate - on line', + pos: linePos, + patch: function(fig) { + fig.data[0].line = { + hovertemplate: 'P=%{probability}with count=%{count}' + }; + return fig; + }, + nums: 'P=0.111', + name: 'with count=1' + }]; + + specs.forEach(function(s) { + it('should generate correct hover labels ' + s.desc, function(done) { + run(s, done); + }); + }); +}); From 2f83d65569145d45d00f51fbdb92f1165716fa85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 15 Feb 2019 12:33:45 -0500 Subject: [PATCH 6/6] fixup extra , in contour attrs --- src/traces/contour/attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index ff3ba73bec6..82055b84201 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -248,7 +248,7 @@ module.exports = extendFlat({ ].join(' ') }), editType: 'plot' - }, + } }, colorscaleAttrs('', { cLetter: 'z',