Skip to content

Commit bd1d6bd

Browse files
authored
Merge pull request #6202 from nickmelnikov82/additional-legendgroup-options
add `entrywidth` and `entrywidthmode` to legend
2 parents 3bab776 + 08cb098 commit bd1d6bd

File tree

7 files changed

+448
-7
lines changed

7 files changed

+448
-7
lines changed

src/components/legend/attributes.js

+13
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ module.exports = {
7474
'Sets the amount of vertical space (in px) between legend groups.'
7575
].join(' ')
7676
},
77+
entrywidth: {
78+
valType: 'number',
79+
min: 0,
80+
editType: 'legend',
81+
description: 'Sets the width (in px or fraction) of the legend.',
82+
},
83+
entrywidthmode: {
84+
valType: 'enumerated',
85+
values: ['fraction', 'pixels'],
86+
dflt: 'pixels',
87+
editType: 'legend',
88+
description: 'Determines what entrywidth means.',
89+
},
7790
itemsizing: {
7891
valType: 'enumerated',
7992
values: ['trace', 'constant'],

src/components/legend/defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ module.exports = function legendDefaults(layoutIn, layoutOut, fullData) {
122122
coerce('traceorder', defaultOrder);
123123
if(helpers.isGrouped(layoutOut.legend)) coerce('tracegroupgap');
124124

125+
coerce('entrywidth');
126+
coerce('entrywidthmode');
125127
coerce('itemsizing');
126128
coerce('itemwidth');
127129

src/components/legend/draw.js

+37-7
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,23 @@ function _draw(gd, legendObj) {
351351
}], gd);
352352
}
353353

