From 4c197e8c6ee3dfaa513ad348c38fde2dd9414762 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 15 Oct 2018 18:11:27 -0400 Subject: [PATCH 01/53] rough first implementation for hovertemplate --- devtools/test_dashboard/server.js | 3 +- src/components/fx/hover.js | 17 +++++++++-- src/lib/index.js | 39 +++++++++++++++++++------- src/plots/attributes.js | 11 ++++++++ src/plots/plots.js | 2 ++ test/jasmine/tests/hover_label_test.js | 27 ++++++++++++++++++ test/jasmine/tests/lib_test.js | 19 +++++++++++++ 7 files changed, 105 insertions(+), 13 deletions(-) diff --git a/devtools/test_dashboard/server.js b/devtools/test_dashboard/server.js index 45f71d6e2fa..22484b1b91f 100644 --- a/devtools/test_dashboard/server.js +++ b/devtools/test_dashboard/server.js @@ -16,7 +16,8 @@ var PORT = process.argv[2] || 3000; var server = http.createServer(ecstatic({ root: constants.pathToRoot, cache: 0, - gzip: true + gzip: true, + cors: true })); // Make watchified bundle for plotly.js diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index bc75f3a1711..d73ff89c340 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -144,7 +144,8 @@ exports.loneHover = function loneHover(hoverItem, opts) { rotateLabels: false, bgColor: opts.bgColor || Color.background, container: container3, - outerContainer: outerContainer3 + outerContainer: outerContainer3, + hovertemplate: opts.hovertemplate || false }; var hoverLabel = createHoverText([pointData], fullOpts, opts.gd); @@ -200,7 +201,8 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { rotateLabels: false, bgColor: opts.bgColor || Color.background, container: container3, - outerContainer: outerContainer3 + outerContainer: outerContainer3, + hovertemplate: opts.hovertemplate || false }; var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd); @@ -950,6 +952,17 @@ function createHoverText(hoverData, opts, gd) { text = name; } + // hovertemplate + var trace = d.trace, hovertemplate = opts.hovertemplate || trace.hovertemplate || false; + if(hovertemplate) { + var i = d.index; + var obj = { + x: trace.x[i], + y: trace.y[i] + }; + text = Lib.templateString(hovertemplate, {text: text}, d.cd[i], obj, trace, gd._fullLayout); + } + // main label var tx = g.select('text.nums') .call(Drawing.font, diff --git a/src/lib/index.js b/src/lib/index.js index e0b6576d8ca..0661fff4e42 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -978,33 +978,52 @@ lib.numSeparate = function(value, separators, separatethousands) { return x1 + x2; }; -var TEMPLATE_STRING_REGEX = /%{([^\s%{}]*)}/g; +var TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)(:[^}]*)?}/g; var SIMPLE_PROPERTY_REGEX = /^\w*$/; /* - * Substitute values from an object into a string + * Substitute values from an object into a string and optionally formats them using d3-format * * Examples: * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf' + * Lib.templateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00' * - * @param {string} input string containing %{...} template strings - * @param {obj} data object containing substitution values + * @param {string} input string containing %{...:...} template strings + * @param {obj} data objects containing substitution values * * @return {string} templated string */ -lib.templateString = function(string, obj) { +lib.templateString = function(string) { + var args = arguments; // Not all that useful, but cache nestedProperty instantiation // just in case it speeds things up *slightly*: var getterCache = {}; - return string.replace(TEMPLATE_STRING_REGEX, function(dummy, key) { - if(SIMPLE_PROPERTY_REGEX.test(key)) { - return obj[key] || ''; + return string.replace(TEMPLATE_STRING_REGEX, function(dummy, key, format) { + var obj, value, i; + for(i = 1; i < args.length; i++) { + obj = args[i]; + if(SIMPLE_PROPERTY_REGEX.test(key)) { + if(obj.hasOwnProperty(key)) { + value = obj[key]; + } + } else { + // getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; + // value = getterCache[key](); + value = getterCache[key] || lib.nestedProperty(obj, key).get(); + if(value) getterCache[key] = value; + } + if(value !== undefined) break; + } + + if(value === undefined) value = ''; + + if(format) { + value = d3.format(format.replace(/^:/, ''))(value); } - getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; - return getterCache[key]() || ''; + return value; }); }; diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 653176fd260..981dbb6d24d 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -129,6 +129,17 @@ module.exports = { ].join(' ') }, hoverlabel: fxAttrs.hoverlabel, + hovertemplate: { + valType: 'string', + role: 'info', + dflt: false, + editType: 'none', + description: [ + 'Template string used for rendering the information that appear on hover box.', + 'Variables are inserted using %{variable}, for example "y: %{y}".', + 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".' + ].join(' ') + }, stream: { token: { valType: 'string', diff --git a/src/plots/plots.js b/src/plots/plots.js index 8e661a99edc..39c3393ebbe 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1170,6 +1170,8 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac Lib.coerceHoverinfo(traceIn, traceOut, layout); } + coerce('hovertemplate'); + if(!Registry.traceIs(traceOut, 'noOpacity')) coerce('opacity'); if(Registry.traceIs(traceOut, 'notLegendIsolatable')) { diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 56483c1dfa4..e7c54a28d64 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1549,6 +1549,33 @@ describe('hover info', function() { .toBeWithin(0, 1); // Be robust against floating point arithmetic and subtle future layout changes }); }); + + describe('hovertemplate', function() { + var mockCopy = Lib.extendDeep({}, mock); + + beforeEach(function(done) { + Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done); + }); + + it('should format labels according to a template string', function() { + var gd = document.getElementById('graph'); + Plotly.restyle(gd, 'data[0].hovertemplate', '%{y:$.2f}').then(function() { + Fx.hover('graph', evt, 'xy'); + + var hoverTrace = gd._hoverdata[0]; + + expect(hoverTrace.curveNumber).toEqual(0); + expect(hoverTrace.pointNumber).toEqual(17); + expect(hoverTrace.x).toEqual(0.388); + expect(hoverTrace.y).toEqual(1); + + assertHoverLabelContent({ + nums: '$1.00', + axis: '0.388' + }); + }); + }); + }); }); describe('hover info on stacked subplots', function() { diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index e4ed92e3a3c..ac6b33273d7 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2143,6 +2143,25 @@ describe('Test lib.js:', function() { it('replaces empty key with empty string', function() { expect(Lib.templateString('foo %{} %{}', {})).toEqual('foo '); }); + + it('find the first object with a given key', function() { + var obj1 = {a: 'first'}, obj2 = {a: 'second', foo: {bar: 'bar'}}; + + // Simple key + expect(Lib.templateString('foo %{a}', obj1, obj2)).toEqual('foo first'); + expect(Lib.templateString('foo %{a}', obj2, obj1)).toEqual('foo second'); + + // Nested Keys + expect(Lib.templateString('foo %{foo.bar}', obj1, obj2)).toEqual('foo bar'); + + // Nested keys with 0 + expect(Lib.templateString('y: %{y}', {y: 0}, {y: 1})).toEqual('y: 0'); + }); + + it('formats value using d3 mini-language', function() { + expect(Lib.templateString('a: %{a:.0%}', {a: 0.123})).toEqual('a: 12%'); + expect(Lib.templateString('b: %{b:2.2f}', {b: 43})).toEqual('b: 43.00'); + }); }); describe('relativeAttr()', function() { From 613c8f568a86ebe6b07f62565b0888f9e90d820c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 16 Oct 2018 19:07:05 -0400 Subject: [PATCH 02/53] supply hover's eventData inot hovertemplate --- src/components/fx/hover.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index d73ff89c340..4cdd5f76855 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -884,7 +884,7 @@ function createHoverText(hoverData, opts, gd) { // then put the text in, position the pointer to the data, // and figure out sizes - hoverLabels.each(function(d) { + hoverLabels.each(function(d, curveNumber) { var g = d3.select(this).attr('transform', ''); var name = ''; var text = ''; @@ -956,11 +956,7 @@ function createHoverText(hoverData, opts, gd) { var trace = d.trace, hovertemplate = opts.hovertemplate || trace.hovertemplate || false; if(hovertemplate) { var i = d.index; - var obj = { - x: trace.x[i], - y: trace.y[i] - }; - text = Lib.templateString(hovertemplate, {text: text}, d.cd[i], obj, trace, gd._fullLayout); + text = Lib.templateString(hovertemplate, gd._hoverdata[curveNumber], {text: text}, d.cd[i], trace, gd._fullLayout); } // main label From edf4795b9d8e0f93b30a6e81526db0e47cfdb61c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 17 Oct 2018 14:01:29 -0400 Subject: [PATCH 03/53] delete unrelated file from branch --- devtools/test_dashboard/server.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devtools/test_dashboard/server.js b/devtools/test_dashboard/server.js index 22484b1b91f..45f71d6e2fa 100644 --- a/devtools/test_dashboard/server.js +++ b/devtools/test_dashboard/server.js @@ -16,8 +16,7 @@ var PORT = process.argv[2] || 3000; var server = http.createServer(ecstatic({ root: constants.pathToRoot, cache: 0, - gzip: true, - cors: true + gzip: true })); // Make watchified bundle for plotly.js From dd628557dd231481f5c535f9ddfe168d8fe774b7 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 17 Oct 2018 18:11:16 -0400 Subject: [PATCH 04/53] update description for hovertemplate --- src/plots/attributes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 981dbb6d24d..dffd13e737f 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -136,6 +136,7 @@ module.exports = { editType: 'none', description: [ 'Template string used for rendering the information that appear on hover box.', + 'Note that this will override `hoverinfo`.', 'Variables are inserted using %{variable}, for example "y: %{y}".', 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".' ].join(' ') From a86327afefceaa5c3a9d76c238957d3ecb94889e Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 18 Oct 2018 12:10:43 -0400 Subject: [PATCH 05/53] fix hovertemplate test not being run --- test/jasmine/tests/hover_label_test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index e7c54a28d64..17972d5264b 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1557,9 +1557,10 @@ describe('hover info', function() { Plotly.plot(createGraphDiv(), mockCopy.data, mockCopy.layout).then(done); }); - it('should format labels according to a template string', function() { + it('should format labels according to a template string', function(done) { var gd = document.getElementById('graph'); - Plotly.restyle(gd, 'data[0].hovertemplate', '%{y:$.2f}').then(function() { + Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}') + .then(function() { Fx.hover('graph', evt, 'xy'); var hoverTrace = gd._hoverdata[0]; @@ -1573,7 +1574,9 @@ describe('hover info', function() { nums: '$1.00', axis: '0.388' }); - }); + }) + .catch(failTest) + .then(done); }); }); }); From 1fde3aa77d191c3b14051afb54d9d0f2cfb8dba4 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 18 Oct 2018 13:08:16 -0400 Subject: [PATCH 06/53] for now, only offer hoverdata and trace objects to hovertemplate --- src/components/fx/hover.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 4cdd5f76855..e8e2f30990d 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -955,8 +955,7 @@ function createHoverText(hoverData, opts, gd) { // hovertemplate var trace = d.trace, hovertemplate = opts.hovertemplate || trace.hovertemplate || false; if(hovertemplate) { - var i = d.index; - text = Lib.templateString(hovertemplate, gd._hoverdata[curveNumber], {text: text}, d.cd[i], trace, gd._fullLayout); + text = Lib.templateString(hovertemplate, gd._hoverdata[curveNumber], trace); } // main label From d263cceb5f411bb6fcfc960155f681753ac07d67 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 18 Oct 2018 14:32:54 -0400 Subject: [PATCH 07/53] templateString evaluates attributes with a dot in their name --- src/lib/index.js | 11 ++++++----- test/jasmine/tests/lib_test.js | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 0661fff4e42..52cc4ae8995 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1005,11 +1005,12 @@ lib.templateString = function(string) { var obj, value, i; for(i = 1; i < args.length; i++) { obj = args[i]; - if(SIMPLE_PROPERTY_REGEX.test(key)) { - if(obj.hasOwnProperty(key)) { - value = obj[key]; - } - } else { + if(obj.hasOwnProperty(key)) { + value = obj[key]; + break; + } + + if(!SIMPLE_PROPERTY_REGEX.test(key)) { // getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; // value = getterCache[key](); value = getterCache[key] || lib.nestedProperty(obj, key).get(); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index ac6b33273d7..8c44091a9db 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2124,6 +2124,10 @@ describe('Test lib.js:', function() { expect(Lib.templateString('foo %{bar}', {bar: 'baz'})).toEqual('foo baz'); }); + it('evaluates attributes with a dot in their name', function() { + expect(Lib.templateString('%{marker.size}', {'marker.size': 12}, {marker: {size: 14}})).toEqual('12'); + }); + it('evaluates nested properties', function() { expect(Lib.templateString('foo %{bar.baz}', {bar: {baz: 'asdf'}})).toEqual('foo asdf'); }); @@ -2144,7 +2148,7 @@ describe('Test lib.js:', function() { expect(Lib.templateString('foo %{} %{}', {})).toEqual('foo '); }); - it('find the first object with a given key', function() { + it('uses the value from the first object with the specified key', function() { var obj1 = {a: 'first'}, obj2 = {a: 'second', foo: {bar: 'bar'}}; // Simple key From 040208f1ba417b46d1bca9d1342c639d5e01991e Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 5 Nov 2018 12:15:36 -0500 Subject: [PATCH 08/53] do not coerce hoverinfo if hovertemplate is defined --- src/plots/plots.js | 2 +- test/jasmine/tests/plots_test.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 39c3393ebbe..accd37abd8a 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1167,7 +1167,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac if(_module) { _module.supplyDefaults(traceIn, traceOut, defaultColor, layout); - Lib.coerceHoverinfo(traceIn, traceOut, layout); + if(!traceOut.hovertemplate) Lib.coerceHoverinfo(traceIn, traceOut, layout); } coerce('hovertemplate'); diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index 6e00acd23d7..49c7c33ed54 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -251,6 +251,14 @@ describe('Test Plots', function() { traceOut = supplyTraceDefaults(traceIn, {type: 'scatter'}, 0, layout); expect(traceOut.hoverinfo).toEqual('name'); }); + + it('only if hovertemplate is not defined', function() { + layout._dataLength = 1; + + traceIn = {}; + traceOut = supplyTraceDefaults(traceIn, {type: 'scatter', hovertemplate: '%{y}'}, 0, layout); + expect(traceOut.hoverinfo).toBeUndefined(); + }); }); }); From 3eaa3b98caa6db91498ea032c4cdb603a7a705ff Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 5 Nov 2018 14:37:00 -0500 Subject: [PATCH 09/53] replace templateString with the more specific hovertemplateString --- src/components/fx/hover.js | 2 +- src/lib/index.js | 41 ++++++++++++++++++++++++++---- test/jasmine/tests/lib_test.js | 46 ++++++++++++++++++++++++++-------- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index e8e2f30990d..9f55e7926a5 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -955,7 +955,7 @@ function createHoverText(hoverData, opts, gd) { // hovertemplate var trace = d.trace, hovertemplate = opts.hovertemplate || trace.hovertemplate || false; if(hovertemplate) { - text = Lib.templateString(hovertemplate, gd._hoverdata[curveNumber], trace); + text = Lib.hovertemplateString(hovertemplate, d, gd._hoverdata[curveNumber], trace); } // main label diff --git a/src/lib/index.js b/src/lib/index.js index 52cc4ae8995..d888e3ea39b 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -982,7 +982,35 @@ var TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)(:[^}]*)?}/g; var SIMPLE_PROPERTY_REGEX = /^\w*$/; /* - * Substitute values from an object into a string and optionally formats them using d3-format + * Substitute values from an object into a string + * + * Examples: + * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' + * Lib.templateString('name: %{trace[0].name}', {trace: [{name: 'asdf'}]}) --> 'name: asdf' + * + * @param {string} input string containing %{...} template strings + * @param {obj} data object containing substitution values + * + * @return {string} templated string + */ + +lib.templateString = function(string, obj) { + // Not all that useful, but cache nestedProperty instantiation + // just in case it speeds things up *slightly*: + var getterCache = {}; + + return string.replace(TEMPLATE_STRING_REGEX, function(dummy, key) { + if(SIMPLE_PROPERTY_REGEX.test(key)) { + return obj[key] || ''; + } + getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; + return getterCache[key]() || ''; + }); +}; + +/* + * Substitute values from an object into a string and optionally formats them using d3-format, + * or fallback to associated labels. * * Examples: * Lib.templateString('name: %{trace}', {trace: 'asdf'}) --> 'name: asdf' @@ -990,20 +1018,21 @@ var SIMPLE_PROPERTY_REGEX = /^\w*$/; * Lib.templateString('price: %{y:$.2f}', {y: 1}) --> 'price: $1.00' * * @param {string} input string containing %{...:...} template strings + * @param {obj} data object containing fallback text when no formatting is specified, ex.: {yLabel: 'formattedYValue'} * @param {obj} data objects containing substitution values * * @return {string} templated string */ -lib.templateString = function(string) { +lib.hovertemplateString = function(string, labels) { var args = arguments; // Not all that useful, but cache nestedProperty instantiation // just in case it speeds things up *slightly*: var getterCache = {}; - return string.replace(TEMPLATE_STRING_REGEX, function(dummy, key, format) { + return string.replace(TEMPLATE_STRING_REGEX, function(match, key, format) { var obj, value, i; - for(i = 1; i < args.length; i++) { + for(i = 2; i < args.length; i++) { obj = args[i]; if(obj.hasOwnProperty(key)) { value = obj[key]; @@ -1019,10 +1048,12 @@ lib.templateString = function(string) { if(value !== undefined) break; } - if(value === undefined) value = ''; + if(value === undefined) value = match; if(format) { value = d3.format(format.replace(/^:/, ''))(value); + } else { + if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label']; } return value; }); diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index 8c44091a9db..d67159bbef4 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2124,10 +2124,6 @@ describe('Test lib.js:', function() { expect(Lib.templateString('foo %{bar}', {bar: 'baz'})).toEqual('foo baz'); }); - it('evaluates attributes with a dot in their name', function() { - expect(Lib.templateString('%{marker.size}', {'marker.size': 12}, {marker: {size: 14}})).toEqual('12'); - }); - it('evaluates nested properties', function() { expect(Lib.templateString('foo %{bar.baz}', {bar: {baz: 'asdf'}})).toEqual('foo asdf'); }); @@ -2147,24 +2143,54 @@ describe('Test lib.js:', function() { it('replaces empty key with empty string', function() { expect(Lib.templateString('foo %{} %{}', {})).toEqual('foo '); }); + }); + + describe('hovertemplateString', function() { + it('evaluates attributes', function() { + expect(Lib.hovertemplateString('foo %{bar}', {}, {bar: 'baz'})).toEqual('foo baz'); + }); + + it('evaluates attributes with a dot in their name', function() { + expect(Lib.hovertemplateString('%{marker.size}', {}, {'marker.size': 12}, {marker: {size: 14}})).toEqual('12'); + }); + + it('evaluates nested properties', function() { + expect(Lib.hovertemplateString('foo %{bar.baz}', {}, {bar: {baz: 'asdf'}})).toEqual('foo asdf'); + }); + + it('evaluates array nested properties', function() { + expect(Lib.hovertemplateString('foo %{bar[0].baz}', {}, {bar: [{baz: 'asdf'}]})).toEqual('foo asdf'); + }); + + it('subtitutes multiple matches', function() { + expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, {group: 'asdf', trace: 'jkl;'})).toEqual('foo asdf jkl;'); + }); + + it('replaces missing matches with template string', function() { + expect(Lib.hovertemplateString('foo %{group} %{trace}', {}, {group: 1})).toEqual('foo 1 %{trace}'); + }); it('uses the value from the first object with the specified key', function() { var obj1 = {a: 'first'}, obj2 = {a: 'second', foo: {bar: 'bar'}}; // Simple key - expect(Lib.templateString('foo %{a}', obj1, obj2)).toEqual('foo first'); - expect(Lib.templateString('foo %{a}', obj2, obj1)).toEqual('foo second'); + expect(Lib.hovertemplateString('foo %{a}', {}, obj1, obj2)).toEqual('foo first'); + expect(Lib.hovertemplateString('foo %{a}', {}, obj2, obj1)).toEqual('foo second'); // Nested Keys - expect(Lib.templateString('foo %{foo.bar}', obj1, obj2)).toEqual('foo bar'); + expect(Lib.hovertemplateString('foo %{foo.bar}', {}, obj1, obj2)).toEqual('foo bar'); // Nested keys with 0 - expect(Lib.templateString('y: %{y}', {y: 0}, {y: 1})).toEqual('y: 0'); + expect(Lib.hovertemplateString('y: %{y}', {}, {y: 0}, {y: 1})).toEqual('y: 0'); }); it('formats value using d3 mini-language', function() { - expect(Lib.templateString('a: %{a:.0%}', {a: 0.123})).toEqual('a: 12%'); - expect(Lib.templateString('b: %{b:2.2f}', {b: 43})).toEqual('b: 43.00'); + expect(Lib.hovertemplateString('a: %{a:.0%}', {}, {a: 0.123})).toEqual('a: 12%'); + expect(Lib.hovertemplateString('b: %{b:2.2f}', {}, {b: 43})).toEqual('b: 43.00'); + }); + + it('looks for default label if no format is provided', function() { + expect(Lib.hovertemplateString('y: %{y}', {yLabel: '0.1'}, {y: 0.123})).toEqual('y: 0.1'); }); }); From 0084efb4477dd5249ab2d00d7ec97159678369ae Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 5 Nov 2018 15:57:46 -0500 Subject: [PATCH 10/53] hovertemplate: add warning if variable can't be found --- src/lib/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/index.js b/src/lib/index.js index d888e3ea39b..23e5e4b9e16 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1048,7 +1048,10 @@ lib.hovertemplateString = function(string, labels) { if(value !== undefined) break; } - if(value === undefined) value = match; + if(value === undefined) { + lib.warn('Variable \'' + key + '\' in hovertemplate could not be found!'); + value = match; + } if(format) { value = d3.format(format.replace(/^:/, ''))(value); From 046d3679d4cf84011a9f22bc8235b536858904d4 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 5 Nov 2018 19:28:29 -0500 Subject: [PATCH 11/53] coerce hovertemplate at trace-level, starting with scatter(gl) --- src/components/fx/hover.js | 17 ++++++++++------- src/plots/plots.js | 2 -- src/traces/scatter/attributes.js | 2 ++ src/traces/scatter/defaults.js | 1 + src/traces/scatter/hover.js | 6 ++++-- src/traces/scattergl/attributes.js | 1 + src/traces/scattergl/defaults.js | 1 + src/traces/scattergl/index.js | 4 +++- test/jasmine/tests/hover_label_test.js | 1 + 9 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 9f55e7926a5..c68e95b42c2 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -765,11 +765,13 @@ function createHoverText(hoverData, opts, gd) { if(allHaveZ && hoverData[i].zLabel === undefined) allHaveZ = false; traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo; - var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+'); - if(parts.indexOf('all') === -1 && - parts.indexOf(hovermode) === -1) { - showCommonLabel = false; - break; + if(traceHoverinfo) { + var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+'); + if(parts.indexOf('all') === -1 && + parts.indexOf(hovermode) === -1) { + showCommonLabel = false; + break; + } } } @@ -953,7 +955,8 @@ function createHoverText(hoverData, opts, gd) { } // hovertemplate - var trace = d.trace, hovertemplate = opts.hovertemplate || trace.hovertemplate || false; + var trace = d.trace; + var hovertemplate = opts.hovertemplate || hoverData[curveNumber].hovertemplate || false; if(hovertemplate) { text = Lib.hovertemplateString(hovertemplate, d, gd._hoverdata[curveNumber], trace); } @@ -1356,7 +1359,7 @@ function cleanPoint(d, hovermode) { var infomode = d.hoverinfo || d.trace.hoverinfo; - if(infomode !== 'all') { + if(infomode && infomode !== 'all') { infomode = Array.isArray(infomode) ? infomode : infomode.split('+'); if(infomode.indexOf('x') === -1) d.xLabel = undefined; if(infomode.indexOf('y') === -1) d.yLabel = undefined; diff --git a/src/plots/plots.js b/src/plots/plots.js index accd37abd8a..8503124e7cd 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1170,8 +1170,6 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac if(!traceOut.hovertemplate) Lib.coerceHoverinfo(traceIn, traceOut, layout); } - coerce('hovertemplate'); - if(!Registry.traceIs(traceOut, 'noOpacity')) coerce('opacity'); if(Registry.traceIs(traceOut, 'notLegendIsolatable')) { diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index b865858ad66..13b3732b370 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -8,6 +8,7 @@ 'use strict'; +var plotAttrs = require('../../plots/attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var fontAttrs = require('../../plots/font_attributes'); @@ -203,6 +204,7 @@ module.exports = { 'or text, then the default is *fills*, otherwise it is *points*.' ].join(' ') }, + hovertemplate: extendFlat({}, plotAttrs.hovertemplate), line: { color: { valType: 'color', diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 5668c9ffd9d..7fb369635ae 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 'lines+markers' : 'lines'; coerce('text'); coerce('hovertext'); + coerce('hovertemplate'); coerce('mode', defaultMode); if(subTypes.hasLines(traceOut)) { diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index 6efe4ca9053..f133f062811 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -93,7 +93,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { y1: yc + rad, yLabelVal: yLabelVal, - spikeDistance: dxy(di) + spikeDistance: dxy(di), + hovertemplate: trace.hovertemplate }); fillHoverText(di, trace, pointData); @@ -177,7 +178,8 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { x1: xmax, y0: yAvg, y1: yAvg, - color: color + color: color, + hovertemplate: '%{name}' }); delete pointData.index; diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index aebdf2b4146..8fd27a758ee 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -97,3 +97,4 @@ var attrs = module.exports = overrideAll({ }, 'calc', 'nested'); attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; +attrs.hovertemplate = extendFlat({}, plotAttrs.hovertemplate); diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js index 1120edea4f1..01a92a4ef48 100644 --- a/src/traces/scattergl/defaults.js +++ b/src/traces/scattergl/defaults.js @@ -37,6 +37,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('text'); coerce('hovertext'); + coerce('hovertemplate'); coerce('mode', defaultMode); if(subTypes.hasLines(traceOut)) { diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c3068932a5b..d7c6e34cb29 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -830,7 +830,9 @@ function calcHover(pointData, x, y, trace) { cd: fakeCd, distance: minDist, - spikeDistance: dxy + spikeDistance: dxy, + + hovertemplate: trace.hovertemplate }); if(di.htx) pointData.text = di.htx; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 17972d5264b..bd61ead510a 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1572,6 +1572,7 @@ describe('hover info', function() { assertHoverLabelContent({ nums: '$1.00', + name: 'PV learning ...', axis: '0.388' }); }) From 59083adea98d3b3ecc13168d1743cd81796be7f5 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 6 Nov 2018 11:19:12 -0500 Subject: [PATCH 12/53] add tag to hovertemplate for secondary labels --- src/components/fx/hover.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index c68e95b42c2..e622500910b 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -959,6 +959,12 @@ function createHoverText(hoverData, opts, gd) { var hovertemplate = opts.hovertemplate || hoverData[curveNumber].hovertemplate || false; if(hovertemplate) { text = Lib.hovertemplateString(hovertemplate, d, gd._hoverdata[curveNumber], trace); + + var EXTRA_STRING_REGEX = /(.*)<\/extra>/; + text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { + name = extra; // Assign name for secondary text label + return ''; // Remove from main text label + }); } // main label From 266d43a90dcb2ecaee905f703520280e43b905f5 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 6 Nov 2018 18:56:27 -0500 Subject: [PATCH 13/53] add URL to d3-format documentation --- src/plots/attributes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plots/attributes.js b/src/plots/attributes.js index dffd13e737f..8590251a5b4 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -138,7 +138,8 @@ module.exports = { 'Template string used for rendering the information that appear on hover box.', 'Note that this will override `hoverinfo`.', 'Variables are inserted using %{variable}, for example "y: %{y}".', - 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".' + 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', + 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.' ].join(' ') }, stream: { From 108b68a2bc6f5e8db537ea87c3b2215f7bb20f64 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 6 Nov 2018 18:57:46 -0500 Subject: [PATCH 14/53] initial hovertemplate support for pie --- src/components/fx/hover.js | 13 ++++--- src/traces/pie/attributes.js | 1 + src/traces/pie/defaults.js | 1 + src/traces/pie/event_data.js | 1 + src/traces/pie/plot.js | 19 +++++----- test/jasmine/tests/hover_label_test.js | 4 +-- test/jasmine/tests/pie_test.js | 49 +++++++++++++++++++++++++- 7 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index e622500910b..89e9425aacd 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -145,9 +145,9 @@ exports.loneHover = function loneHover(hoverItem, opts) { bgColor: opts.bgColor || Color.background, container: container3, outerContainer: outerContainer3, - hovertemplate: opts.hovertemplate || false + hovertemplate: opts.hovertemplate || false, + eventData: opts.eventData || {} }; - var hoverLabel = createHoverText([pointData], fullOpts, opts.gd); alignHoverText(hoverLabel, fullOpts.rotateLabels); @@ -202,7 +202,8 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { bgColor: opts.bgColor || Color.background, container: container3, outerContainer: outerContainer3, - hovertemplate: opts.hovertemplate || false + hovertemplate: opts.hovertemplate || false, + eventData: opts.eventData || {} }; var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd); @@ -686,7 +687,8 @@ function _hover(gd, evt, subplot, noHoverEvent) { container: fullLayout._hoverlayer, outerContainer: fullLayout._paperdiv, commonLabelOpts: fullLayout.hoverlabel, - hoverdistance: fullLayout.hoverdistance + hoverdistance: fullLayout.hoverdistance, + eventData: gd._hoverdata }; var hoverLabels = createHoverText(hoverData, labelOpts, gd); @@ -957,8 +959,9 @@ function createHoverText(hoverData, opts, gd) { // hovertemplate var trace = d.trace; var hovertemplate = opts.hovertemplate || hoverData[curveNumber].hovertemplate || false; + if(hovertemplate) { - text = Lib.hovertemplateString(hovertemplate, d, gd._hoverdata[curveNumber], trace); + text = Lib.hovertemplateString(hovertemplate, d, opts.eventData[curveNumber] || {}, trace); var EXTRA_STRING_REGEX = /(.*)<\/extra>/; text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index d81ba172554..07280d67e1c 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -158,6 +158,7 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), + hovertemplate: extendFlat({}, plotAttrs.hovertemplate), textposition: { valType: 'enumerated', role: 'info', diff --git a/src/traces/pie/defaults.js b/src/traces/pie/defaults.js index 2a03a68075a..fd720c8a042 100644 --- a/src/traces/pie/defaults.js +++ b/src/traces/pie/defaults.js @@ -51,6 +51,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('hovertemplate'); if(textInfo && textInfo !== 'none') { var textPosition = coerce('textposition'), diff --git a/src/traces/pie/event_data.js b/src/traces/pie/event_data.js index efaa3e8824d..1fa5a7206d0 100644 --- a/src/traces/pie/event_data.js +++ b/src/traces/pie/event_data.js @@ -24,6 +24,7 @@ module.exports = function eventData(pt, trace) { label: pt.label, color: pt.color, value: pt.v, + percent: pt.percent, // pt.v (and pt.i below) for backward compatibility v: pt.v diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 27c3518a705..2c0312134fb 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -99,20 +99,21 @@ module.exports = function plot(gd, cdpie) { // in case we dragged over the pie from another subplot, // or if hover is turned off - if(hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo) { + if(trace2.hovertemplate || (hoverinfo !== 'none' && hoverinfo !== 'skip' && hoverinfo)) { var rInscribed = getInscribedRadiusFraction(pt, cd0); var hoverCenterX = cx + pt.pxmid[0] * (1 - rInscribed); var hoverCenterY = cy + pt.pxmid[1] * (1 - rInscribed); var separators = fullLayout.separators; var thisText = []; - if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); - if(hoverinfo.indexOf('text') !== -1) { + if(hoverinfo && hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); + if(hoverinfo && hoverinfo.indexOf('text') !== -1) { var texti = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); if(texti) thisText.push(texti); } - 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)); + if(hoverinfo && hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)); + pt.percent = pt.v / cd0.vTotal; + if(hoverinfo && hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.percent, separators)); var hoverLabel = trace.hoverlabel; var hoverFont = hoverLabel.font; @@ -122,17 +123,19 @@ module.exports = function plot(gd, cdpie) { x1: hoverCenterX + rInscribed * cd0.r, y: hoverCenterY, text: thisText.join('
'), - name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined, + name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined, idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right', color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color, borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts), fontFamily: helpers.castOption(hoverFont.family, pt.pts), fontSize: helpers.castOption(hoverFont.size, pt.pts), - fontColor: helpers.castOption(hoverFont.color, pt.pts) + fontColor: helpers.castOption(hoverFont.color, pt.pts), }, { container: fullLayout2._hoverlayer.node(), outerContainer: fullLayout2._paper.node(), - gd: gd + gd: gd, + hovertemplate: trace2.hovertemplate, + eventData: [eventData(pt, trace2)] }); hasHoverLabel = true; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index bd61ead510a..5b676d43ff5 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1559,7 +1559,7 @@ describe('hover info', function() { it('should format labels according to a template string', function(done) { var gd = document.getElementById('graph'); - Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}') + Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}trace 0') .then(function() { Fx.hover('graph', evt, 'xy'); @@ -1572,7 +1572,7 @@ describe('hover info', function() { assertHoverLabelContent({ nums: '$1.00', - name: 'PV learning ...', + name: 'trace 0', axis: '0.388' }); }) diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index 24c85e494e1..d4300053d66 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -563,7 +563,7 @@ describe('pie hovering', function() { 'curveNumber', 'pointNumber', 'pointNumbers', 'data', 'fullData', 'label', 'color', 'value', - 'i', 'v' + 'i', 'v', 'percent' ]; expect(Object.keys(hoverData.points[0]).sort()).toEqual(fields.sort()); @@ -731,6 +731,53 @@ describe('pie hovering', function() { }) .then(done); }); + + it('should use hovertemplate if specified', function(done) { + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(_hover) + .then(function() { + assertLabel( + ['4', '5', '33.3%'].join('\n'), + ['rgb(31, 119, 180)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'], + 'initial' + ); + + return Plotly.restyle(gd, 'hovertemplate', '%{value}'); + }) + .then(_hover) + .then(function() { + assertLabel( + ['5'].join('\n'), + null, + 'hovertemplate %{value}' + ); + + return Plotly.restyle(gd, { + 'text': [['A', 'B', 'C', 'D', 'E']], + 'hovertemplate': '%{text}' + }); + }) + .then(_hover) + .then(function() { + assertLabel( + ['E'].join('\n'), + null, + 'hovertemplate %{text}' + ); + + return Plotly.restyle(gd, 'hovertemplate', '%{percent:.1%}'); + }) + .then(_hover) + .then(function() { + assertLabel( + ['33.3%'].join('\n'), + null, + 'hovertemplate %{percent}' + ); + }) + .catch(fail) + .then(done); + }); }); }); From eb1a94a428a07a8b595c96d65337a63d4bfa712f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 11:25:46 -0500 Subject: [PATCH 15/53] pie hovertemplate test %{label} --- test/jasmine/tests/pie_test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index d4300053d66..76397aa6898 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -774,6 +774,16 @@ describe('pie hovering', function() { null, 'hovertemplate %{percent}' ); + + return Plotly.restyle(gd, 'hovertemplate', '%{label}'); + }) + .then(_hover) + .then(function() { + assertLabel( + ['4'].join('\n'), + null, + 'hovertemplate %{label}' + ); }) .catch(fail) .then(done); From 8214d36c5d0aca58034c1ed406669a02423a6eb8 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 13:28:56 -0500 Subject: [PATCH 16/53] bar support with limited test --- src/traces/bar/attributes.js | 1 + src/traces/bar/defaults.js | 1 + src/traces/bar/hover.js | 1 + test/jasmine/tests/bar_test.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index a84f5efd0d7..e7a9e97126e 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -59,6 +59,7 @@ module.exports = { text: scatterAttrs.text, hovertext: scatterAttrs.hovertext, + hovertemplate: scatterAttrs.hovertemplate, textposition: { valType: 'enumerated', diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js index 02a2846737d..f5fa6e044b6 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('text'); coerce('hovertext'); + coerce('hovertemplate'); var textPosition = coerce('textposition'); diff --git a/src/traces/bar/hover.js b/src/traces/bar/hover.js index 5e5b5d9f9ec..b32ad58b54e 100644 --- a/src/traces/bar/hover.js +++ b/src/traces/bar/hover.js @@ -137,6 +137,7 @@ function hoverPoints(pointData, xval, yval, hovermode) { fillHoverText(di, trace, pointData); Registry.getComponentMethod('errorbars', 'hoverInfo')(di, trace, pointData); + pointData.hovertemplate = trace.hovertemplate; return [pointData]; } diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 609231fffda..76e09fb60bc 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -16,6 +16,8 @@ var supplyAllDefaults = require('../assets/supply_defaults'); var customAssertions = require('../assets/custom_assertions'); var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; +var assertHoverLabelContent = customAssertions.assertHoverLabelContent; +var Fx = require('@src/components/fx'); var d3 = require('d3'); @@ -1602,6 +1604,34 @@ describe('bar hover', function() { .catch(failTest) .then(done); }); + + it('should use hovertemplate if specified', function(done) { + gd = createGraphDiv(); + + var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays')); + mock.data.forEach(function(t) { + t.type = 'bar'; + t.hovertemplate = '%{y}'; + }); + + function _hover() { + var evt = { xpx: 125, ypx: 150 }; + Fx.hover('graph', evt, 'xy'); + } + + Plotly.plot(gd, mock) + .then(_hover) + .then(function() { + assertHoverLabelContent({ + nums: ['1', '2', '1.5'], + name: ['', '', ''], + axis: '0' + }); + // return Plotly.restyle(gd, 'text', ['APPLE', 'BANANA', 'ORANGE']); + }) + .catch(failTest) + .then(done); + }); }); describe('with special width/offset combinations', function() { From bb97abeff0c37da47b30a9e32b7df33f2bdbb617 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 13:43:46 -0500 Subject: [PATCH 17/53] add hovertemplate support in histogram --- src/traces/histogram/attributes.js | 2 ++ src/traces/histogram/defaults.js | 2 ++ src/traces/histogram/hover.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 80bf57e1585..33184003ac7 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -185,6 +185,8 @@ module.exports = { ].join(' ') }, + hovertemplate: barAttrs.hovertemplate, + marker: barAttrs.marker, selected: barAttrs.selected, diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index 23ef933ba18..9d5d3715461 100644 --- a/src/traces/histogram/defaults.js +++ b/src/traces/histogram/defaults.js @@ -56,6 +56,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout // autobin(x|y) are only included here to appease Plotly.validate coerce('autobin' + sampleLetter); + coerce('hovertemplate'); + handleStyleDefaults(traceIn, traceOut, coerce, defaultColor, layout); // override defaultColor for error bars with defaultLine diff --git a/src/traces/histogram/hover.js b/src/traces/histogram/hover.js index cf79f533d6a..b4fde83ca04 100644 --- a/src/traces/histogram/hover.js +++ b/src/traces/histogram/hover.js @@ -27,5 +27,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], di.ph0, di.ph1); } + if(trace.hovermplate) pointData.hovertemplate = trace.hovertemplate; + return pts; }; From 22e3a9d32cb94e0fa61d620a9cd05cecbc62ef3d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 20:19:17 -0500 Subject: [PATCH 18/53] pie returns default formatted value --- src/components/fx/hover.js | 6 ++++-- src/traces/pie/plot.js | 8 ++++++-- test/jasmine/tests/pie_test.js | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 89e9425aacd..e6704beb1fd 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -146,6 +146,7 @@ exports.loneHover = function loneHover(hoverItem, opts) { container: container3, outerContainer: outerContainer3, hovertemplate: opts.hovertemplate || false, + hovertemplateLabels: opts.hovertemplateLabels || false, eventData: opts.eventData || {} }; var hoverLabel = createHoverText([pointData], fullOpts, opts.gd); @@ -203,6 +204,7 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { container: container3, outerContainer: outerContainer3, hovertemplate: opts.hovertemplate || false, + hovertemplateLabels: opts.hovertemplateLabels || false, eventData: opts.eventData || {} }; @@ -959,9 +961,9 @@ function createHoverText(hoverData, opts, gd) { // hovertemplate var trace = d.trace; var hovertemplate = opts.hovertemplate || hoverData[curveNumber].hovertemplate || false; - + var hovertemplateLabels = opts.hovertemplateLabels || d; if(hovertemplate) { - text = Lib.hovertemplateString(hovertemplate, d, opts.eventData[curveNumber] || {}, trace); + text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, opts.eventData[curveNumber] || {}, trace); var EXTRA_STRING_REGEX = /(.*)<\/extra>/; text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 2c0312134fb..4c0c26f12a0 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -111,9 +111,12 @@ module.exports = function plot(gd, cdpie) { var texti = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); if(texti) thisText.push(texti); } - if(hoverinfo && hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators)); + pt.value = pt.v; + pt.valueLabel = helpers.formatPieValue(pt.v, separators); + if(hoverinfo && hoverinfo.indexOf('value') !== -1) thisText.push(pt.valueLabel); pt.percent = pt.v / cd0.vTotal; - if(hoverinfo && hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.percent, separators)); + pt.percentLabel = helpers.formatPiePercent(pt.percent, separators); + if(hoverinfo && hoverinfo.indexOf('percent') !== -1) thisText.push(pt.percentLabel); var hoverLabel = trace.hoverlabel; var hoverFont = hoverLabel.font; @@ -135,6 +138,7 @@ module.exports = function plot(gd, cdpie) { outerContainer: fullLayout2._paper.node(), gd: gd, hovertemplate: trace2.hovertemplate, + hovertemplateLabels: pt, eventData: [eventData(pt, trace2)] }); diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index 76397aa6898..b6fc358eb8c 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -765,7 +765,7 @@ describe('pie hovering', function() { 'hovertemplate %{text}' ); - return Plotly.restyle(gd, 'hovertemplate', '%{percent:.1%}'); + return Plotly.restyle(gd, 'hovertemplate', '%{percent}'); }) .then(_hover) .then(function() { From 377ecba801578a69c18a37ad173a1ecf736d6b40 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 21:46:28 -0500 Subject: [PATCH 19/53] pass hovertemplate data around in `hoverData` instead of opts --- src/components/fx/hover.js | 38 ++++++++++++++++++++-------------- src/traces/pie/event_data.js | 1 + src/traces/pie/plot.js | 12 ++++++----- test/jasmine/tests/pie_test.js | 2 +- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index e6704beb1fd..f8fd8c8637c 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -132,7 +132,11 @@ exports.loneHover = function loneHover(hoverItem, opts) { }, xa: {_offset: 0}, ya: {_offset: 0}, - index: 0 + index: 0, + + hovertemplate: hoverItem.hovertemplate || false, + eventData: hoverItem.eventData || false, + hovertemplateLabels: hoverItem.hovertemplateLabels || false, }; var container3 = d3.select(opts.container); @@ -144,10 +148,7 @@ exports.loneHover = function loneHover(hoverItem, opts) { rotateLabels: false, bgColor: opts.bgColor || Color.background, container: container3, - outerContainer: outerContainer3, - hovertemplate: opts.hovertemplate || false, - hovertemplateLabels: opts.hovertemplateLabels || false, - eventData: opts.eventData || {} + outerContainer: outerContainer3 }; var hoverLabel = createHoverText([pointData], fullOpts, opts.gd); alignHoverText(hoverLabel, fullOpts.rotateLabels); @@ -188,7 +189,11 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { }, xa: {_offset: 0}, ya: {_offset: 0}, - index: 0 + index: 0, + + hovertemplate: hoverItem.hovertemplate || false, + eventData: opts.eventData || {}, + hovertemplateLabels: opts.hovertemplateLabels || false, }; }); @@ -202,10 +207,7 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { rotateLabels: false, bgColor: opts.bgColor || Color.background, container: container3, - outerContainer: outerContainer3, - hovertemplate: opts.hovertemplate || false, - hovertemplateLabels: opts.hovertemplateLabels || false, - eventData: opts.eventData || {} + outerContainer: outerContainer3 }; var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd); @@ -667,7 +669,11 @@ function _hover(gd, evt, subplot, noHoverEvent) { // other people and send it to the event for(itemnum = 0; itemnum < hoverData.length; itemnum++) { var pt = hoverData[itemnum]; - newhoverdata.push(helpers.makeEventData(pt, pt.trace, pt.cd)); + var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); + newhoverdata.push(eventData); + + hoverData[itemnum].hovertemplate = pt.trace.hovertemplate || false; + hoverData[itemnum].eventData = [eventData]; } gd._hoverdata = newhoverdata; @@ -689,8 +695,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { container: fullLayout._hoverlayer, outerContainer: fullLayout._paperdiv, commonLabelOpts: fullLayout.hoverlabel, - hoverdistance: fullLayout.hoverdistance, - eventData: gd._hoverdata + hoverdistance: fullLayout.hoverdistance }; var hoverLabels = createHoverText(hoverData, labelOpts, gd); @@ -960,10 +965,11 @@ function createHoverText(hoverData, opts, gd) { // hovertemplate var trace = d.trace; - var hovertemplate = opts.hovertemplate || hoverData[curveNumber].hovertemplate || false; - var hovertemplateLabels = opts.hovertemplateLabels || d; + var hovertemplate = d.hovertemplate || false; + var hovertemplateLabels = d.hovertemplateLabels || d; + var eventData = d.eventData[0] || {}; if(hovertemplate) { - text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, opts.eventData[curveNumber] || {}, trace); + text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, eventData, trace); var EXTRA_STRING_REGEX = /(.*)<\/extra>/; text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { diff --git a/src/traces/pie/event_data.js b/src/traces/pie/event_data.js index 1fa5a7206d0..272a5109836 100644 --- a/src/traces/pie/event_data.js +++ b/src/traces/pie/event_data.js @@ -25,6 +25,7 @@ module.exports = function eventData(pt, trace) { color: pt.color, value: pt.v, percent: pt.percent, + text: pt.text, // pt.v (and pt.i below) for backward compatibility v: pt.v diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 4c0c26f12a0..f1bc2fffa9c 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -107,8 +107,9 @@ module.exports = function plot(gd, cdpie) { var thisText = []; if(hoverinfo && hoverinfo.indexOf('label') !== -1) thisText.push(pt.label); + pt.text = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); if(hoverinfo && hoverinfo.indexOf('text') !== -1) { - var texti = helpers.castOption(trace2.hovertext || trace2.text, pt.pts); + var texti = pt.text; if(texti) thisText.push(texti); } pt.value = pt.v; @@ -133,13 +134,14 @@ module.exports = function plot(gd, cdpie) { fontFamily: helpers.castOption(hoverFont.family, pt.pts), fontSize: helpers.castOption(hoverFont.size, pt.pts), fontColor: helpers.castOption(hoverFont.color, pt.pts), - }, { - container: fullLayout2._hoverlayer.node(), - outerContainer: fullLayout2._paper.node(), - gd: gd, + hovertemplate: trace2.hovertemplate, hovertemplateLabels: pt, eventData: [eventData(pt, trace2)] + }, { + container: fullLayout2._hoverlayer.node(), + outerContainer: fullLayout2._paper.node(), + gd: gd }); hasHoverLabel = true; diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index b6fc358eb8c..f9d6c795105 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -563,7 +563,7 @@ describe('pie hovering', function() { 'curveNumber', 'pointNumber', 'pointNumbers', 'data', 'fullData', 'label', 'color', 'value', - 'i', 'v', 'percent' + 'i', 'v', 'percent', 'text' ]; expect(Object.keys(hoverData.points[0]).sort()).toEqual(fields.sort()); From bc6cbabb63deb4dfb1c4003f099f3cd1fef1673d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 21:58:28 -0500 Subject: [PATCH 20/53] update Fx.multiHovers to properly massage hoverItem --- src/components/fx/hover.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index f8fd8c8637c..7cc3f6fafe1 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -192,8 +192,8 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { index: 0, hovertemplate: hoverItem.hovertemplate || false, - eventData: opts.eventData || {}, - hovertemplateLabels: opts.hovertemplateLabels || false, + eventData: hoverItem.eventData || false, + hovertemplateLabels: hoverItem.hovertemplateLabels || false, }; }); From 8c9ba5bb7bb664e5bd35a2346d043f309777bc76 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 7 Nov 2018 22:24:24 -0500 Subject: [PATCH 21/53] fix lint --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 7cc3f6fafe1..8d941892396 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -895,7 +895,7 @@ function createHoverText(hoverData, opts, gd) { // then put the text in, position the pointer to the data, // and figure out sizes - hoverLabels.each(function(d, curveNumber) { + hoverLabels.each(function(d) { var g = d3.select(this).attr('transform', ''); var name = ''; var text = ''; From c76748215cba819e4bbafb03c71c9d4ad69be118 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 8 Nov 2018 11:25:16 -0500 Subject: [PATCH 22/53] pass `trace` object in Fx.loneHover and Fx.multiHovers for hovertemplate --- src/components/fx/hover.js | 4 ++-- src/traces/pie/plot.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 8d941892396..a589971e43d 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -126,7 +126,7 @@ exports.loneHover = function loneHover(hoverItem, opts) { fontColor: hoverItem.fontColor, // filler to make createHoverText happy - trace: { + trace: hoverItem.trace || { index: 0, hoverinfo: '' }, @@ -183,7 +183,7 @@ exports.multiHovers = function multiHovers(hoverItems, opts) { fontColor: hoverItem.fontColor, // filler to make createHoverText happy - trace: { + trace: hoverItem.trace || { index: 0, hoverinfo: '' }, diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index f1bc2fffa9c..01904f4d152 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -135,6 +135,7 @@ module.exports = function plot(gd, cdpie) { fontSize: helpers.castOption(hoverFont.size, pt.pts), fontColor: helpers.castOption(hoverFont.color, pt.pts), + trace: trace2, hovertemplate: trace2.hovertemplate, hovertemplateLabels: pt, eventData: [eventData(pt, trace2)] From 6d4c03ab8d2e1397ccf259c6435d38cd0afdd645 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 8 Nov 2018 11:50:43 -0500 Subject: [PATCH 23/53] extra regex still matches in the presence of newlines --- src/components/fx/hover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index a589971e43d..a8687e8c2d9 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -971,7 +971,7 @@ function createHoverText(hoverData, opts, gd) { if(hovertemplate) { text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, eventData, trace); - var EXTRA_STRING_REGEX = /(.*)<\/extra>/; + var EXTRA_STRING_REGEX = /([\s\S]*)<\/extra>/; text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { name = extra; // Assign name for secondary text label return ''; // Remove from main text label From 8f440b09ef6b998fe9c2aa4406d1e746e55bf1b3 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 08:24:34 -0500 Subject: [PATCH 24/53] fix jsDocs syntax --- src/lib/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index 23e5e4b9e16..ab456109c89 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -981,7 +981,7 @@ lib.numSeparate = function(value, separators, separatethousands) { var TEMPLATE_STRING_REGEX = /%{([^\s%{}:]*)(:[^}]*)?}/g; var SIMPLE_PROPERTY_REGEX = /^\w*$/; -/* +/** * Substitute values from an object into a string * * Examples: @@ -993,7 +993,6 @@ var SIMPLE_PROPERTY_REGEX = /^\w*$/; * * @return {string} templated string */ - lib.templateString = function(string, obj) { // Not all that useful, but cache nestedProperty instantiation // just in case it speeds things up *slightly*: @@ -1008,7 +1007,7 @@ lib.templateString = function(string, obj) { }); }; -/* +/** * Substitute values from an object into a string and optionally formats them using d3-format, * or fallback to associated labels. * @@ -1023,7 +1022,6 @@ lib.templateString = function(string, obj) { * * @return {string} templated string */ - lib.hovertemplateString = function(string, labels) { var args = arguments; // Not all that useful, but cache nestedProperty instantiation From 0659d205b14b3565d4114648175be0a5143301a9 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 08:25:10 -0500 Subject: [PATCH 25/53] move regex to outer scope --- src/components/fx/hover.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index a8687e8c2d9..06d930e08f5 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -731,6 +731,8 @@ function _hover(gd, evt, subplot, noHoverEvent) { }); } +var EXTRA_STRING_REGEX = /([\s\S]*)<\/extra>/; + function createHoverText(hoverData, opts, gd) { var hovermode = opts.hovermode; var rotateLabels = opts.rotateLabels; @@ -971,7 +973,6 @@ function createHoverText(hoverData, opts, gd) { if(hovertemplate) { text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, eventData, trace); - var EXTRA_STRING_REGEX = /([\s\S]*)<\/extra>/; text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { name = extra; // Assign name for secondary text label return ''; // Remove from main text label From 41ab464fae82cdf9fe38ca93a4468edf615f7243 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 08:25:56 -0500 Subject: [PATCH 26/53] remove old unused commented lines --- src/lib/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index ab456109c89..79e32dee582 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1038,8 +1038,6 @@ lib.hovertemplateString = function(string, labels) { } if(!SIMPLE_PROPERTY_REGEX.test(key)) { - // getterCache[key] = getterCache[key] || lib.nestedProperty(obj, key).get; - // value = getterCache[key](); value = getterCache[key] || lib.nestedProperty(obj, key).get(); if(value) getterCache[key] = value; } From 37e40f9dc4eebd19c45c2005d98428a59a4599d9 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 08:34:21 -0500 Subject: [PATCH 27/53] move regex to outside scope --- src/lib/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/index.js b/src/lib/index.js index 79e32dee582..cd38ad99d94 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1007,6 +1007,7 @@ lib.templateString = function(string, obj) { }); }; +var TEMPLATE_STRING_FORMAT_SEPARATOR = /^:/; /** * Substitute values from an object into a string and optionally formats them using d3-format, * or fallback to associated labels. @@ -1050,7 +1051,7 @@ lib.hovertemplateString = function(string, labels) { } if(format) { - value = d3.format(format.replace(/^:/, ''))(value); + value = d3.format(format.replace(TEMPLATE_STRING_FORMAT_SEPARATOR, ''))(value); } else { if(labels.hasOwnProperty(key + 'Label')) value = labels[key + 'Label']; } From 8e8b75b3da02cfaa258e69c46bddce67f4e2d2a7 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 09:01:46 -0500 Subject: [PATCH 28/53] move hovertemplate out of global-level plots attributes --- src/components/fx/hovertemplate_attributes.js | 33 +++++++++++++++++++ src/plots/attributes.js | 13 -------- src/traces/bar/attributes.js | 3 +- src/traces/pie/attributes.js | 3 +- src/traces/scatter/attributes.js | 4 +-- src/traces/scattergl/attributes.js | 3 +- 6 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/components/fx/hovertemplate_attributes.js diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js new file mode 100644 index 00000000000..af063dc23ce --- /dev/null +++ b/src/components/fx/hovertemplate_attributes.js @@ -0,0 +1,33 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function(opts, extra) { + opts = opts || {}; + extra = extra || {}; + + var descPart = extra.description ? ' ' + extra.description : ''; + + var hovertemplate = { + valType: 'string', + role: 'info', + dflt: '', + editType: 'none', + description: [ + 'Template string used for rendering the information that appear on hover box.', + 'Note that this will override `hoverinfo`.', + 'Variables are inserted using %{variable}, for example "y: %{y}".', + 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', + 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.', + descPart + ].join(' ') + }; + + return hovertemplate; +} diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 8590251a5b4..653176fd260 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -129,19 +129,6 @@ module.exports = { ].join(' ') }, hoverlabel: fxAttrs.hoverlabel, - hovertemplate: { - valType: 'string', - role: 'info', - dflt: false, - editType: 'none', - description: [ - 'Template string used for rendering the information that appear on hover box.', - 'Note that this will override `hoverinfo`.', - 'Variables are inserted using %{variable}, for example "y: %{y}".', - 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', - 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.' - ].join(' ') - }, stream: { token: { valType: 'string', diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index e7a9e97126e..fe685c39e97 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -9,6 +9,7 @@ 'use strict'; var scatterAttrs = require('../scatter/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var fontAttrs = require('../../plots/font_attributes'); @@ -59,7 +60,7 @@ module.exports = { text: scatterAttrs.text, hovertext: scatterAttrs.hovertext, - hovertemplate: scatterAttrs.hovertemplate, + hovertemplate: hovertemplateAttrs(), textposition: { valType: 'enumerated', diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index 07280d67e1c..22e141efb97 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -11,6 +11,7 @@ var colorAttrs = require('../../components/color/attributes'); var fontAttrs = require('../../plots/font_attributes'); var plotAttrs = require('../../plots/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var domainAttrs = require('../../plots/domain').attributes; var extendFlat = require('../../lib/extend').extendFlat; @@ -158,7 +159,7 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: extendFlat({}, plotAttrs.hovertemplate), + hovertemplate: hovertemplateAttrs(), textposition: { valType: 'enumerated', role: 'info', diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 13b3732b370..b8c3df60cc0 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -8,7 +8,7 @@ 'use strict'; -var plotAttrs = require('../../plots/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var fontAttrs = require('../../plots/font_attributes'); @@ -204,7 +204,7 @@ module.exports = { 'or text, then the default is *fills*, otherwise it is *points*.' ].join(' ') }, - hovertemplate: extendFlat({}, plotAttrs.hovertemplate), + hovertemplate: hovertemplateAttrs(), line: { color: { valType: 'color', diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 8fd27a758ee..f143cfa36c5 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -9,6 +9,7 @@ 'use strict'; var plotAttrs = require('../../plots/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var scatterAttrs = require('../scatter/attributes'); var colorAttrs = require('../../components/colorscale/attributes'); @@ -97,4 +98,4 @@ var attrs = module.exports = overrideAll({ }, 'calc', 'nested'); attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; -attrs.hovertemplate = extendFlat({}, plotAttrs.hovertemplate); +attrs.hovertemplate = hovertemplateAttrs(); From 2015c57d9dfc9b7fb728003af2c0ca3cc2ae916c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 09:07:09 -0500 Subject: [PATCH 29/53] scatter: do not coerce hovertemplate if hoveron: 'fills' --- src/traces/scatter/defaults.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js index 7fb369635ae..772406dbb9f 100644 --- a/src/traces/scatter/defaults.js +++ b/src/traces/scatter/defaults.js @@ -38,7 +38,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout 'lines+markers' : 'lines'; coerce('text'); coerce('hovertext'); - coerce('hovertemplate'); coerce('mode', defaultMode); if(subTypes.hasLines(traceOut)) { @@ -76,7 +75,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout dfltHoverOn.push('fills'); } coerce('hoveron', dfltHoverOn.join('+') || 'points'); - + if(traceOut.hoveron !== 'fills') coerce('hovertemplate'); var errorBarsSupplyDefaults = Registry.getComponentMethod('errorbars', 'supplyDefaults'); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); From aef48c021cce04d48aabed1e3a9386aa05c381a9 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 09:08:17 -0500 Subject: [PATCH 30/53] fix lint --- src/components/fx/hovertemplate_attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index af063dc23ce..a2793768d95 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -30,4 +30,4 @@ module.exports = function(opts, extra) { }; return hovertemplate; -} +}; From a3058f41747f5f6c858700b280c81cef3b909094 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 10:00:07 -0500 Subject: [PATCH 31/53] test hovertemplate support for and pseudo-html --- test/jasmine/tests/hover_label_test.js | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 5b676d43ff5..098a476fbcc 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1579,6 +1579,52 @@ describe('hover info', function() { .catch(failTest) .then(done); }); + + it('should format secondary label with extra tag', function(done) { + var gd = document.getElementById('graph'); + Plotly.restyle(gd, 'hovertemplate', 'trace 20 %{y:$.2f}') + .then(function() { + Fx.hover('graph', evt, 'xy'); + + var hoverTrace = gd._hoverdata[0]; + + expect(hoverTrace.curveNumber).toEqual(0); + expect(hoverTrace.pointNumber).toEqual(17); + expect(hoverTrace.x).toEqual(0.388); + expect(hoverTrace.y).toEqual(1); + + assertHoverLabelContent({ + nums: '', + name: 'trace 20 $1.00', + axis: '0.388' + }); + }) + .catch(failTest) + .then(done); + }); + + it('should support pseudo-html', function(done) { + var gd = document.getElementById('graph'); + Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}
%{name}') + .then(function() { + Fx.hover('graph', evt, 'xy'); + + var hoverTrace = gd._hoverdata[0]; + + expect(hoverTrace.curveNumber).toEqual(0); + expect(hoverTrace.pointNumber).toEqual(17); + expect(hoverTrace.x).toEqual(0.388); + expect(hoverTrace.y).toEqual(1); + + assertHoverLabelContent({ + nums: '$1.00\nPV learning curve.txt', + name: '', + axis: '0.388' + }); + }) + .catch(failTest) + .then(done); + }); }); }); From 3068d7dbdeaaba98fbc229b9de8e27185f21e62f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 12:01:50 -0500 Subject: [PATCH 32/53] hovertemplate warns user about missing variables up to 10 times --- src/lib/index.js | 13 +++++++++++-- test/jasmine/tests/lib_test.js | 11 +++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index cd38ad99d94..a0ad489f7a0 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -1008,6 +1008,8 @@ lib.templateString = function(string, obj) { }; var TEMPLATE_STRING_FORMAT_SEPARATOR = /^:/; +var numberOfHoverTemplateWarnings = 0; +var maximumNumberOfHoverTemplateWarnings = 10; /** * Substitute values from an object into a string and optionally formats them using d3-format, * or fallback to associated labels. @@ -1046,8 +1048,15 @@ lib.hovertemplateString = function(string, labels) { } if(value === undefined) { - lib.warn('Variable \'' + key + '\' in hovertemplate could not be found!'); - value = match; + if(numberOfHoverTemplateWarnings < maximumNumberOfHoverTemplateWarnings) { + lib.warn('Variable \'' + key + '\' in hovertemplate could not be found!'); + value = match; + } + + if(numberOfHoverTemplateWarnings === maximumNumberOfHoverTemplateWarnings) { + lib.warn('Too many hovertemplate warnings - additional warnings will be suppressed'); + } + numberOfHoverTemplateWarnings++; } if(format) { diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js index d67159bbef4..6acf7dfeb60 100644 --- a/test/jasmine/tests/lib_test.js +++ b/test/jasmine/tests/lib_test.js @@ -2192,6 +2192,17 @@ describe('Test lib.js:', function() { it('looks for default label if no format is provided', function() { expect(Lib.hovertemplateString('y: %{y}', {yLabel: '0.1'}, {y: 0.123})).toEqual('y: 0.1'); }); + + it('warns user up to 10 times if a variable cannot be found', function() { + spyOn(Lib, 'warn').and.callThrough(); + Lib.hovertemplateString('%{idontexist}', {}); + expect(Lib.warn.calls.count()).toBe(1); + + for(var i = 0; i < 15; i++) { + Lib.hovertemplateString('%{idontexist}', {}); + } + expect(Lib.warn.calls.count()).toBe(10); + }); }); describe('relativeAttr()', function() { From a808883b60d456cc1907432776a682de2fdcf2b0 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 12:21:32 -0500 Subject: [PATCH 33/53] hovertemplate attribute supports array --- src/components/fx/calc.js | 2 ++ src/components/fx/hover.js | 4 ++- src/components/fx/hovertemplate_attributes.js | 1 + test/jasmine/tests/hover_label_test.js | 27 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/components/fx/calc.js b/src/components/fx/calc.js index ebeb4887c1c..aaebc599037 100644 --- a/src/components/fx/calc.js +++ b/src/components/fx/calc.js @@ -34,6 +34,8 @@ module.exports = function calc(gd) { fillFn(trace.hoverinfo, cd, 'hi', makeCoerceHoverInfo(trace)); + if(trace.hovertemplate) fillFn(trace.hovertemplate, cd, 'ht'); + if(!trace.hoverlabel) continue; fillFn(trace.hoverlabel.bgcolor, cd, 'hbg'); diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 06d930e08f5..73816698df8 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -672,7 +672,9 @@ function _hover(gd, evt, subplot, noHoverEvent) { var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); newhoverdata.push(eventData); - hoverData[itemnum].hovertemplate = pt.trace.hovertemplate || false; + 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]; } diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index a2793768d95..5ff15a325b2 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -18,6 +18,7 @@ module.exports = function(opts, extra) { valType: 'string', role: 'info', dflt: '', + arrayOk: true, editType: 'none', description: [ 'Template string used for rendering the information that appear on hover box.', diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 098a476fbcc..b5f864f483e 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1625,6 +1625,33 @@ describe('hover info', function() { .catch(failTest) .then(done); }); + + it('should support array', function(done) { + var gd = document.getElementById('graph'); + var templates = []; + for(var i = 0; i < mockCopy.data[0].y.length; i++) { + templates[i] = 'hovertemplate ' + i + ':%{y:$.2f}'; + } + Plotly.restyle(gd, 'hovertemplate', [templates]) + .then(function() { + Fx.hover('graph', evt, 'xy'); + + var hoverTrace = gd._hoverdata[0]; + + expect(hoverTrace.curveNumber).toEqual(0); + expect(hoverTrace.pointNumber).toEqual(17); + expect(hoverTrace.x).toEqual(0.388); + expect(hoverTrace.y).toEqual(1); + + assertHoverLabelContent({ + nums: 'hovertemplate 17:$1.00', + name: '', + axis: '0.388' + }); + }) + .catch(failTest) + .then(done); + }); }); }); From 522f744a53d8b15bd3596c6caad75cd6486f9efc Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 12:33:15 -0500 Subject: [PATCH 34/53] scattergl support for hovertemplate array --- src/traces/scattergl/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index d7c6e34cb29..d6b4f753f33 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -814,6 +814,11 @@ function calcHover(pointData, x, y, trace) { di.hi = Array.isArray(hoverinfo) ? hoverinfo[id] : hoverinfo; } + var hovertemplate = trace.hovertemplate; + if(hovertemplate) { + di.ht = Array.isArray(hovertemplate) ? hovertemplate[id] : hovertemplate; + } + var fakeCd = {}; fakeCd[pointData.index] = di; @@ -832,7 +837,7 @@ function calcHover(pointData, x, y, trace) { distance: minDist, spikeDistance: dxy, - hovertemplate: trace.hovertemplate + hovertemplate: di.ht }); if(di.htx) pointData.text = di.htx; From c501b440fa41d236804554060191bc0aee19d6ce Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 12:37:15 -0500 Subject: [PATCH 35/53] pie support for hovertemplate array --- src/traces/pie/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/pie/plot.js b/src/traces/pie/plot.js index 01904f4d152..344ac60ab55 100644 --- a/src/traces/pie/plot.js +++ b/src/traces/pie/plot.js @@ -136,7 +136,7 @@ module.exports = function plot(gd, cdpie) { fontColor: helpers.castOption(hoverFont.color, pt.pts), trace: trace2, - hovertemplate: trace2.hovertemplate, + hovertemplate: helpers.castOption(trace2.hovertemplate, pt.pts), hovertemplateLabels: pt, eventData: [eventData(pt, trace2)] }, { From f89baca22f9263630d7092465205e91cd6ef561a Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 12:57:50 -0500 Subject: [PATCH 36/53] make axes available in eventData to give access to its title --- src/components/fx/hover.js | 4 ++++ test/jasmine/tests/hover_label_test.js | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 73816698df8..88343971f5a 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -670,6 +670,10 @@ function _hover(gd, evt, subplot, noHoverEvent) { for(itemnum = 0; itemnum < hoverData.length; itemnum++) { var pt = hoverData[itemnum]; var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); + + // Add axis information to eventData + eventData.xaxis = gd._fullLayout[pt.trace.xaxis + 'axis']; + eventData.yaxis = gd._fullLayout[pt.trace.yaxis + 'axis']; newhoverdata.push(eventData); var ht = false; diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index b5f864f483e..80019206d57 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1652,6 +1652,29 @@ describe('hover info', function() { .catch(failTest) .then(done); }); + + it('should contain the axis names', function(done) { + var gd = document.getElementById('graph'); + Plotly.restyle(gd, 'hovertemplate', '%{yaxis.title}:%{y:$.2f}
%{xaxis.title}:%{x:0.4f}') + .then(function() { + Fx.hover('graph', evt, 'xy'); + + var hoverTrace = gd._hoverdata[0]; + + expect(hoverTrace.curveNumber).toEqual(0); + expect(hoverTrace.pointNumber).toEqual(17); + expect(hoverTrace.x).toEqual(0.388); + expect(hoverTrace.y).toEqual(1); + + assertHoverLabelContent({ + nums: 'Cost ($/W​P):$1.00\nCumulative Production (GW):0.3880', + name: '', + axis: '0.388' + }); + }) + .catch(failTest) + .then(done); + }); }); }); From 369c9d662b3d38ec29464a6fe5f05ce2f6315763 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 13:14:55 -0500 Subject: [PATCH 37/53] add axis information to eventData only if hovertemplate --- src/components/fx/hover.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 88343971f5a..0b06b271114 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -671,15 +671,18 @@ function _hover(gd, evt, subplot, noHoverEvent) { var pt = hoverData[itemnum]; var eventData = helpers.makeEventData(pt, pt.trace, pt.cd); - // Add axis information to eventData - eventData.xaxis = gd._fullLayout[pt.trace.xaxis + 'axis']; - eventData.yaxis = gd._fullLayout[pt.trace.yaxis + 'axis']; - newhoverdata.push(eventData); - 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]; + + // Add axis information to eventData if hovertemplate + if(hoverData[itemnum].hovertemplate) { + eventData.xaxis = gd._fullLayout[pt.trace.xaxis + 'axis']; + eventData.yaxis = gd._fullLayout[pt.trace.yaxis + 'axis']; + } + + newhoverdata.push(eventData); } gd._hoverdata = newhoverdata; From 8d95f05a40792bd8fb67086ffddd04595acfd166 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Mon, 12 Nov 2018 14:52:46 -0500 Subject: [PATCH 38/53] axis information is already included in hovertemplate --- src/components/fx/hover.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 0b06b271114..da195eb62bb 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -676,12 +676,6 @@ function _hover(gd, evt, subplot, noHoverEvent) { hoverData[itemnum].hovertemplate = ht || pt.trace.hovertemplate || false; hoverData[itemnum].eventData = [eventData]; - // Add axis information to eventData if hovertemplate - if(hoverData[itemnum].hovertemplate) { - eventData.xaxis = gd._fullLayout[pt.trace.xaxis + 'axis']; - eventData.yaxis = gd._fullLayout[pt.trace.yaxis + 'axis']; - } - newhoverdata.push(eventData); } From 1e4bd33069b9d2ecb2460b069b05a0442dcf43df Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 10:29:08 -0500 Subject: [PATCH 39/53] pie: test that hovertemplate supports array --- test/jasmine/tests/pie_test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/jasmine/tests/pie_test.js b/test/jasmine/tests/pie_test.js index f9d6c795105..8dd4dd847b0 100644 --- a/test/jasmine/tests/pie_test.js +++ b/test/jasmine/tests/pie_test.js @@ -785,6 +785,15 @@ describe('pie hovering', function() { 'hovertemplate %{label}' ); }) + .then(function() { return Plotly.restyle(gd, 'hovertemplate', [['', '', '', '', 'ht 5 %{percent:0.2%}']]); }) + .then(_hover) + .then(function() { + assertLabel( + ['ht 5 33.33%'].join('\n'), + null, + 'hovertemplate arrayOK' + ); + }) .catch(fail) .then(done); }); From 61a2da7ff9fca6a91bd36df8845488a1ff168983 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 11:12:09 -0500 Subject: [PATCH 40/53] list available variables in pie's hovertemplate description --- src/components/fx/hovertemplate_attributes.js | 13 +++++++++++++ src/traces/pie/attributes.js | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 5ff15a325b2..262a9601bee 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -13,6 +13,18 @@ module.exports = function(opts, extra) { extra = extra || {}; var descPart = extra.description ? ' ' + extra.description : ''; + var keys = extra.keys || []; + if(keys.length > 0) { + for(var i = 0; i < keys.length; i++) { + keys[i] = '`' + keys[i] + '`'; + } + descPart = descPart + 'This trace supports the additional '; + if(keys.length === 1) { + descPart = 'variable ' + keys[0]; + } else { + descPart = 'variables ' + keys.slice(0, -1).join(', ') + ' and ' + keys.slice(-1) + '.'; + } + } var hovertemplate = { valType: 'string', @@ -26,6 +38,7 @@ module.exports = function(opts, extra) { 'Variables are inserted using %{variable}, for example "y: %{y}".', 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.', + 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plot.ly/javascript/plotlyjs-events/#event-data.', descPart ].join(' ') }; diff --git a/src/traces/pie/attributes.js b/src/traces/pie/attributes.js index 22e141efb97..223d7645219 100644 --- a/src/traces/pie/attributes.js +++ b/src/traces/pie/attributes.js @@ -159,7 +159,9 @@ module.exports = { hoverinfo: extendFlat({}, plotAttrs.hoverinfo, { flags: ['label', 'text', 'value', 'percent', 'name'] }), - hovertemplate: hovertemplateAttrs(), + hovertemplate: hovertemplateAttrs({}, { + keys: ['label', 'color', 'value', 'percent', 'text'] + }), textposition: { valType: 'enumerated', role: 'info', From 43a4cd9b081ddf4ea06963eb517dd16c0794eee7 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 11:30:09 -0500 Subject: [PATCH 41/53] hovertemplate: do not look into trace object, use fullData instead --- src/components/fx/hover.js | 2 +- test/jasmine/tests/hover_label_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index da195eb62bb..84cc439dfb3 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -974,7 +974,7 @@ function createHoverText(hoverData, opts, gd) { var hovertemplateLabels = d.hovertemplateLabels || d; var eventData = d.eventData[0] || {}; if(hovertemplate) { - text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, eventData, trace); + text = Lib.hovertemplateString(hovertemplate, hovertemplateLabels, eventData); text = text.replace(EXTRA_STRING_REGEX, function(match, extra) { name = extra; // Assign name for secondary text label diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 80019206d57..aafa51cc801 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -1605,7 +1605,7 @@ describe('hover info', function() { it('should support pseudo-html', function(done) { var gd = document.getElementById('graph'); - Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}
%{name}') + Plotly.restyle(gd, 'hovertemplate', '%{y:$.2f}
%{fullData.name}') .then(function() { Fx.hover('graph', evt, 'xy'); From 70befe330e53a8d255f947132ca0e9cb2cd800f9 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 11:35:32 -0500 Subject: [PATCH 42/53] describe hovertemplate variables for scatter(gl) --- src/traces/scatter/attributes.js | 4 +++- src/traces/scattergl/attributes.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index b8c3df60cc0..2e08e6179aa 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -204,7 +204,9 @@ module.exports = { 'or text, then the default is *fills*, otherwise it is *points*.' ].join(' ') }, - hovertemplate: hovertemplateAttrs(), + hovertemplate: hovertemplateAttrs({}, { + keys: ['marker.size', 'marker.color'] + }), line: { color: { valType: 'color', diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index f143cfa36c5..9f2f4f3c7f8 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -98,4 +98,4 @@ var attrs = module.exports = overrideAll({ }, 'calc', 'nested'); attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; -attrs.hovertemplate = hovertemplateAttrs(); +attrs.hovertemplate = scatterAttrs.hovertemplate; From b6822e8366be259f682da7b85c23c90972838cba Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 11:38:04 -0500 Subject: [PATCH 43/53] describe hovertemplate variables for histogram --- src/traces/histogram/attributes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 33184003ac7..ad76a30319f 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -9,6 +9,7 @@ 'use strict'; var barAttrs = require('../bar/attributes'); +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var makeBinAttrs = require('./bin_attributes'); module.exports = { @@ -185,7 +186,9 @@ module.exports = { ].join(' ') }, - hovertemplate: barAttrs.hovertemplate, + hovertemplate: hovertemplateAttrs({}, { + keys: ['binNumber'] + }), marker: barAttrs.marker, From 59bc981edf797a746191377fb49d82cb57e32751 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 11:39:44 -0500 Subject: [PATCH 44/53] fix lint --- src/components/fx/hover.js | 1 - src/traces/scattergl/attributes.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 84cc439dfb3..94598806085 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -969,7 +969,6 @@ function createHoverText(hoverData, opts, gd) { } // hovertemplate - var trace = d.trace; var hovertemplate = d.hovertemplate || false; var hovertemplateLabels = d.hovertemplateLabels || d; var eventData = d.eventData[0] || {}; diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 9f2f4f3c7f8..08b715d06ea 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -9,7 +9,6 @@ 'use strict'; var plotAttrs = require('../../plots/attributes'); -var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var scatterAttrs = require('../scatter/attributes'); var colorAttrs = require('../../components/colorscale/attributes'); From f75f2e0d9217da74d2152a0cafdf5d81999de755 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 15:52:36 -0500 Subject: [PATCH 45/53] scatter: test hover event data --- src/traces/scatter/attributes.js | 2 +- test/jasmine/tests/scatter_test.js | 40 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 2e08e6179aa..cc1f2896c1f 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -205,7 +205,7 @@ module.exports = { ].join(' ') }, hovertemplate: hovertemplateAttrs({}, { - keys: ['marker.size', 'marker.color'] + keys: ['marker.size'] }), line: { color: { diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index cca7961e1c3..0c7260f542d 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -14,6 +14,7 @@ var transitions = require('../assets/transitions'); var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; var assertMultiNodeOrder = customAssertions.assertMultiNodeOrder; +var hover = require('../assets/hover'); var getOpacity = function(node) { return Number(node.style.opacity); }; var getFillOpacity = function(node) { return Number(node.style['fill-opacity']); }; @@ -1808,3 +1809,42 @@ describe('Test scatter *clipnaxis*:', function() { .then(done); }); }); + +describe('event data', function() { + var mock = require('@mocks/12'); + var mockCopy = Lib.extendDeep({}, mock), + gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + afterEach(destroyGraphDiv); + + it('should contain the correct fields', function() { + + var hoverData; + + gd.on('plotly_hover', function(data) { + hoverData = data; + }); + + hover(130, 350); + + expect(hoverData.points.length).toEqual(1); + + var fields = [ + 'curveNumber', 'pointNumber', + 'data', 'fullData', + 'xaxis', 'yaxis', 'x', 'y', + 'marker.size' + ]; + + fields.forEach(function(field) { + expect(Object.keys(hoverData.points[0])).toContain(field); + }); + }); +}); From 0654245c0dac9c7a8d2a03eac6c4f985998292d1 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 17:05:39 -0500 Subject: [PATCH 46/53] test that event data has correct fields in bar, scatter, histogram --- test/jasmine/assets/check_event_data.js | 42 +++++++++++++++++++++++++ test/jasmine/tests/bar_test.js | 7 +++++ test/jasmine/tests/histogram_test.js | 6 ++++ test/jasmine/tests/scatter_test.js | 38 ++-------------------- 4 files changed, 57 insertions(+), 36 deletions(-) create mode 100644 test/jasmine/assets/check_event_data.js diff --git a/test/jasmine/assets/check_event_data.js b/test/jasmine/assets/check_event_data.js new file mode 100644 index 00000000000..9df72fd7b1f --- /dev/null +++ b/test/jasmine/assets/check_event_data.js @@ -0,0 +1,42 @@ +var Plotly = require('@lib/index'); +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); +var Lib = require('@src/lib'); + +var hover = require('../assets/hover'); + +'use strict'; + +module.exports = function checkEventData(mock, x, y, additionalFields) { + var mockCopy = Lib.extendDeep({}, mock), + gd; + + beforeEach(function(done) { + gd = createGraphDiv(); + + Plotly.plot(gd, mockCopy.data, mockCopy.layout) + .then(done); + }); + + afterEach(destroyGraphDiv); + + it('should contain the correct fields', function() { + var hoverData; + + gd.on('plotly_hover', function(data) { + hoverData = data; + }); + + hover(x, y); + + var fields = [ + 'curveNumber', + 'data', 'fullData', + 'xaxis', 'yaxis', 'x', 'y', + ].concat(additionalFields); + + fields.forEach(function(field) { + expect(Object.keys(hoverData.points[0])).toContain(field); + }); + }); +}; diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 76e09fb60bc..69f090b9896 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -13,6 +13,8 @@ var failTest = require('../assets/fail_test'); var checkTicks = require('../assets/custom_assertions').checkTicks; var supplyAllDefaults = require('../assets/supply_defaults'); +var checkEventData = require('../assets/check_event_data'); + var customAssertions = require('../assets/custom_assertions'); var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; @@ -1857,6 +1859,11 @@ describe('bar hover', function() { }); }); +describe('event data', function() { + var mock = require('@mocks/stacked_bar'); + checkEventData(mock, 0, 309, []); +}); + function mockBarPlot(dataWithoutTraceType, layout) { var traceTemplate = { type: 'bar' }; diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index 1d86448b260..bde931378c0 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -13,6 +13,7 @@ var destroyGraphDiv = require('../assets/destroy_graph_div'); var supplyAllDefaults = require('../assets/supply_defaults'); var failTest = require('../assets/fail_test'); +var checkEventData = require('../assets/check_event_data'); describe('Test histogram', function() { 'use strict'; @@ -1060,3 +1061,8 @@ describe('getBinSpanLabelRound', function() { ]); }); }); + +describe('event data', function() { + var mock = require('@mocks/hist_category'); + checkEventData(mock, 100, 200, ['binNumber']); +}); diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 0c7260f542d..e4a3139217d 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -14,7 +14,7 @@ var transitions = require('../assets/transitions'); var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; var assertMultiNodeOrder = customAssertions.assertMultiNodeOrder; -var hover = require('../assets/hover'); +var checkEventData = require('../assets/check_event_data'); var getOpacity = function(node) { return Number(node.style.opacity); }; var getFillOpacity = function(node) { return Number(node.style['fill-opacity']); }; @@ -1812,39 +1812,5 @@ describe('Test scatter *clipnaxis*:', function() { describe('event data', function() { var mock = require('@mocks/12'); - var mockCopy = Lib.extendDeep({}, mock), - gd; - - beforeEach(function(done) { - gd = createGraphDiv(); - - Plotly.plot(gd, mockCopy.data, mockCopy.layout) - .then(done); - }); - - afterEach(destroyGraphDiv); - - it('should contain the correct fields', function() { - - var hoverData; - - gd.on('plotly_hover', function(data) { - hoverData = data; - }); - - hover(130, 350); - - expect(hoverData.points.length).toEqual(1); - - var fields = [ - 'curveNumber', 'pointNumber', - 'data', 'fullData', - 'xaxis', 'yaxis', 'x', 'y', - 'marker.size' - ]; - - fields.forEach(function(field) { - expect(Object.keys(hoverData.points[0])).toContain(field); - }); - }); + checkEventData(mock, 130, 350, ['marker.size']); }); From 0ecd1c1d0db3d7d09962b471ea0f1042002dd519 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 13 Nov 2018 17:09:36 -0500 Subject: [PATCH 47/53] bar test: fix hover position to trigger hover events --- test/jasmine/tests/bar_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 69f090b9896..1058c6400f6 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1861,7 +1861,7 @@ describe('bar hover', function() { describe('event data', function() { var mock = require('@mocks/stacked_bar'); - checkEventData(mock, 0, 309, []); + checkEventData(mock, 216, 309, []); }); function mockBarPlot(dataWithoutTraceType, layout) { From 6f865225087930fdbd9e771d1ab399f6b53fabe7 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 14 Nov 2018 11:38:16 -0500 Subject: [PATCH 48/53] scatter: add `marker.color` to `hovertemplate` available variables --- src/traces/scatter/attributes.js | 2 +- test/jasmine/tests/scatter_test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index cc1f2896c1f..2e08e6179aa 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -205,7 +205,7 @@ module.exports = { ].join(' ') }, hovertemplate: hovertemplateAttrs({}, { - keys: ['marker.size'] + keys: ['marker.size', 'marker.color'] }), line: { color: { diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index e4a3139217d..d6edc08bde7 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -1811,6 +1811,6 @@ describe('Test scatter *clipnaxis*:', function() { }); describe('event data', function() { - var mock = require('@mocks/12'); - checkEventData(mock, 130, 350, ['marker.size']); + var mock = require('@mocks/scatter-colorscale-colorbar'); + checkEventData(mock, 540, 260, ['marker.size', 'marker.color']); }); From 7be804eae749220a654bb80a5c7ecacaccf99718 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 14 Nov 2018 12:27:43 -0500 Subject: [PATCH 49/53] one source of truth for event data keys used for doc and test --- src/components/fx/hovertemplate_attributes.js | 7 ++++--- src/traces/bar/attributes.js | 5 ++++- src/traces/bar/constants.js | 14 ++++++++++++++ src/traces/histogram/attributes.js | 3 ++- src/traces/histogram/constants.js | 14 ++++++++++++++ src/traces/scatter/attributes.js | 2 +- src/traces/scatter/constants.js | 4 +++- test/jasmine/tests/bar_test.js | 3 ++- test/jasmine/tests/histogram_test.js | 3 ++- test/jasmine/tests/scatter_test.js | 3 ++- 10 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 src/traces/bar/constants.js create mode 100644 src/traces/histogram/constants.js diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 262a9601bee..49a10641356 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -15,14 +15,15 @@ module.exports = function(opts, extra) { var descPart = extra.description ? ' ' + extra.description : ''; var keys = extra.keys || []; if(keys.length > 0) { + var quotedKeys = []; for(var i = 0; i < keys.length; i++) { - keys[i] = '`' + keys[i] + '`'; + quotedKeys[i] = '`' + keys[i] + '`'; } descPart = descPart + 'This trace supports the additional '; if(keys.length === 1) { - descPart = 'variable ' + keys[0]; + descPart = 'variable ' + quotedKeys[0]; } else { - descPart = 'variables ' + keys.slice(0, -1).join(', ') + ' and ' + keys.slice(-1) + '.'; + descPart = 'variables ' + quotedKeys.slice(0, -1).join(', ') + ' and ' + quotedKeys.slice(-1) + '.'; } } diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index fe685c39e97..213c0ef62e7 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -13,6 +13,7 @@ var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes') var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var fontAttrs = require('../../plots/font_attributes'); +var constants = require('./constants.js') var extendFlat = require('../../lib/extend').extendFlat; @@ -60,7 +61,9 @@ module.exports = { text: scatterAttrs.text, hovertext: scatterAttrs.hovertext, - hovertemplate: hovertemplateAttrs(), + hovertemplate: hovertemplateAttrs({}, { + keys: constants.eventDataKeys + }), textposition: { valType: 'enumerated', diff --git a/src/traces/bar/constants.js b/src/traces/bar/constants.js new file mode 100644 index 00000000000..fc5f769d92a --- /dev/null +++ b/src/traces/bar/constants.js @@ -0,0 +1,14 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + eventDataKeys: [] +}; diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index ad76a30319f..260a4a7b267 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -11,6 +11,7 @@ var barAttrs = require('../bar/attributes'); var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var makeBinAttrs = require('./bin_attributes'); +var constants = require('./constants'); module.exports = { x: { @@ -187,7 +188,7 @@ module.exports = { }, hovertemplate: hovertemplateAttrs({}, { - keys: ['binNumber'] + keys: constants.eventDataKeys }), marker: barAttrs.marker, diff --git a/src/traces/histogram/constants.js b/src/traces/histogram/constants.js new file mode 100644 index 00000000000..84c1ab61216 --- /dev/null +++ b/src/traces/histogram/constants.js @@ -0,0 +1,14 @@ +/** +* Copyright 2012-2018, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + + +'use strict'; + +module.exports = { + eventDataKeys: ['binNumber'] +}; diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 2e08e6179aa..2bc6eb923bb 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -205,7 +205,7 @@ module.exports = { ].join(' ') }, hovertemplate: hovertemplateAttrs({}, { - keys: ['marker.size', 'marker.color'] + keys: constants.eventDataKeys }), line: { color: { diff --git a/src/traces/scatter/constants.js b/src/traces/scatter/constants.js index 788987e5e3e..ecd431ff974 100644 --- a/src/traces/scatter/constants.js +++ b/src/traces/scatter/constants.js @@ -21,5 +21,7 @@ module.exports = { // number of viewport sizes away from the visible region // at which we clip all lines to the perimeter - maxScreensAway: 20 + maxScreensAway: 20, + + eventDataKeys: ['marker.size', 'marker.color'] }; diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 1058c6400f6..0cdf575cfaf 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -14,6 +14,7 @@ var checkTicks = require('../assets/custom_assertions').checkTicks; var supplyAllDefaults = require('../assets/supply_defaults'); var checkEventData = require('../assets/check_event_data'); +var constants = require('@src/traces/bar/constants'); var customAssertions = require('../assets/custom_assertions'); var assertClip = customAssertions.assertClip; @@ -1861,7 +1862,7 @@ describe('bar hover', function() { describe('event data', function() { var mock = require('@mocks/stacked_bar'); - checkEventData(mock, 216, 309, []); + checkEventData(mock, 216, 309, constants.eventDataKeys); }); function mockBarPlot(dataWithoutTraceType, layout) { diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index bde931378c0..7415ae6eb67 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -14,6 +14,7 @@ var supplyAllDefaults = require('../assets/supply_defaults'); var failTest = require('../assets/fail_test'); var checkEventData = require('../assets/check_event_data'); +var constants = require('@src/traces/histogram/constants'); describe('Test histogram', function() { 'use strict'; @@ -1064,5 +1065,5 @@ describe('getBinSpanLabelRound', function() { describe('event data', function() { var mock = require('@mocks/hist_category'); - checkEventData(mock, 100, 200, ['binNumber']); + checkEventData(mock, 100, 200, constants.eventDataKeys); }); diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index d6edc08bde7..34a56aa7b38 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -15,6 +15,7 @@ var assertClip = customAssertions.assertClip; var assertNodeDisplay = customAssertions.assertNodeDisplay; var assertMultiNodeOrder = customAssertions.assertMultiNodeOrder; var checkEventData = require('../assets/check_event_data'); +var constants = require('@src/traces/scatter/constants'); var getOpacity = function(node) { return Number(node.style.opacity); }; var getFillOpacity = function(node) { return Number(node.style['fill-opacity']); }; @@ -1812,5 +1813,5 @@ describe('Test scatter *clipnaxis*:', function() { describe('event data', function() { var mock = require('@mocks/scatter-colorscale-colorbar'); - checkEventData(mock, 540, 260, ['marker.size', 'marker.color']); + checkEventData(mock, 540, 260, constants.eventDataKeys); }); From 583eb9739fba72b620473b5112577aa808361a75 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 14 Nov 2018 12:35:51 -0500 Subject: [PATCH 50/53] fix lint --- src/traces/bar/attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index 213c0ef62e7..319f0aa454d 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -13,7 +13,7 @@ var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes') var colorAttributes = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); var fontAttrs = require('../../plots/font_attributes'); -var constants = require('./constants.js') +var constants = require('./constants.js'); var extendFlat = require('../../lib/extend').extendFlat; From 14ac99fe1edcf2492ff0dd6569d7c254bb471e15 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 14 Nov 2018 18:24:03 -0500 Subject: [PATCH 51/53] scatter: list additionnal variables available in eventData --- src/traces/scatter/constants.js | 2 +- test/jasmine/tests/scatter_test.js | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/traces/scatter/constants.js b/src/traces/scatter/constants.js index ecd431ff974..ebacb4faee3 100644 --- a/src/traces/scatter/constants.js +++ b/src/traces/scatter/constants.js @@ -23,5 +23,5 @@ module.exports = { // at which we clip all lines to the perimeter maxScreensAway: 20, - eventDataKeys: ['marker.size', 'marker.color'] + eventDataKeys: ['marker.size', 'marker.color', 'marker.symbol', 'marker.opacity'] }; diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js index 34a56aa7b38..b288b706386 100644 --- a/test/jasmine/tests/scatter_test.js +++ b/test/jasmine/tests/scatter_test.js @@ -1813,5 +1813,14 @@ describe('Test scatter *clipnaxis*:', function() { describe('event data', function() { var mock = require('@mocks/scatter-colorscale-colorbar'); - checkEventData(mock, 540, 260, constants.eventDataKeys); + var mockCopy = Lib.extendDeep({}, mock); + + var marker = mockCopy.data[0].marker; + marker.opacity = []; + marker.symbol = []; + for(var i = 0; i < mockCopy.data[0].y.length; ++i) { + marker.opacity.push(0.5); + marker.symbol.push('square'); + } + checkEventData(mockCopy, 540, 260, constants.eventDataKeys); }); From 1caf3216470f5ce223ec266e39599db67e90a883 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 15 Nov 2018 12:32:39 -0500 Subject: [PATCH 52/53] hovertemplate: update desc, do no list attributes that are `arrayOK` --- src/components/fx/hovertemplate_attributes.js | 3 ++- src/traces/scatter/constants.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 49a10641356..b3dca52b374 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -19,7 +19,7 @@ module.exports = function(opts, extra) { for(var i = 0; i < keys.length; i++) { quotedKeys[i] = '`' + keys[i] + '`'; } - descPart = descPart + 'This trace supports the additional '; + descPart = descPart + 'Finally, this trace also supports '; if(keys.length === 1) { descPart = 'variable ' + quotedKeys[0]; } else { @@ -40,6 +40,7 @@ module.exports = function(opts, extra) { 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.', 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plot.ly/javascript/plotlyjs-events/#event-data.', + 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.' descPart ].join(' ') }; diff --git a/src/traces/scatter/constants.js b/src/traces/scatter/constants.js index ebacb4faee3..f356d9196f6 100644 --- a/src/traces/scatter/constants.js +++ b/src/traces/scatter/constants.js @@ -23,5 +23,5 @@ module.exports = { // at which we clip all lines to the perimeter maxScreensAway: 20, - eventDataKeys: ['marker.size', 'marker.color', 'marker.symbol', 'marker.opacity'] + eventDataKeys: [] }; From a78600a5689696b63e051454a6976c242698bc18 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 15 Nov 2018 12:39:02 -0500 Subject: [PATCH 53/53] fix syntax --- src/components/fx/hovertemplate_attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index b3dca52b374..2c19336a509 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -40,7 +40,7 @@ module.exports = function(opts, extra) { 'Numbers are formatted using d3-format\'s syntax %{variable:d3-format}, for example "Price: %{y:$.2f}".', 'See https://github.com/d3/d3-format/blob/master/README.md#locale_format for details on the formatting syntax.', 'The variables available in `hovertemplate` are the ones emitted as event data described at this link https://plot.ly/javascript/plotlyjs-events/#event-data.', - 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.' + 'Additionally, every attributes that can be specified per-point (the ones that are `arrayOk: true`) are available.', descPart ].join(' ') };