diff --git a/src/components/legend/attributes.js b/src/components/legend/attributes.js index 2920f1ba6f4..8eb3d9557ce 100644 --- a/src/components/legend/attributes.js +++ b/src/components/legend/attributes.js @@ -42,6 +42,13 @@ module.exports = { role: 'info', description: 'Sets the orientation of the legend.' }, + horizontalspacing: { + valType: 'enumerated', + values: ['column', 'wrapped'], + dflt: ['column'], + role: 'info', + description: 'Sets whether a horizontal legend is broken into columns or wrapped horizontally.' + }, traceorder: { valType: 'flaglist', flags: ['reversed', 'grouped'], diff --git a/src/components/legend/defaults.js b/src/components/legend/defaults.js index 39e9bd80c70..280a0c1f35c 100644 --- a/src/components/legend/defaults.js +++ b/src/components/legend/defaults.js @@ -23,6 +23,7 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { var visibleTraces = 0, defaultOrder = 'normal', + defaultHorizontalSpacing = 'column', defaultX, defaultY, defaultXAnchor, @@ -87,5 +88,6 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) { coerce('xanchor', defaultXAnchor); coerce('y', defaultY); coerce('yanchor', defaultYAnchor); + coerce('horizontalspacing', defaultHorizontalSpacing); Lib.noneOrAll(containerIn, containerOut, ['x', 'y']); }; diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index fd08a20baa5..eb1a38a8d6a 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -473,7 +473,8 @@ function computeLegendDimensions(gd, groups, traces) { var fullLayout = gd._fullLayout, opts = fullLayout.legend, borderwidth = opts.borderwidth, - isGrouped = helpers.isGrouped(opts); + isGrouped = helpers.isGrouped(opts), + isHorizontalColumn = helpers.isHorizontalColumn(opts); if(helpers.isVertical(opts)) { if(isGrouped) { @@ -590,14 +591,14 @@ function computeLegendDimensions(gd, groups, traces) { maxTraceWidth = 0, offsetX = 0; - // calculate largest width for traces and use for width of all legend items + // calculate largest width for traces and use for width of all legend items (if horizontalspacing mode is 'column') traces.each(function(d) { maxTraceWidth = Math.max(40 + d[0].width, maxTraceWidth); }); traces.each(function(d) { var legendItem = d[0], - traceWidth = maxTraceWidth, + traceWidth = isHorizontalColumn ? maxTraceWidth : 40 + d[0].width, traceGap = opts.tracegroupgap || 5; if((borderwidth + offsetX + traceGap + traceWidth) > (fullLayout.width - (fullLayout.margin.r + fullLayout.margin.l))) { diff --git a/src/components/legend/helpers.js b/src/components/legend/helpers.js index 2c4813a05de..f4a052ce4ba 100644 --- a/src/components/legend/helpers.js +++ b/src/components/legend/helpers.js @@ -27,3 +27,7 @@ exports.isVertical = function isVertical(legendLayout) { exports.isReversed = function isReversed(legendLayout) { return (legendLayout.traceorder || '').indexOf('reversed') !== -1; }; + +exports.isHorizontalColumn = function isHorizontalColumn(legendLayout) { + return legendLayout.horizontalspacing === 'column'; +} \ No newline at end of file diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index c832da02dcd..fd343d13b89 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -413,6 +413,15 @@ describe('legend helpers:', function() { expect(isReversed({ traceorder: 'reversed' })).toBe(true); }); }); + + describe('isHorizontalColumn', function() { + var isHorizontalColumn = helpers.isHorizontalColumn; + + it('should return true when option horizontalspacing is "column"', function() { + expect(isHorizontalColumn({ horizontalspacing: 'column'})).toBe(true); + expect(isHorizontalColumn({ horizontalspacing: 'wrapped'})).toBe(false); + }); + }); }); describe('legend anchor utils:', function() { @@ -628,3 +637,54 @@ describe('legend restyle update', function() { }); }); }); + +describe('legend horizontal spacing', function() { + 'use strict'; + + beforeAll(function() { + jasmine.addMatchers(customMatchers); + }); + + afterEach(destroyGraphDiv); + + it('should not space the items equally if horizontalspacing is wrapped', function(done) { + var mock = require('@mocks/0.json'), + mockCopy = Lib.extendDeep({}, mock), + traceTransforms = [ + 'translate(1, 15.5)', + 'translate(120, 15.5)', + 'translate(231.68333435058594, 15.5)', + ], + gd = createGraphDiv(); + mockCopy.layout.legend.horizontalspacing = 'wrapped'; + mockCopy.layout.legend.orientation = 'h'; + Plotly.plot(gd, mockCopy.data, mockCopy.layout); + var nodes = d3.select(gd).selectAll('g.traces'); + nodes.each(function(n, i) { + var node = d3.select(this); + var transform = node.attr('transform'); + expect(transform).toEqual(traceTransforms[i]); + }); + done(); + }); + + it('should space the items equally if horizontalspacing is column', function(done) { + var mock = require('@mocks/0.json'), + mockCopy = Lib.extendDeep({}, mock), + traceTransforms = [ + 'translate(1, 15.5)', + 'translate(120, 15.5)', + 'translate(239, 15.5)', + ], + gd = createGraphDiv(); + mockCopy.layout.legend.orientation = 'h'; + Plotly.plot(gd, mockCopy.data, mockCopy.layout); + var nodes = d3.select(gd).selectAll('g.traces'); + nodes.each(function(n, i) { + var node = d3.select(this); + var transform = node.attr('transform'); + expect(transform).toEqual(traceTransforms[i]); + }); + done(); + }); +});