354+
function getTraceWidth(trace, legendObj, textGap) {
355+
var legendItem = trace[0];
356+
var legendWidth = legendItem.width;
357+
358+
var traceLegendWidth = legendItem.trace.legendwidth || legendObj.entrywidth;
359+
360+
if(traceLegendWidth) {
361+
if(legendObj.entrywidthmode === 'pixels') {
362+
return traceLegendWidth + textGap;
363+
} else {
364+
return legendObj._maxWidth * traceLegendWidth;
365+
}
366+
}
367+
368+
return legendWidth + textGap;
369+
}
370+
354371
function clickOrDoubleClick(gd, legend, legendItem, numClicks, evt) {
355372
var trace = legendItem.data()[0][0].trace;
356373
var evtData = {
@@ -636,6 +653,7 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
636653
var isAbovePlotArea = legendObj.y > 1 || (legendObj.y === 1 && yanchor === 'bottom');
637654

638655
var traceGroupGap = legendObj.tracegroupgap;
656+
var legendGroupWidths = {};
639657

640658
// - if below/above plot area, give it the maximum potential margin-push value
641659
// - otherwise, extend the height of the plot area
@@ -688,7 +706,7 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
688706
var maxItemWidth = 0;
689707
var combinedItemWidth = 0;
690708
traces.each(function(d) {
691-
var w = d[0].width + textGap;
709+
var w = getTraceWidth(d, legendObj, textGap);
692710
maxItemWidth = Math.max(maxItemWidth, w);
693711
combinedItemWidth += w;
694712
});
@@ -704,15 +722,16 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
704722
var maxWidthInGroup = 0;
705723
var offsetY = 0;
706724
d3.select(this).selectAll('g.traces').each(function(d) {
707-
var w = d[0].width;
725+
var w = getTraceWidth(d, legendObj, textGap);
708726
var h = d[0].height;
709727

710728
Drawing.setTranslate(this,
711729
titleSize[0],
712730
titleSize[1] + bw + itemGap + h / 2 + offsetY
713731
);
714732
offsetY += h;
715-
maxWidthInGroup = Math.max(maxWidthInGroup, textGap + w);
733+
maxWidthInGroup = Math.max(maxWidthInGroup, w);
734+
legendGroupWidths[d[0].trace.legendgroup] = maxWidthInGroup;
716735
});
717736

718737
var next = maxWidthInGroup + itemGap;
@@ -750,8 +769,12 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
750769
var rowWidth = 0;
751770
traces.each(function(d) {
752771
var h = d[0].height;
753-
var w = textGap + d[0].width;
754-
var next = (oneRowLegend ? w : maxItemWidth) + itemGap;
772+
var w = getTraceWidth(d, legendObj, textGap, isGrouped);
773+
var next = (oneRowLegend ? w : maxItemWidth);
774+
775+
if(legendObj.entrywidthmode !== 'fraction') {
776+
next += itemGap;
777+
}
755778

756779
if((next + bw + offsetX - itemGap) >= legendObj._maxWidth) {
757780
maxRowWidth = Math.max(maxRowWidth, rowWidth);
@@ -802,8 +825,15 @@ function computeLegendDimensions(gd, groups, traces, legendObj) {
802825
traces.each(function(d) {
803826
var traceToggle = d3.select(this).select('.legendtoggle');
804827
var h = d[0].height;
805-
var w = isEditable ? textGap : (toggleRectWidth || (textGap + d[0].width));
806-
if(!isVertical) w += itemGap / 2;
828+
var legendgroup = d[0].trace.legendgroup;
829+
var traceWidth = getTraceWidth(d, legendObj, textGap);
830+
if(isGrouped && legendgroup !== '') {
831+
traceWidth = legendGroupWidths[legendgroup];
832+
}
833+
var w = isEditable ? textGap : (toggleRectWidth || traceWidth);
834+
if(!isVertical && legendObj.entrywidthmode !== 'fraction') {
835+
w += itemGap / 2;
836+
}
807837
Drawing.setRect(traceToggle, 0, -h / 2, w, h);
808838
});
809839
}

src/plots/attributes.js

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ module.exports = {
7272
'and ranks greater than 1000 to go after all unranked items.'
7373
].join(' ')
7474
},
75+
legendwidth: {
76+
valType: 'number',
77+
min: 0,
78+
editType: 'style',
79+
description: 'Sets the width (in px or fraction) of the legend for this trace.',
80+
},
7581
opacity: {
7682
valType: 'number',
7783
min: 0,

src/plots/plots.js

+1
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac
13201320
'showlegend'
13211321
);
13221322

1323+
coerce('legendwidth');
13231324
coerce('legendgroup');
13241325
coerce('legendgrouptitle.text');
13251326
coerce('legendrank');

test/jasmine/tests/legend_test.js

+85
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var DBLCLICKDELAY = require('@src/plot_api/plot_config').dfltConfig.doubleClickD
66
var Legend = require('@src/components/legend');
77
var getLegendData = require('@src/components/legend/get_legend_data');
88
var helpers = require('@src/components/legend/helpers');
9+
var constants = require('@src/components/legend/constants');
910

1011
var d3Select = require('../../strict-d3').select;
1112
var d3SelectAll = require('../../strict-d3').selectAll;
@@ -2345,3 +2346,87 @@ describe('legend with custom doubleClickDelay', function() {
23452346
.then(done, done.fail);
23462347
}, 3 * jasmine.DEFAULT_TIMEOUT_INTERVAL);
23472348
});
2349+
2350+
describe('legend with custom legendwidth', function() {
2351+
var gd;
2352+
2353+
var data = [
2354+
{x: [1, 2, 1], y: [1, 2, 1], name: 'legend text 1'},
2355+
{x: [2, 1, 2], y: [2, 1, 2], name: 'legend text 12'},
2356+
{x: [2, 3, 4], y: [2, 3, 4], name: 'legend text 123'}
2357+
];
2358+
2359+
var layout = {
2360+
legend: {
2361+
orientation: 'h'
2362+
}
2363+
};
2364+
2365+
beforeEach(function() {
2366+
gd = createGraphDiv();
2367+
});
2368+
2369+
afterEach(destroyGraphDiv);
2370+
2371+
function assertLegendTextWidth(variants) {
2372+
var nodes = d3SelectAll('rect.legendtoggle');
2373+
var index = 0;
2374+
nodes.each(function() {
2375+
var node = d3Select(this);
2376+
var w = +node.attr('width');
2377+
if(variants[index]) expect(w).toEqual(variants[index]);
2378+
index += 1;
2379+
});
2380+
}
2381+
2382+
it('should change width when trace has legendwidth', function(done) {
2383+
var extendedData = Lib.extendDeep([], data);
2384+
extendedData.forEach(function(trace, index) {
2385+
trace.legendwidth = (index + 1) * 50;
2386+
});
2387+
2388+
var textGap = 30 + constants.itemGap * 2 + constants.itemGap / 2;
2389+
2390+
Plotly.newPlot(gd, {data: extendedData, layout: layout}).then(function() {
2391+
assertLegendTextWidth([50 + textGap, 100 + textGap, 150 + textGap]);
2392+
}).then(done);
2393+
});
2394+
2395+
it('should change width when legend has entrywidth', function(done) {
2396+
var extendedLayout = Lib.extendDeep([], layout);
2397+
var width = 50;
2398+
extendedLayout.legend.entrywidth = width;
2399+
2400+
var textGap = 30 + constants.itemGap * 2 + constants.itemGap / 2;
2401+
2402+
Plotly.newPlot(gd, {data: data, layout: extendedLayout}).then(function() {
2403+
assertLegendTextWidth([width + textGap, width + textGap, width + textGap]);
2404+
}).then(done);
2405+
});
2406+
2407+
it('should change group width when trace has legendwidth', function(done) {
2408+
var extendedLayout = Lib.extendDeep([], layout);
2409+
extendedLayout.legend.traceorder = 'grouped';
2410+
2411+
var extendedData = Lib.extendDeep([], data);
2412+
extendedData[0].legendwidth = 100;
2413+
extendedData[0].legendgroup = 'test';
2414+
extendedData[1].legendgroup = 'test';
2415+
2416+
var textGap = 30 + constants.itemGap * 2 + constants.itemGap / 2;
2417+
2418+
Plotly.newPlot(gd, {data: extendedData, layout: extendedLayout}).then(function() {
2419+
assertLegendTextWidth([100 + textGap, 100 + textGap, undefined]);
2420+
}).then(done);
2421+
});
2422+
2423+
it('should change width when legend has entrywidth and entrywidthmode is fraction', function(done) {
2424+
var extendedLayout = Lib.extendDeep([], layout);
2425+
extendedLayout.legend.entrywidthmode = 'fraction';
2426+
extendedLayout.legend.entrywidth = 0.3;
2427+
2428+
Plotly.newPlot(gd, {data: data, layout: extendedLayout}).then(function() {
2429+
assertLegendTextWidth([162, 162, 162]);
2430+
}).then(done);
2431+
});
2432+
});

0 commit comments

Comments
 (0)