diff --git a/src/components/fx/attributes.js b/src/components/fx/attributes.js index 65685335015..8ea95a84031 100644 --- a/src/components/fx/attributes.js +++ b/src/components/fx/attributes.js @@ -33,6 +33,20 @@ module.exports = { family: extendFlat({}, fontAttrs.family, { arrayOk: true }), size: extendFlat({}, fontAttrs.size, { arrayOk: true }), color: extendFlat({}, fontAttrs.color, { arrayOk: true }) + }, + namelength: { + valType: 'integer', + min: -1, + arrayOk: true, + role: 'style', + description: [ + 'Sets the length (in number of characters) of the trace name in', + 'the hover labels for this trace. -1 shows the whole name', + 'regardless of length. 0-3 shows the first 0-3 characters, and', + 'an integer >3 will show the whole name if it is less than that', + 'many characters, but if it is longer, will truncate to', + '`namelength - 3` characters and add an ellipsis.' + ].join(' ') } } }; diff --git a/src/components/fx/calc.js b/src/components/fx/calc.js index 38433e3094f..677dc620778 100644 --- a/src/components/fx/calc.js +++ b/src/components/fx/calc.js @@ -41,6 +41,7 @@ module.exports = function calc(gd) { fillFn(trace.hoverlabel.font.size, cd, 'hts'); fillFn(trace.hoverlabel.font.color, cd, 'htc'); fillFn(trace.hoverlabel.font.family, cd, 'htf'); + fillFn(trace.hoverlabel.namelength, cd, 'hnl'); } }; diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 3a90939bb0f..ffc44a338ce 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -689,7 +689,12 @@ function createHoverText(hoverData, opts, gd) { // strip out our pseudo-html elements from d.name (if it exists at all) name = svgTextUtils.plainText(d.name || ''); - if(name.length > 15) name = name.substr(0, 12) + '...'; + var nameLength = Math.round(d.nameLength); + + if(nameLength > -1 && name.length > nameLength) { + if(nameLength > 3) name = name.substr(0, nameLength - 3) + '...'; + else name = name.substr(0, nameLength); + } } // used by other modules (initially just ternary) that @@ -1061,6 +1066,7 @@ function cleanPoint(d, hovermode) { fill('fontFamily', 'htf', 'hoverlabel.font.family'); fill('fontSize', 'hts', 'hoverlabel.font.size'); fill('fontColor', 'htc', 'hoverlabel.font.color'); + fill('nameLength', 'hnl', 'hoverlabel.namelength'); d.posref = hovermode === 'y' ? (d.x0 + d.x1) / 2 : (d.y0 + d.y1) / 2; diff --git a/src/components/fx/hoverlabel_defaults.js b/src/components/fx/hoverlabel_defaults.js index 85fb682dee0..0b3573fe92d 100644 --- a/src/components/fx/hoverlabel_defaults.js +++ b/src/components/fx/hoverlabel_defaults.js @@ -15,5 +15,6 @@ module.exports = function handleHoverLabelDefaults(contIn, contOut, coerce, opts coerce('hoverlabel.bgcolor', opts.bgcolor); coerce('hoverlabel.bordercolor', opts.bordercolor); + coerce('hoverlabel.namelength', opts.namelength); Lib.coerceFont(coerce, 'hoverlabel.font', opts.font); }; diff --git a/src/components/fx/layout_attributes.js b/src/components/fx/layout_attributes.js index f09e0dff0d1..ef975bc79d4 100644 --- a/src/components/fx/layout_attributes.js +++ b/src/components/fx/layout_attributes.js @@ -55,6 +55,20 @@ module.exports = { dflt: constants.HOVERFONTSIZE }), color: extendFlat({}, fontAttrs.color) + }, + namelength: { + valType: 'integer', + min: -1, + dflt: 15, + role: 'style', + description: [ + 'Sets the default length (in number of characters) of the trace name in', + 'the hover labels for all traces. -1 shows the whole name', + 'regardless of length. 0-3 shows the first 0-3 characters, and', + 'an integer >3 will show the whole name if it is less than that', + 'many characters, but if it is longer, will truncate to', + '`namelength - 3` characters and add an ellipsis.' + ].join(' ') } } }; diff --git a/src/lib/coerce.js b/src/lib/coerce.js index eacdfe1f637..bdca900b14c 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -94,7 +94,7 @@ exports.valObjects = { 'are coerced to the `dflt`.' ].join(' '), requiredOpts: [], - otherOpts: ['dflt', 'min', 'max'], + otherOpts: ['dflt', 'min', 'max', 'arrayOk'], coerceFunction: function(v, propOut, dflt, opts) { if(v % 1 || !isNumeric(v) || (opts.min !== undefined && v < opts.min) || diff --git a/test/jasmine/tests/fx_test.js b/test/jasmine/tests/fx_test.js index 3ffea51d51c..3844045d65c 100644 --- a/test/jasmine/tests/fx_test.js +++ b/test/jasmine/tests/fx_test.js @@ -148,7 +148,8 @@ describe('Fx defaults', function() { family: 'Roboto', size: 40, color: 'pink' - } + }, + namelength: 15 }); expect(out.data[1].hoverlabel).toEqual({ @@ -158,7 +159,8 @@ describe('Fx defaults', function() { family: 'Roboto', size: 20, color: 'red' - } + }, + namelength: 15 }); expect(out.layout.annotations[0].hoverlabel).toEqual({ diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js index 5498d69aff1..92f713a722f 100644 --- a/test/jasmine/tests/hover_label_test.js +++ b/test/jasmine/tests/hover_label_test.js @@ -683,7 +683,13 @@ describe('hover info on stacked subplots', function() { }); describe('hover info on stacked subplots with shared y-axis', function() { - var mock = require('@mocks/stacked_subplots_shared_yaxis.json'); + var mock = Lib.extendDeep(require('@mocks/stacked_subplots_shared_yaxis.json')); + mock.data[0].name = 'Who put the bomp in the bomp bah bomp bah bomp'; + mock.data[0].hoverlabel = {namelength: -1}; + mock.data[1].name = 'Who put the ram in the rama lama ding dong'; + mock.data[1].hoverlabel = {namelength: [2, 4]}; + mock.data[2].name = 'Who put the bop in the bop shoo bop shoo bop'; + mock.layout.hoverlabel = {namelength: 10}; beforeEach(function(done) { Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(done); @@ -727,11 +733,11 @@ describe('hover info on stacked subplots', function() { expect(d3.selectAll('g.hovertext').size()).toEqual(3); var textNodes = d3.selectAll('g.hovertext').selectAll('text'); - expect(textNodes[0][0].innerHTML).toEqual('trace 0'); + expect(textNodes[0][0].innerHTML).toEqual('Who put the bomp in the bomp bah bomp bah bomp'); expect(textNodes[0][1].innerHTML).toEqual('1'); - expect(textNodes[1][0].innerHTML).toEqual('trace 1'); + expect(textNodes[1][0].innerHTML).toEqual('Wh'); expect(textNodes[1][1].innerHTML).toEqual('2.1'); - expect(textNodes[2][0].innerHTML).toEqual('trace 2'); + expect(textNodes[2][0].innerHTML).toEqual('Who put...'); expect(textNodes[2][1].innerHTML).toEqual('3'); }); });