diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 6c1baf89ce5..da00361f35a 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -553,6 +553,8 @@ function computeLegendDimensions(gd, groups, traces) { var extraWidth = 0; + var traceGap = 5; + opts._width = 0; opts._height = 0; @@ -586,23 +588,53 @@ function computeLegendDimensions(gd, groups, traces) { extraWidth = 40; } else if(isGrouped) { - var groupXOffsets = [opts._width]; + var maxHeight = 0; + var maxWidth = 0; var groupData = groups.data(); - for(var i = 0, n = groupData.length; i < n; i++) { - var textWidths = groupData[i].map(function(legendItemArray) { + var maxItems = 0; + + var i; + for(i = 0; i < groupData.length; i++) { + var group = groupData[i]; + var groupWidths = group.map(function(legendItemArray) { return legendItemArray[0].width; }); - var groupWidth = 40 + Math.max.apply(null, textWidths); + var groupWidth = Lib.aggNums(Math.max, null, groupWidths); + var groupHeight = group.reduce(function(a, b) { + return a + b[0].height; + }, 0); - opts._width += opts.tracegroupgap + groupWidth; + maxWidth = Math.max(maxWidth, groupWidth); + maxHeight = Math.max(maxHeight, groupHeight); + maxItems = Math.max(maxItems, group.length); + } + maxWidth += traceGap; + maxWidth += 40; + + var groupXOffsets = [opts._width]; + var groupYOffsets = []; + var rowNum = 0; + for(i = 0; i < groupData.length; i++) { + if(fullLayout._size.w < (borderwidth + opts._width + traceGap + maxWidth)) { + groupXOffsets[groupXOffsets.length - 1] = groupXOffsets[0]; + opts._width = maxWidth; + rowNum++; + } else { + opts._width += maxWidth + borderwidth; + } + + var rowYOffset = (rowNum * maxHeight); + rowYOffset += rowNum > 0 ? opts.tracegroupgap : 0; + + groupYOffsets.push(rowYOffset); groupXOffsets.push(opts._width); } groups.each(function(d, i) { - Drawing.setTranslate(this, groupXOffsets[i], 0); + Drawing.setTranslate(this, groupXOffsets[i], groupYOffsets[i]); }); groups.each(function() { @@ -620,11 +652,13 @@ function computeLegendDimensions(gd, groups, traces) { groupHeight += textHeight; }); - - opts._height = Math.max(opts._height, groupHeight); }); - opts._height += 10 + borderwidth * 2; + var maxYLegend = groupYOffsets[groupYOffsets.length - 1] + maxHeight; + opts._height = 10 + (borderwidth * 2) + maxYLegend; + + var maxOffset = Math.max.apply(null, groupXOffsets); + opts._width = maxOffset + maxWidth + 40; opts._width += borderwidth * 2; } else { @@ -633,7 +667,6 @@ function computeLegendDimensions(gd, groups, traces) { var maxTraceWidth = 0; var offsetX = 0; var fullTracesWidth = 0; - var traceGap = opts.tracegroupgap || 5; // calculate largest width for traces and use for width of all legend items traces.each(function(d) { diff --git a/test/image/baselines/legend_horizontal_groups.png b/test/image/baselines/legend_horizontal_groups.png index 29536dc9b0f..facd97b7273 100644 Binary files a/test/image/baselines/legend_horizontal_groups.png and b/test/image/baselines/legend_horizontal_groups.png differ diff --git a/test/image/baselines/legendgroup_horizontal_wrapping.png b/test/image/baselines/legendgroup_horizontal_wrapping.png new file mode 100644 index 00000000000..19e410a63b4 Binary files /dev/null and b/test/image/baselines/legendgroup_horizontal_wrapping.png differ diff --git a/test/image/mocks/legendgroup_horizontal_wrapping.json b/test/image/mocks/legendgroup_horizontal_wrapping.json new file mode 100644 index 00000000000..a9a954b7900 --- /dev/null +++ b/test/image/mocks/legendgroup_horizontal_wrapping.json @@ -0,0 +1,57 @@ +{ + "data": [{ + "x": [1, 2, 3, 4], + "y": [63.69, 62.55, 61.64, 61.39], + "legendgroup": 1, + "name": "Trace A-1" + }, { + "x": [1, 2, 3, 4], + "y": [58.24, 54.93, 42.11, 50.75], + "legendgroup": 1, + "name":"Trace A-2" + }, { + "x": [1, 2, 3, 4], + "y": [51.49, 49.59, 37.12, 31.45], + "legendgroup":2, + "name":"Trace B-1" + }, { + "x": [1, 2, 3, 4], + "y": [49.09, 58.54, 53.91, 43.12], + "legendgroup":2, + "name":"Trace B-2" + }, { + "x": [1, 2, 3, 4], + "y": [70.53, 72.51, 72.28, 78.65], + "name":"Trace C-1" + }, { + "x": [1, 2, 3, 4], + "y": [62.69, 59.09, 63.82, 62], + "legendgroup":3, + "name":"Trace D-1" + }, { + "x": [1, 2, 3, 4], + "y": [76.27, 71.43, 59.83, 64.34], + "legendgroup":3, + "name":"Trace D-2" + }, { + "x": [1, 2, 3, 4], + "y": [71.15, 81.82, 88.46, 74.29], + "name":"Trace E-1" + }], + "layout": { + "width": 600, + "legend": { + "orientation": "h" + }, + "xaxis": { + "type": "linear", + "range": [0.7840236686390533, 4.215976331360947], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [27.274108280254776, 92.63589171974522], + "autorange": true + } + } +} diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index 5c0b3b9cf28..4930bd6b571 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -703,6 +703,34 @@ describe('legend relayout update', function() { .then(done); }); }); + + describe('with legendgroup', function() { + var mock = require('@mocks/legendgroup_horizontal_wrapping.json'); + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + afterEach(destroyGraphDiv); + + it('changes the margin size to fit tracegroupgap', function(done) { + var mockCopy = Lib.extendDeep({}, mock); + Plotly.newPlot(gd, mockCopy) + .then(function() { + expect(gd._fullLayout._size.b).toBe(130); + return Plotly.relayout(gd, 'legend.tracegroupgap', 70); + }) + .then(function() { + expect(gd._fullLayout._size.b).toBe(185); + return Plotly.relayout(gd, 'legend.tracegroupgap', 10); + }) + .then(function() { + expect(gd._fullLayout._size.b).toBe(130); + }) + .catch(failTest) + .then(done); + }); + }); }); describe('legend orientation change:', function() {