From b91fd3b7427a9603d637d10f751129fc40d01b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 28 Mar 2017 11:35:00 -0400 Subject: [PATCH 01/11] [proof of concept] add 'hovertext' attribute --- src/traces/scatter/arrays_to_calcdata.js | 1 + src/traces/scatter/attributes.js | 18 +++++++++++++++++- src/traces/scatter/defaults.js | 1 + src/traces/scatter/hover.js | 4 +++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/traces/scatter/arrays_to_calcdata.js b/src/traces/scatter/arrays_to_calcdata.js index 8b81c42bcb7..378fc7613f0 100644 --- a/src/traces/scatter/arrays_to_calcdata.js +++ b/src/traces/scatter/arrays_to_calcdata.js @@ -16,6 +16,7 @@ var Lib = require('../../lib'); module.exports = function arraysToCalcdata(cd, trace) { Lib.mergeArray(trace.text, cd, 'tx'); + Lib.mergeArray(trace.hovertext, cd, 'htx'); Lib.mergeArray(trace.customdata, cd, 'data'); Lib.mergeArray(trace.textposition, cd, 'tp'); if(trace.textfont) { diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index fc1412e9f05..38b3015a4bb 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -83,7 +83,23 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' + 'this trace\'s (x,y) coordinates.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }, + hovertext: { + valType: 'string', + role: 'info', + dflt: '', + arrayOk: true, + description: [ + 'Sets hover text elements associated with each (x,y) pair.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y) coordinates.', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }, mode: { diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index c0115bb2ab7..c6c97850b98 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -38,6 +38,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('customdata'); coerce('text'); + coerce('hovertext'); coerce('mode', defaultMode); coerce('ids'); diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index 2392a23bb4f..caba649e550 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -72,7 +72,9 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { yLabelVal: di.y }); - if(di.tx) pointData.text = di.tx; + if(di.htx) pointData.text = di.htx; + else if(trace.hovertext) pointData.text = trace.hovertext; + else if(di.tx) pointData.text = di.tx; else if(trace.text) pointData.text = trace.text; ErrorBars.hoverInfo(di, trace, pointData); From 2dc0250cab484286453fbcd0a7e0162e8ad5405b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 16:28:03 -0400 Subject: [PATCH 02/11] add 'hovertext' to text_chart_arrays mock --- test/image/mocks/text_chart_arrays.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/image/mocks/text_chart_arrays.json b/test/image/mocks/text_chart_arrays.json index 90af525162c..ae614d0c267 100644 --- a/test/image/mocks/text_chart_arrays.json +++ b/test/image/mocks/text_chart_arrays.json @@ -40,6 +40,11 @@ "top left", "top left" ], + "hovertext": [ + "Hover text\nA", + "Hover text\rB", + "Hover text\r\nC" + ], "type": "scatter", "uid": "459c77" }, @@ -83,6 +88,11 @@ "bottom left", "bottom left" ], + "hovertext": [ + "Hover text G", + "Hover text H", + "Hover text I" + ], "type": "scatter", "uid": "f8361c" }, @@ -103,6 +113,11 @@ "a", "", "b" + ], + "hovertext": [ + "a (hover)", + "", + "b (hover)" ] } ], From 253a29b00ad738154559ba2cacce1fea80917d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 16:28:33 -0400 Subject: [PATCH 03/11] :lock: down 'hovertext' / 'text' logic for scatter traces --- test/jasmine/tests/scatter_test.js | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 210c418341e..7a137ce9761 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -7,6 +7,7 @@ var Lib = require('@src/lib'); var Plotly = require('@lib/index'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var customMatchers = require('../assets/custom_matchers'); var fail = require('../assets/fail_test'); describe('Test scatter', function() { @@ -372,3 +373,76 @@ describe('end-to-end scatter tests', function() { }).catch(fail).then(done); }); }); + +describe('scatter hoverPoints', function() { + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + afterEach(destroyGraphDiv); + + function _hover(gd, xval, yval, hovermode) { + return gd._fullData.map(function(trace, i) { + var cd = gd.calcdata[i]; + var subplot = gd._fullLayout._plots.xy; + + var out = Scatter.hoverPoints({ + index: false, + distance: 20, + cd: cd, + trace: trace, + xa: subplot.xaxis, + ya: subplot.yaxis + }, xval, yval, hovermode); + + return Array.isArray(out) ? out[0] : null; + }); + } + + it('should show \'hovertext\' items when present, \'text\' if not', function(done) { + var gd = createGraphDiv(); + var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays')); + + Plotly.plot(gd, mock).then(function() { + var pts = _hover(gd, 0, 1, 'x'); + + // as in 'hovertext' arrays + expect(pts[0].text).toEqual('Hover text\nA', 'hover text'); + expect(pts[1].text).toEqual('Hover text G', 'hover text'); + expect(pts[2].text).toEqual('a (hover)', 'hover text'); + + return Plotly.restyle(gd, 'hovertext', null); + }) + .then(function() { + var pts = _hover(gd, 0, 1, 'x'); + + // as in 'text' arrays + expect(pts[0].text).toEqual('Text\nA', 'hover text'); + expect(pts[1].text).toEqual('Text G', 'hover text'); + expect(pts[2].text).toEqual('a', 'hover text'); + + return Plotly.restyle(gd, 'text', ['APPLE', 'BANANA', 'ORANGE']); + }) + .then(function() { + var pts = _hover(gd, 1, 1, 'x'); + + // as in 'text' values + expect(pts[0].text).toEqual('APPLE', 'hover text'); + expect(pts[1].text).toEqual('BANANA', 'hover text'); + expect(pts[2].text).toEqual('ORANGE', 'hover text'); + + return Plotly.restyle(gd, 'hovertext', ['apple', 'banana', 'orange']); + }) + .then(function() { + var pts = _hover(gd, 1, 1, 'x'); + + // as in 'hovertext' values + expect(pts[0].text).toEqual('apple', 'hover text'); + expect(pts[1].text).toEqual('banana', 'hover text'); + expect(pts[2].text).toEqual('orange', 'hover text'); + }) + .catch(fail) + .then(done); + }); +}); From 2b02c5f7cfee8e6a978c5a93d8a08678114ee9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 16:29:02 -0400 Subject: [PATCH 04/11] add 'hovertext' to scatterternary traces --- src/traces/scatterternary/attributes.js | 14 ++++++- src/traces/scatterternary/defaults.js | 1 + test/jasmine/tests/scatterternary_test.js | 51 ++++++++++++++++------- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/traces/scatterternary/attributes.js b/src/traces/scatterternary/attributes.js index 847cc8436fe..b0c0d4a2acf 100644 --- a/src/traces/scatterternary/attributes.js +++ b/src/traces/scatterternary/attributes.js @@ -70,7 +70,19 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of strings, the items are mapped in order to the', - 'the data points in (a,b,c).' + 'the data points in (a,b,c).', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets hover text elements associated with each (a,b,c) point.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of strings, the items are mapped in order to the', + 'the data points in (a,b,c).', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }), line: { diff --git a/src/traces/scatterternary/defaults.js b/src/traces/scatterternary/defaults.js index 5095345e929..7e7cadc8d44 100644 --- a/src/traces/scatterternary/defaults.js +++ b/src/traces/scatterternary/defaults.js @@ -63,6 +63,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('sum'); coerce('text'); + coerce('hovertext'); var defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'; coerce('mode', defaultMode); diff --git a/test/jasmine/tests/scatterternary_test.js b/test/jasmine/tests/scatterternary_test.js index 21da09876f0..14c3a0ecd69 100644 --- a/test/jasmine/tests/scatterternary_test.js +++ b/test/jasmine/tests/scatterternary_test.js @@ -299,9 +299,7 @@ describe('scatterternary plot and hover', function() { describe('scatterternary hover', function() { 'use strict'; - var hoverPoints = ScatterTernary.hoverPoints; - - var gd, pointData; + var gd; beforeAll(function(done) { gd = createGraphDiv(); @@ -310,17 +308,20 @@ describe('scatterternary hover', function() { type: 'scatterternary', a: [0.1, 0.2, 0.3], b: [0.3, 0.2, 0.1], - c: [0.1, 0.4, 0.5] + c: [0.1, 0.4, 0.5], + text: ['A', 'B', 'C'] }]; Plotly.plot(gd, data).then(done); }); - beforeEach(function() { - var cd = gd.calcdata, - ternary = gd._fullLayout.ternary._subplot; + afterAll(destroyGraphDiv); + + function _hover(gd, xval, yval, hovermode) { + var cd = gd.calcdata; + var ternary = gd._fullLayout.ternary._subplot; - pointData = { + var pointData = { index: false, distance: 20, cd: cd[0], @@ -329,16 +330,16 @@ describe('scatterternary hover', function() { ya: ternary.yaxis }; - }); - - afterAll(destroyGraphDiv); + return ScatterTernary.hoverPoints(pointData, xval, yval, hovermode); + } - it('should generate extra text field on hover', function() { - var xval = 0.42, - yval = 0.37, - hovermode = 'closest'; + it('should generate extra text field on hover', function(done) { + var xval = 0.42; + var yval = 0.37; + var hovermode = 'closest'; + var scatterPointData; - var scatterPointData = hoverPoints(pointData, xval, yval, hovermode); + scatterPointData = _hover(gd, xval, yval, hovermode); expect(scatterPointData[0].extraText).toEqual( 'Component A: 0.3333333
Component B: 0.1111111
Component C: 0.5555556' @@ -346,6 +347,24 @@ describe('scatterternary hover', function() { expect(scatterPointData[0].xLabelVal).toBeUndefined(); expect(scatterPointData[0].yLabelVal).toBeUndefined(); + expect(scatterPointData[0].text).toEqual('C'); + + Plotly.restyle(gd, { + text: null, + hovertext: [['apple', 'banana', 'orange']] + }) + .then(function() { + scatterPointData = _hover(gd, xval, yval, hovermode); + + expect(scatterPointData[0].extraText).toEqual( + 'Component A: 0.3333333
Component B: 0.1111111
Component C: 0.5555556' + ); + + expect(scatterPointData[0].xLabelVal).toBeUndefined(); + expect(scatterPointData[0].yLabelVal).toBeUndefined(); + expect(scatterPointData[0].text).toEqual('orange'); + }) + .then(done); }); }); From 7b6911fcc3643878cd6e6f7ca97620c57a3fdfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 16:29:22 -0400 Subject: [PATCH 05/11] add 'hovertext' to bar traces --- src/traces/bar/arrays_to_calcdata.js | 1 + src/traces/bar/attributes.js | 1 + src/traces/bar/defaults.js | 1 + src/traces/bar/hover.js | 5 +++- test/jasmine/tests/bar_test.js | 39 +++++++++++++++++++++++++++- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/arrays_to_calcdata.js b/src/traces/bar/arrays_to_calcdata.js index 15ecc601d72..675364e9920 100644 --- a/src/traces/bar/arrays_to_calcdata.js +++ b/src/traces/bar/arrays_to_calcdata.js @@ -15,6 +15,7 @@ var mergeArray = require('../../lib').mergeArray; // arrayOk attributes, merge them into calcdata array module.exports = function arraysToCalcdata(cd, trace) { mergeArray(trace.text, cd, 'tx'); + mergeArray(trace.hovertext, cd, 'htx'); var marker = trace.marker; if(marker) { diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 95141454624..9fa333fdf05 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -49,6 +49,7 @@ module.exports = { dy: scatterAttrs.dy, text: scatterAttrs.text, + hovertext: scatterAttrs.hovertext, textposition: { valType: 'enumerated', diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 614176cfc68..6436dffb9d4 100644 --- a/src/traces/bar/defaults.js +++ b/src/traces/bar/defaults.js @@ -37,6 +37,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('width'); coerce('text'); + coerce('hovertext'); var textPosition = coerce('textposition'); diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index f65f947461c..333edef69ab 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -83,7 +83,10 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { pointData.xLabelVal = di.p; } - if(di.tx) pointData.text = di.tx; + if(di.htx) pointData.text = di.htx; + else if(trace.hovertext) pointData.text = trace.hovertext; + else if(di.tx) pointData.text = di.tx; + else if(trace.text) pointData.text = trace.text; ErrorBars.hoverInfo(di, trace, pointData); diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index d864a592528..5445f8cd8dc 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -9,6 +9,7 @@ var Axes = PlotlyInternal.Axes; var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var fail = require('../assets/fail_test'); var customMatchers = require('../assets/custom_matchers'); describe('Bar.supplyDefaults', function() { @@ -1197,7 +1198,8 @@ describe('bar hover', function() { return { style: [pt.index, pt.color, pt.xLabelVal, pt.yLabelVal], - pos: [pt.x0, pt.x1, pt.y0, pt.y1] + pos: [pt.x0, pt.x1, pt.y0, pt.y1], + text: pt.text }; } @@ -1274,6 +1276,41 @@ describe('bar hover', function() { }); }); + describe('text labels', function() { + + it('should show \'hovertext\' items when present, \'text\' if not', function(done) { + gd = createGraphDiv(); + + var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays')); + mock.data.forEach(function(t) { t.type = 'bar'; }); + + Plotly.plot(gd, mock).then(function() { + var out = _hover(gd, -0.25, 0.5, 'closest'); + expect(out.text).toEqual('Hover text\nA', 'hover text'); + + return Plotly.restyle(gd, 'hovertext', null); + }) + .then(function() { + var out = _hover(gd, -0.25, 0.5, 'closest'); + expect(out.text).toEqual('Text\nA', 'hover text'); + + return Plotly.restyle(gd, 'text', ['APPLE', 'BANANA', 'ORANGE']); + }) + .then(function() { + var out = _hover(gd, -0.25, 0.5, 'closest'); + expect(out.text).toEqual('APPLE', 'hover text'); + + return Plotly.restyle(gd, 'hovertext', ['apple', 'banana', 'orange']); + }) + .then(function() { + var out = _hover(gd, -0.25, 0.5, 'closest'); + expect(out.text).toEqual('apple', 'hover text'); + }) + .catch(fail) + .then(done); + }); + }); + }); function mockBarPlot(dataWithoutTraceType, layout) { From a7b76ee876324d2b2d1e6af3e71884d8701837da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 16:51:52 -0400 Subject: [PATCH 06/11] add 'hovertext' to scatter3d traces --- src/traces/scatter3d/attributes.js | 15 ++++++++++++++- src/traces/scatter3d/convert.js | 10 +++++++--- src/traces/scatter3d/defaults.js | 1 + test/jasmine/tests/gl_plot_interact_test.js | 18 +++++++++++++++++- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index 7c5278c25c9..ac0b957be63 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -72,9 +72,22 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y,z) coordinates.' + 'this trace\'s (x,y,z) coordinates.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' ].join(' ') }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets text elements associated with each (x,y,z) triplet.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y,z) coordinates.', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' + ].join(' ') + }), + mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D? {dflt: 'lines+markers'}), surfaceaxis: { diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js index 0b45b090f3b..f491d2b057f 100644 --- a/src/traces/scatter3d/convert.js +++ b/src/traces/scatter3d/convert.js @@ -58,8 +58,12 @@ proto.handlePick = function(selection) { selection.object = this.scatterPlot; this.scatterPlot.highlight(selection.data); } - if(this.textLabels && this.textLabels[selection.data.index] !== undefined) { - selection.textLabel = this.textLabels[selection.data.index]; + if(this.textLabels) { + if(this.textLabels[selection.data.index] !== undefined) { + selection.textLabel = this.textLabels[selection.data.index]; + } else { + selection.textLabel = this.textLabels; + } } else selection.textLabel = ''; @@ -371,7 +375,7 @@ proto.update = function(data) { opacity: data.opacity }; - this.textLabels = options.text; + this.textLabels = data.hovertext || data.text; if(this.mode.indexOf('text') !== -1) { if(this.textMarkers) this.textMarkers.update(textOptions); diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index 4f62fd3c1d8..89e0985709f 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -34,6 +34,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertext'); coerce('mode'); if(subTypes.hasLines(traceOut)) { diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index c9f4f0ae299..cb4d185d078 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -46,7 +46,7 @@ describe('Test gl3d plots', function() { mouseEvent(type, 605, 271, opts); } - function assertHoverText(xLabel, yLabel, zLabel) { + function assertHoverText(xLabel, yLabel, zLabel, textLabel) { var node = d3.selectAll('g.hovertext'); expect(node.size()).toEqual(1, 'hover text group'); @@ -54,6 +54,10 @@ describe('Test gl3d plots', function() { expect(tspan[0].innerHTML).toEqual(xLabel, 'x val'); expect(tspan[1].innerHTML).toEqual(yLabel, 'y val'); expect(tspan[2].innerHTML).toEqual(zLabel, 'z val'); + + if(textLabel) { + expect(tspan[3].innerHTML).toEqual(textLabel, 'text label'); + } } function assertEventData(x, y, z, curveNumber, pointNumber) { @@ -132,6 +136,18 @@ describe('Test gl3d plots', function() { .then(_hover) .then(function() { assertHoverText('x: 二 6, 2017', 'y: c', 'z: 100k'); + + return Plotly.restyle(gd, 'text', [['A', 'B', 'C', 'D']]); + }) + .then(_hover) + .then(function() { + assertHoverText('x: 二 6, 2017', 'y: c', 'z: 100k', 'C'); + + return Plotly.restyle(gd, 'hovertext', [['Apple', 'Banana', 'Clementine', 'Dragon fruit']]); + }) + .then(_hover) + .then(function() { + assertHoverText('x: 二 6, 2017', 'y: c', 'z: 100k', 'Clementine'); }) .then(done); From 55a51f8f72706551f2f712ca4005932219df824e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 17:41:07 -0400 Subject: [PATCH 07/11] DRY up scattermapbox 'mode' attribute declaration --- src/traces/scattermapbox/attributes.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 034f85a0b43..75d92843572 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -27,19 +27,15 @@ module.exports = { // locations // locationmode - mode: { - valType: 'flaglist', - flags: ['lines', 'markers', 'text'], + mode: extendFlat({}, scatterAttrs.mode, { dflt: 'markers', - extras: ['none'], - role: 'info', description: [ 'Determines the drawing mode for this scatter trace.', 'If the provided `mode` includes *text* then the `text` elements', 'appear at the coordinates. Otherwise, the `text` elements', 'appear on hover.' ].join(' ') - }, + }), text: extendFlat({}, scatterAttrs.text, { description: [ From aa6eadffc9dda5184c856fdcfcc4ce9b5badf2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 29 Mar 2017 17:41:21 -0400 Subject: [PATCH 08/11] add 'hoverinfo' to scattermapbox traces --- src/traces/scattermapbox/attributes.js | 14 +++++++++++++- src/traces/scattermapbox/calc.js | 8 +++++++- src/traces/scattermapbox/defaults.js | 1 + src/traces/scattermapbox/hover.js | 8 +++++++- test/jasmine/tests/scattermapbox_test.js | 14 +++++++++++++- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/traces/scattermapbox/attributes.js b/src/traces/scattermapbox/attributes.js index 75d92843572..1518093db19 100644 --- a/src/traces/scattermapbox/attributes.js +++ b/src/traces/scattermapbox/attributes.js @@ -43,7 +43,19 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) coordinates.' + 'this trace\'s (lon,lat) coordinates.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets hover text elements associated with each (lon,lat) pair', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (lon,lat) coordinates.', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' ].join(' ') }), diff --git a/src/traces/scattermapbox/calc.js b/src/traces/scattermapbox/calc.js index 6f6230851e8..ae9feb75fa8 100644 --- a/src/traces/scattermapbox/calc.js +++ b/src/traces/scattermapbox/calc.js @@ -27,7 +27,8 @@ module.exports = function calc(gd, trace) { hasColorArray = (hasMarkers && Array.isArray(marker.color)), hasSizeArray = (hasMarkers && Array.isArray(marker.size)), hasSymbolArray = (hasMarkers && Array.isArray(marker.symbol)), - hasTextArray = Array.isArray(trace.text); + hasTextArray = Array.isArray(trace.text), + hasHoverTextArray = Array.isArray(trace.hovertext); calcMarkerColorscale(trace); @@ -94,6 +95,11 @@ module.exports = function calc(gd, trace) { calcPt.tx = (typeof tx === 'string') ? tx : ''; } + if(hasHoverTextArray) { + var htx = trace.hovertext[i]; + calcPt.htx = (typeof htx === 'string') ? htx : ''; + } + calcTrace.push(calcPt); } diff --git a/src/traces/scattermapbox/defaults.js b/src/traces/scattermapbox/defaults.js index 3de5641b76a..ad884eb7f32 100644 --- a/src/traces/scattermapbox/defaults.js +++ b/src/traces/scattermapbox/defaults.js @@ -42,6 +42,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertext'); coerce('mode'); if(subTypes.hasLines(traceOut)) { diff --git a/src/traces/scattermapbox/hover.js b/src/traces/scattermapbox/hover.js index 088d35f6dc5..6caa13cd40a 100644 --- a/src/traces/scattermapbox/hover.js +++ b/src/traces/scattermapbox/hover.js @@ -86,7 +86,13 @@ function getExtraText(trace, di) { else if(hasLat) text.push('lat: ' + format(lonlat[1])); if(isAll || hoverinfo.indexOf('text') !== -1) { - var tx = di.tx || trace.text; + var tx; + + if(di.htx) tx = di.htx; + else if(trace.hovertext) tx = trace.hovertext; + else if(di.tx) tx = di.tx; + else if(trace.text) tx = trace.text; + if(!Array.isArray(tx)) text.push(tx); } diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index 716af5581a3..8f022bd1860 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -588,7 +588,7 @@ describe('@noCI scattermapbox hover', function() { }); }); - it('should generate hover label info (hoverinfo: \'text\' case)', function(done) { + it('should generate hover label info (hoverinfo: \'text\' + \'text\' array case)', function(done) { Plotly.restyle(gd, 'hoverinfo', 'text').then(function() { var xval = 11, yval = 11; @@ -599,4 +599,16 @@ describe('@noCI scattermapbox hover', function() { done(); }); }); + + it('should generate hover label info (hoverinfo: \'text\' + \'hovertext\' array case)', function(done) { + Plotly.restyle(gd, 'hovertext', ['Apple', 'Banana', 'Orange']).then(function() { + var xval = 11, + yval = 11; + + var out = hoverPoints(getPointData(gd), xval, yval)[0]; + + expect(out.extraText).toEqual('Apple'); + done(); + }); + }); }); From 4148cd4565e1858a52dd49ba9d61d8a7f8e6ad93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 31 Mar 2017 17:38:13 -0400 Subject: [PATCH 09/11] add 'hovertext' attribute to pie traces --- src/traces/pie/attributes.js | 22 +++++++- src/traces/pie/defaults.js | 1 + src/traces/pie/plot.js | 12 ++++- test/jasmine/tests/hover_pie_test.js | 75 +++++++++++++++++----------- 4 files changed, 77 insertions(+), 33 deletions(-) diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index ed2a8641fe3..f1ca7b6426c 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -79,7 +79,27 @@ module.exports = { text: { valType: 'data_array', - description: 'Sets text elements associated with each sector.' + description: [ + 'Sets text elements associated with each sector.', + 'If trace `textinfo` contains a *text* flag, these elements will seen', + 'on the chart.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' + ].join(' ') + }, + hovertext: { + valType: 'string', + role: 'info', + dflt: '', + arrayOk: true, + description: [ + 'Sets hover text elements associated with each sector.', + 'If a single string, the same string appears for', + 'all data points.', + 'If an array of string, the items are mapped in order of', + 'this trace\'s sectors.', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' + ].join(' ') }, // 'see eg:' diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 9a21628aca9..26f68f03d0a 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -45,6 +45,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout var textData = coerce('text'); var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent'); + coerce('hovertext'); coerce('hoverinfo', (layout._dataLength === 1) ? 'label+text+value+percent' : undefined); diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 8539093101f..124e96368e3 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -110,8 +110,16 @@ module.exports = function plot(gd, cdpie) { thisText = []; if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); - if(trace2.text && trace2.text[pt.i] && hoverinfo.indexOf('text') !== -1) { - thisText.push(trace2.text[pt.i]); + if(hoverinfo.indexOf('text') !== -1) { + if(trace2.hovertext) { + thisText.push( + Array.isArray(trace2.hovertext) ? + trace2.hovertext[pt.i] : + trace2.hovertext + ); + } else if(trace2.text && trace2.text[pt.i]) { + thisText.push(trace2.text[pt.i]); + } } if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)); if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators)); diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js index 2891dd31ea0..c4e71d926b8 100644 --- a/test/jasmine/tests/hover_pie_test.js +++ b/test/jasmine/tests/hover_pie_test.js @@ -6,6 +6,7 @@ var customMatchers = require('../assets/custom_matchers'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); +var d3 = require('d3'); var click = require('../assets/click'); var getClientPosition = require('../assets/get_client_position'); var mouseEvent = require('../assets/mouse_event'); @@ -175,9 +176,7 @@ describe('pie hovering', function() { }); describe('labels', function() { - - var gd, - mockCopy; + var gd, mockCopy; beforeEach(function() { gd = createGraphDiv(); @@ -186,44 +185,60 @@ describe('pie hovering', function() { afterEach(destroyGraphDiv); - it('should show the default selected values', function(done) { - - var expected = ['4', '5', '33.3%']; - - Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { + function _hover() { + mouseEvent('mouseover', 223, 143); + } - mouseEvent('mouseover', 223, 143); + function assertLabel(expected) { + var labels = d3.selectAll('.hovertext .nums .line'); - var labels = Plotly.d3.selectAll('.hovertext .nums .line'); + expect(labels[0].length).toBe(expected.length); - expect(labels[0].length).toBe(3); + labels.each(function(_, i) { + expect(d3.select(this).text()).toBe(expected[i]); + }); + } - labels.each(function(_, i) { - expect(Plotly.d3.select(this).text()).toBe(expected[i]); - }); - }).then(done); + it('should show the default selected values', function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(_hover) + .then(function() { + assertLabel(['4', '5', '33.3%']); + + return Plotly.restyle(gd, 'text', [['A', 'B', 'C', 'D', 'E']]); + }) + .then(_hover) + .then(function() { + assertLabel(['4', 'E', '5', '33.3%']); + + return Plotly.restyle(gd, 'hovertext', [[ + 'Apple', 'Banana', 'Clementine', 'Dragon Fruit', 'Eggplant' + ]]); + }) + .then(_hover) + .then(function() { + assertLabel(['4', 'Eggplant', '5', '33.3%']); + + return Plotly.restyle(gd, 'hovertext', 'SUP'); + }) + .then(_hover) + .then(function() { + assertLabel(['4', 'SUP', '5', '33.3%']); + }) + .then(done); }); it('should show the correct separators for values', function(done) { - - var expected = ['0', '12|345|678@91', '99@9%']; - mockCopy.layout.separators = '@|'; mockCopy.data[0].values[0] = 12345678.912; mockCopy.data[0].values[1] = 10000; - Plotly.plot(gd, mockCopy.data, mockCopy.layout).then(function() { - - mouseEvent('mouseover', 223, 143); - - var labels = Plotly.d3.selectAll('.hovertext .nums .line'); - - expect(labels[0].length).toBe(3); - - labels.each(function(_, i) { - expect(Plotly.d3.select(this).text()).toBe(expected[i]); - }); - }).then(done); + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(_hover) + .then(function() { + assertLabel(['0', '12|345|678@91', '99@9%']); + }) + .then(done); }); }); }); From a85946499762b819767b2d1646663e8833c8ac00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Apr 2017 16:02:55 -0400 Subject: [PATCH 10/11] add 'hoverinfo' to scattergeo traces --- src/traces/scattergeo/attributes.js | 16 ++++++- src/traces/scattergeo/defaults.js | 1 + src/traces/scattergeo/hover.js | 8 +++- test/jasmine/tests/scattergeo_test.js | 69 +++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/traces/scattergeo/attributes.js b/src/traces/scattergeo/attributes.js index 464354e35b0..1b5ddeffc19 100644 --- a/src/traces/scattergeo/attributes.js +++ b/src/traces/scattergeo/attributes.js @@ -56,9 +56,23 @@ module.exports = { 'If a single string, the same string appears over', 'all the data points.', 'If an array of string, the items are mapped in order to the', - 'this trace\'s (lon,lat) or `locations` coordinates.' + 'this trace\'s (lon,lat) or `locations` coordinates.', + 'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,', + 'these elements will be seen in the hover labels.' ].join(' ') }), + hovertext: extendFlat({}, scatterAttrs.hovertext, { + description: [ + 'Sets hover text elements associated with each (lon,lat) pair', + 'or item in `locations`.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (lon,lat) or `locations` coordinates.', + 'To be seen, trace `hoverinfo` must contain a *text* flag.' + ].join(' ') + }), + textfont: scatterAttrs.textfont, textposition: scatterAttrs.textposition, diff --git a/src/traces/scattergeo/defaults.js b/src/traces/scattergeo/defaults.js index aa0bbdd5f45..61551c3f66f 100644 --- a/src/traces/scattergeo/defaults.js +++ b/src/traces/scattergeo/defaults.js @@ -32,6 +32,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } coerce('text'); + coerce('hovertext'); coerce('mode'); if(subTypes.hasLines(traceOut)) { diff --git a/src/traces/scattergeo/hover.js b/src/traces/scattergeo/hover.js index dd609047e99..44928b875e8 100644 --- a/src/traces/scattergeo/hover.js +++ b/src/traces/scattergeo/hover.js @@ -101,7 +101,13 @@ function getExtraText(trace, pt, axis) { else if(hasLat) text.push('lat: ' + format(pt.lonlat[1])); if(hasText) { - var tx = pt.tx || trace.text; + var tx; + + if(pt.htx) tx = pt.htx; + else if(trace.hovertext) tx = trace.hovertext; + else if(pt.tx) tx = pt.tx; + else if(trace.text) tx = trace.text; + if(!Array.isArray(tx)) text.push(tx); } diff --git a/test/jasmine/tests/scattergeo_test.js b/test/jasmine/tests/scattergeo_test.js index 33581c718bd..b95c06898cf 100644 --- a/test/jasmine/tests/scattergeo_test.js +++ b/test/jasmine/tests/scattergeo_test.js @@ -1,9 +1,15 @@ +var Plotly = require('@lib'); var Plots = require('@src/plots/plots'); var Lib = require('@src/lib'); var BADNUM = require('@src/constants/numerical').BADNUM; var ScatterGeo = require('@src/traces/scattergeo'); +var d3 = require('d3'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var customMatchers = require('../assets/custom_matchers'); +var mouseEvent = require('../assets/mouse_event'); describe('Test scattergeo defaults', function() { var traceIn, @@ -221,3 +227,66 @@ describe('Test scattergeo calc', function() { ]); }); }); + +describe('Test scattergeo hover', function() { + var gd; + + // we can't mock ScatterGeo.hoverPoints + // because geo hover relies on mouse event + // to set the c2p conversion functions + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, [{ + type: 'scattergeo', + lon: [10, 20, 30], + lat: [10, 20, 30], + text: ['A', 'B', 'C'] + }]) + .then(done); + }); + + afterEach(destroyGraphDiv); + + function assertHoverLabels(expected) { + var hoverText = d3.selectAll('g.hovertext').selectAll('tspan'); + + hoverText.each(function(_, i) { + expect(this.innerHTML).toEqual(expected[i]); + }); + } + + it('should generate hover label info (base case)', function() { + mouseEvent('mousemove', 381, 221); + assertHoverLabels(['(10°, 10°)', 'A']); + }); + + it('should generate hover label info (\'text\' single value case)', function(done) { + Plotly.restyle(gd, 'text', 'text').then(function() { + mouseEvent('mousemove', 381, 221); + assertHoverLabels(['(10°, 10°)', 'text']); + }) + .then(done); + }); + + it('should generate hover label info (\'hovertext\' single value case)', function(done) { + Plotly.restyle(gd, 'hovertext', 'hovertext').then(function() { + mouseEvent('mousemove', 381, 221); + assertHoverLabels(['(10°, 10°)', 'hovertext']); + }) + .then(done); + }); + + it('should generate hover label info (\'hovertext\' array case)', function(done) { + Plotly.restyle(gd, 'hovertext', ['Apple', 'Banana', 'Orange']).then(function() { + mouseEvent('mousemove', 381, 221); + assertHoverLabels(['(10°, 10°)', 'Apple']); + }) + .then(done); + }); +}); From 1bf54e29a966dd8a203c9bd0f9dd022f3928183e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Apr 2017 16:05:47 -0400 Subject: [PATCH 11/11] use d3's .size() instead of diving into selection items --- test/jasmine/tests/hover_pie_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/hover_pie_test.js b/test/jasmine/tests/hover_pie_test.js index c4e71d926b8..b12b9194c54 100644 --- a/test/jasmine/tests/hover_pie_test.js +++ b/test/jasmine/tests/hover_pie_test.js @@ -192,7 +192,7 @@ describe('pie hovering', function() { function assertLabel(expected) { var labels = d3.selectAll('.hovertext .nums .line'); - expect(labels[0].length).toBe(expected.length); + expect(labels.size()).toBe(expected.length); labels.each(function(_, i) { expect(d3.select(this).text()).toBe(expected[i]);