From 3cee835e320ede161ea72c4a9c90f72901bba235 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 15 Apr 2021 17:28:50 -0400 Subject: [PATCH 01/20] implement legendrank --- src/components/legend/get_legend_data.js | 22 +++++++++++++++++----- src/plots/attributes.js | 10 ++++++++++ src/plots/plots.js | 1 + src/traces/parcats/attributes.js | 1 + 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 7e09fa32337..c7ac7f82e1d 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -71,21 +71,33 @@ module.exports = function getLegendData(calcdata, opts) { var ltraces; var legendData; + // sort considering trace.legendrank and legend.traceorder + var dir = helpers.isReversed(opts) ? -1 : 1; + var orderFn = function(a, b) { + var A = a[0].trace; + var B = b[0].trace; + var delta = A.legendrank - B.legendrank; + if(!delta) delta = A.index - B.index; + + return dir * delta; + }; + if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { legendData = new Array(lgroupsLength); for(i = 0; i < lgroupsLength; i++) { ltraces = lgroupToTraces[lgroups[i]]; - legendData[i] = helpers.isReversed(opts) ? ltraces.reverse() : ltraces; + legendData[i] = ltraces.sort(orderFn); } } else { // collapse all groups into one if all groups are blank - legendData = [new Array(lgroupsLength)]; - + legendData = [[]]; for(i = 0; i < lgroupsLength; i++) { - ltraces = lgroupToTraces[lgroups[i]][0]; - legendData[0][helpers.isReversed(opts) ? lgroupsLength - i - 1 : i] = ltraces; + legendData[0].push( + lgroupToTraces[lgroups[i]][0] + ); } + legendData[0] = legendData[0].sort(orderFn); lgroupsLength = 1; } diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 20ff1c2a3b3..0e79717baf2 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -41,6 +41,16 @@ module.exports = { 'when toggling legend items.' ].join(' ') }, + legendrank: { + valType: 'number', + dflt: 1000, + editType: 'style', + description: [ + 'Sets the legend rank for this trace.', + 'Items with smaller ranks would be presented on top/left side while', + 'with `*reversed* `legend.traceorder` they would be on bottom/right side.' + ].join(' ') + }, opacity: { valType: 'number', min: 0, diff --git a/src/plots/plots.js b/src/plots/plots.js index a3789026460..dd22614ccae 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1309,6 +1309,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac ); coerce('legendgroup'); + coerce('legendrank'); traceOut._dfltShowLegend = true; } else { diff --git a/src/traces/parcats/attributes.js b/src/traces/parcats/attributes.js index 0434074ad7a..cb0ddb4d912 100644 --- a/src/traces/parcats/attributes.js +++ b/src/traces/parcats/attributes.js @@ -198,6 +198,7 @@ module.exports = { hoverlabel: undefined, ids: undefined, legendgroup: undefined, + legendrank: undefined, opacity: undefined, selectedpoints: undefined, showlegend: undefined From a60a382ee0c72d5c50dc5cb36d7c4e3766c4ff42 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 15 Apr 2021 19:14:01 -0400 Subject: [PATCH 02/20] keep track of initial order - to handle chrome unstable sort in old versions --- src/components/legend/get_legend_data.js | 5 ++++- test/jasmine/tests/legend_test.js | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index c7ac7f82e1d..bc909f85887 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -11,8 +11,10 @@ module.exports = function getLegendData(calcdata, opts) { var lgroupi = 0; var maxNameLength = 0; var i, j; - + var initID = 0; function addOneItem(legendGroup, legendItem) { + legendItem._initID = initID++; + // each '' legend group is treated as a separate group if(legendGroup === '' || !helpers.isGrouped(opts)) { // TODO: check this against fullData legendgroups? @@ -78,6 +80,7 @@ module.exports = function getLegendData(calcdata, opts) { var B = b[0].trace; var delta = A.legendrank - B.legendrank; if(!delta) delta = A.index - B.index; + if(!delta) delta = a[0]._initID - b[0]._initID; return dir * delta; }; diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index a4599f9e6b8..ebb5676751b 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -283,14 +283,14 @@ describe('legend getLegendData', function() { expected = [ [ - [{trace: { + [{_initID: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_initID: 2, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -298,7 +298,7 @@ describe('legend getLegendData', function() { }}] ], [ - [{trace: { + [{_initID: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', @@ -341,20 +341,20 @@ describe('legend getLegendData', function() { expected = [ [ - [{trace: { + [{_initID: 0, trace: { type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_initID: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_initID: 2, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -427,20 +427,20 @@ describe('legend getLegendData', function() { expected = [ [ - [{trace: { + [{_initID: 2, trace: { type: 'box', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_initID: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_initID: 0, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -483,14 +483,14 @@ describe('legend getLegendData', function() { expected = [ [ - [{trace: { + [{_initID: 2, trace: { type: 'box', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_initID: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -498,7 +498,7 @@ describe('legend getLegendData', function() { }}] ], [ - [{trace: { + [{_initID: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', From 581371742e6fdc3df91004e1ccd8e02aac828a8f Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 16 Apr 2021 11:25:57 -0400 Subject: [PATCH 03/20] remove some empty lines --- test/jasmine/tests/legend_test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index ebb5676751b..b1f2746219e 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -260,7 +260,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: 'group', showlegend: true - }}], [{trace: { type: 'bar', @@ -288,7 +287,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: 'group', showlegend: true - }}], [{_initID: 2, trace: { type: 'scatter', @@ -318,7 +316,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: '', showlegend: true - }}], [{trace: { type: 'bar', @@ -346,7 +343,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: '', showlegend: true - }}], [{_initID: 1, trace: { type: 'bar', @@ -374,7 +370,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: '', showlegend: false - }}], [{trace: { type: 'box', @@ -404,7 +399,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: '', showlegend: true - }}], [{trace: { type: 'bar', @@ -432,7 +426,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: '', showlegend: true - }}], [{_initID: 1, trace: { type: 'bar', @@ -460,7 +453,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: 'group', showlegend: true - }}], [{trace: { type: 'bar', @@ -488,7 +480,6 @@ describe('legend getLegendData', function() { visible: true, legendgroup: 'group', showlegend: true - }}], [{_initID: 0, trace: { type: 'scatter', From 5136cc01af5a21f31a2064670d46fb3ea7e2723b Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 16 Apr 2021 14:26:35 -0400 Subject: [PATCH 04/20] add legend calc jasmine test with legendrank --- test/jasmine/tests/legend_test.js | 284 +++++++++++++++++++++++++++++- 1 file changed, 283 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index b1f2746219e..b080de5f936 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -248,7 +248,289 @@ describe('legend defaults', function() { }); }); -describe('legend getLegendData', function() { +describe('legend getLegendData user-defined legendrank', function() { + 'use strict'; + + var calcdata, opts, legendData, expected; + + it('should group legendgroup traces', function() { + calcdata = [ + [{trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}], + [{trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 1, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}] + ]; + opts = { + traceorder: 'grouped' + }; + + legendData = getLegendData(calcdata, opts); + + expected = [ + [ + [{_initID: 2, trace: { + legendrank: 1, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}], + [{_initID: 0, trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}] + ], + [ + [{_initID: 1, trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}] + ] + ]; + + expect(legendData).toEqual(expected); + expect(opts._lgroupsLength).toEqual(2); + }); + + it('should collapse when data has only one group', function() { + calcdata = [ + [{trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 1, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}] + ]; + opts = { + traceorder: 'grouped' + }; + + legendData = getLegendData(calcdata, opts); + + expected = [ + [ + [{_initID: 2, trace: { + legendrank: 1, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}], + [{_initID: 1, trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{_initID: 0, trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}] + ] + ]; + + expect(legendData).toEqual(expected); + expect(opts._lgroupsLength).toEqual(1); + }); + + it('should return empty array when legend data has no traces', function() { + calcdata = [ + [{trace: { + legendrank: 3, + type: 'histogram', + visible: true, + legendgroup: '', + showlegend: false + }}], + [{trace: { + legendrank: 2, + type: 'box', + visible: 'legendonly', + legendgroup: '', + showlegend: false + }}], + [{trace: { + legendrank: 1, + type: 'heatmap', + visible: true, + legendgroup: '' + }}] + ]; + opts = { + _main: true, + traceorder: 'normal' + }; + + legendData = getLegendData(calcdata, opts); + expect(legendData).toEqual([]); + }); + + it('should reverse the order when legend.traceorder is set', function() { + calcdata = [ + [{trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 1, + type: 'box', + visible: true, + legendgroup: '', + showlegend: true + }}] + ]; + opts = { + traceorder: 'reversed' + }; + + legendData = getLegendData(calcdata, opts); + + expected = [ + [ + [{_initID: 0, trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: '', + showlegend: true + }}], + [{_initID: 1, trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{_initID: 2, trace: { + legendrank: 1, + type: 'box', + visible: true, + legendgroup: '', + showlegend: true + }}] + ] + ]; + + expect(legendData).toEqual(expected); + expect(opts._lgroupsLength).toEqual(1); + }); + + it('should reverse the trace order within groups when reversed+grouped', function() { + calcdata = [ + [{trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}], + [{trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}], + [{trace: { + legendrank: 1, + type: 'box', + visible: true, + legendgroup: 'group', + showlegend: true + }}] + ]; + opts = { + traceorder: 'reversed+grouped' + }; + + legendData = getLegendData(calcdata, opts); + + expected = [ + [ + [{_initID: 0, trace: { + legendrank: 3, + type: 'scatter', + visible: true, + legendgroup: 'group', + showlegend: true + }}], + [{_initID: 2, trace: { + legendrank: 1, + type: 'box', + visible: true, + legendgroup: 'group', + showlegend: true + }}] + ], + [ + [{_initID: 1, trace: { + legendrank: 2, + type: 'bar', + visible: 'legendonly', + legendgroup: '', + showlegend: true + }}] + ] + ]; + + expect(legendData).toEqual(expected); + expect(opts._lgroupsLength).toEqual(2); + }); +}); + +describe('legend getLegendData default legendrank', function() { 'use strict'; var calcdata, opts, legendData, expected; From 598d60c9153baac1f03a2691554752a778d1d4a1 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 16 Apr 2021 16:55:28 -0400 Subject: [PATCH 05/20] add image test - pie ranking looks strange --- test/image/baselines/legendrank.png | Bin 0 -> 16381 bytes test/image/mocks/legendrank.json | 51 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/image/baselines/legendrank.png create mode 100644 test/image/mocks/legendrank.json diff --git a/test/image/baselines/legendrank.png b/test/image/baselines/legendrank.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9bbf50eab57654a06f4be022ef75297f75839d GIT binary patch literal 16381 zcmeIZWmJ{l+b#-7r?e;`9cuv!A|Txziol{vL`piOL0UQ`1Sx4)bjK2D5JbAWyPGrl z{onsS?>T#qJ@)>v$2lL~4>;C%oa=e!n$I2Aecjg`qN*%|hy55E2?+^LPFC^_5)v{L z{6%4+gDWIXdhSR_Pm$y##ooH=?|j2bjZnYp^yW7CYSh7H^5`l?FH<$UB}Si3JSS%C zsyZr4&r~6JTS2@_nWQ|M;gcNEL`_FmbGRqeT&+ zrTk+~zc2L7yncJ`Iv8~BjTY|A|X-c`rXs2U82V8U9Y?$Hc)oiqW5&=$8n>U-;%Six@96d0p_9 zRi@K+XOd^QP?Mm^?L@pknTN_cC4550$Ip~Tp~fxKP^(P=QQk42R>TTRBMNc66hoAg zv0x|gc%>ODAr9kYzdkH_!u!#1v`7oq?7?qrViL`1ApF972-RtGSnk02=F+JzP3Q?k zqo{S`Z*Fv4T-;%WZk@f+?sVPeUBjZ~P_`VO*EL_7+a{~WQ9m!A$N4jHadFigdD2R& z2_k;`W!VGIw9r6e0xV1~saJ>d+*V~IVLTc)W5MpLJ zCMI;fdPlLH$tt_WUsxxYjZRw{sY32#6BTBiOOYZalSJunB)c&19{bZu1R5SLcL#@t z`n9yQxGj5}&A7a7bhT6GWJgT4c#F(>-+8{U8h>cheDNc(4|Q&r39VOQF?|$U!Rs}z z)ws_~>q!=|@k(=I$+z!HA)nHvZ|{2IA%ALY3`igviZ|lD!oL+boBTV%pFj}Niv^`p zU&!*SB|n6cu=<&FMzGG&;Vx>*i;Mda`3QbG{Ql`^6%!9n-7`hl^OdBi&0HIi2*1Os zf3Qr&`~D~qZ!Z!KUD(dw?zBYArN1g7#kaSmEw?{|3F-F_dpVbOYUY#)p%kUp*G*@$ zZ4v}R*^@Lp79^*;GhbNMez4dnu*2xWs05tfE<&84vtmAeD^zxGAR$w{Gb`kc>V+Dc zdln-F@m!%sZGlE(C3>6W>OC#DC!AH~gwJI;${*L+F8FEh$l8r02B#Of{!mH@L_x#O zKEJxX+B;ZDG!VaqNNHHpDVLWwc#beSm(wY-!>&$u)J_AiN#%JT51DCMhuAWf!u`e8 zet$cdb{NPRr{TAk3|aa5+(fnBaYF#L&}yPwzKBh|AX@|G<>j?e-HV-eze@@`*&6bE zw5=(d7WGKgFXWucp9G1R6%skrTlx;q5D0_?DI+9i%)nbHCw!qTXsIuW+d`eOUEP2V zCUSdDPX>kA%6NO<-MAcY{Kc5_JfkXq-yiCAb4km=fe+lEDQot0XDVJan+&S8f=9{s zOb`8yn7`{xy_3au4Nd}PTG7*31rHBFeUAevkHc=}JpCrOP(#ms1ctn$6eW0q$;o-w zq7W(zOrNN5*pKmw=dhN9gv7BE^W$t|z88P4^0!bwG@QkX0pYgQ{*?IOF8b-+nT9`2 z?k<%dXS9nOvuigMP%VM2vKH1X2gagVF8mDGB&<-&RSpggRxNCq>h*nqG2Hyd0^v6A zi!mI^>=Y6a5qKUaBS!e$#|P=8mYjm(Q=(yx@NP5|MwiWFF)T|6-AF9rfV|E7j+#;k z;k-aQx8Y?4`zr0Byl4v*4a07CBYiGBkLDB-C}$E%KAM6CmGxA$x=kmw*ZG2bg!pNN+u3fh z*PoR{;o`*tPE9n0(~@^5%y&w&!->uox)rmJB71a!sd=A;!Oc z@gBBs^(DKo6@FD|S6S$D;fyO`CnWayR?ZUjpjQ^XyuBFkE(kYeWmZXh8!325rrJ|( z+B-}?9Yo`mH#^$RCNk?j7?(wLO4eOtn8^}g)nvlCLQPJ`Z zhZb_Tj`##|tdi%#)7v+DMs=66u2*trf3jo>dGicB4gy{g{am~N?>>!&ex$7uRpGP{ zyyIMd(cjRJN8QcpHTU1K;-VN zyGo?c>DO0Yw2QCom%HLr9F}1c+L8W?Cb!oY76b~vzjpH%lk2-8gZQB?!@K+C#eWloGD@?Xk2U8W^$oPiolJ&{x;W>lN>M~Dr zDt}C8;hfi%64iX1j1>Pyzc|XQIB-n%Xs!vBi#Tcg*q--|ly*_EYvp2`rKdEXmI-6^ z&81P?z`c%b%?d&KV*(!)MYFI#f}=(ufeKAeuz`j+`0ZcFWzKeI2+7EP zFdJLNqk4P=ce_UlPkFnB3)B_#Mw(sr^qo8#rc&o1^W7Y5}-36RqXiz@d)(ue7N;y^haQs7(wtL5rS z8dcJTT~8;>Mg~AS$}Qnar2BccSy*a3S!v#tz@CzpSEf$1{~AS@6*g9Cpb5$8PvJG$ zpGRzNo<>j!EEbkF%O|1RBj*dg3=9e?B#qaVPS%~QvJ9%Wo+`Myfyw%yrqa(l_>I&% zSyBqRKD;gkXnlYEx zwd-uzj;6=gxzE4zEHu%zRAWi7t`e=Y&B~9=Vzdb|q`6ABAnVg(lR1;_Z zlgE!OlAE`q%Uqd}JCqHkJ*45w$uIG;HstT`{vTOWR3ZI+g60`tIvMTBg-2_N%^U>$j zs>?NzyO75`7A%(s2M3!6&)C@Hi_6~)6xgryY?@)uyxcwSZ#@e;n`M={E=9n#6j z9ZM+t+c{^3sz9??sk54WRvBD7OY^Nv!0njjbZ>6Y%?w%aWLPb5)7{<_JK@Lrdg^g< zV5(8_2t~tEXXH{ZXNzN!DTp(F%1sp#x7^*Nz#`0z|F%eg8BL*DX3$l2zYz0gZI&>ZWg#;k%(0?&zQP7+Zr zKbYP0Th6Au_r=k3ggn(g@SH-4F?;ma5jS0HR^6OGmU{_5v7YQU?9HB;X)qI_^|Fb@ zo{~Ug=LK68&9+l@jba=^tUAU0- zpLcsPkF9W;Cml&0-kZp|6tAg|(6MRCi9qW%$?(9V>qgG*5>8Cji8~7WPn#4%>t*00 zc=neTg#`IF08ksDMyFIZzyEgiqxee%epmMtah>bF2JcJT?>!qUmJbJ+|58-F6%j9E7~P^3(UouKZ=w8IKxLVWT` z0w-GcGiJL%DwO1PdhSfEorEbjzxQq9<&X&o{IF^(tsaoLf)k-lq-<|%_XdgrBmGR{ zzstFQi=BO!XufI!DMG^`6TJx99F92&}B ze16Lf1jFcC$F-Ks+LIwr$T4)>en`hBcOtIrFF6u+0@Eolu-2(TsEOGF$yg2+@Uygd zfLq9`rVE+mMmDKnC3xrwyPsNh2XB7B=yYtfEDSKex{y9VmZ{%#bM;HPN!lx3SYxY!^mZIVn*-AZ56Qpg*uz6+T zve_!^G18RP`}L5F)A#)Y5r?|wT+L%SM2rgFGMA!UA}x)LcHM>6X+``Z3PoDW~!gro6O%lfL@4Seq0eKef9Q))@AnAs!4A?Kg&k`>hXL zZ%)SIqo*}LoUWS5QCa@*7_H2p;GT3v@{Ooy;U4({fS;OkJ*wqBP}dfmIE$YeatCPY z*4Jk6JF*&sPDC~izGM-Ii$6FNt^Z_7g$7(VfnQDFUEGW#^qy3OVbJt9!DRi)GB)Jo z>59a9EVD`h372B@b}LC#Ux=8PnCsd%mtBkgjg)j&7(VK>lG1sBVRrzr{lPwnoBbr+ zACq-hKbo3DHuP5HGpD{>f7>?Y2d@ULcZSd<-P?c!+C3rcu&NkF$xn3np_in4H^qQ@ zBgt>#bu6F$r`HHZ>Dd zikT2R0&1gz)1nv!ka0$CKEDCa*iBkN+_J!^DN`NmS0R2YA%utBvAbyxF|iYvB@wzb zjyR*Y{ZDY6Qs;;s|7`CuCj`~`UgIDuY*wK^EzKdolt89Up9K~e{V5C?O~HVL1(rOn z27lpOlc9y3fcH?T{<~oks@X``y{8GgV-(R$H?ne+e*@~g0r2C)s; zIhq0uSjBX|YTo*TMy{bXzdd!~S?co_p%DXdTyN)MA>j5>L4G+{jd=Z=Bt_qzlOOzC z=i!LV6!)b(Z=NCM7&|)-1z#u3G&D!Ck*EM?7c*@S%J8ou-oj)Yx?ja3S!1lf%FZtP z`K>7Zsjnz)ri0m^`Fg0Zu$pR==;lj8>Y#e{B=!Tj3?~h4Cv2~rHvK_$lJ%}T=1eKw zif2>T=suLE`I!=>Vtl;_HN>)zo7Mz{dE@!w7gidf-Z^fmatY$sAG@M;g-^D|G+hXp z$bs+uFytq(<$bZF;Kz8roJm@D^ps!W^YzRVn?_$ya_1?8#0DsNagaAo@0n?|EvP|? zKxtsq_w_l8b|niapk0Ct|7A^^!y{%Y2oyC2f#)~Nhi~gvE;?zvB|m)l04f9YOkmM! z@YLX1_sQ>u&!x)U-;WA4_E-%R>vx}48EV~oZF65-=mR|w1(OaD>uDK7cNd4Xbem`OV?+X0mSG;H4R@TrRz%pdjDP&l zF^1ZUhpE(XoL}SL9rp$&;U7m-gDVbb?F81NSxOzVO5mQeXVB`VY4Wop>@Qc%uoKq$ zaWvht)!@22Cep=gu@I;VCLkV&GYv}!NT3eW+we@o39&!^1lS2!AE=GR5kus_hW~9y z34W&kcSyZYZg~S~emRlA+=%bZZU5y9K;|H4%8}n^%A(}g@FJpNaL7ZQU{%QdD%U5; zIffHRf3k6~6Xa|)`dMHaSeq5Oh8ree)i1E>-W=b+t$aQVq1~sx!uKT5Le!H9HTZEE z$P30`73yr@dsf&>(H9$j_ok!HeV@H9fiGO%d(8c+dvhg$K6#q#D2Cv|`dvVApAU2%>Tr%zWG(%^f_NsA2nnkYVwCWR6SKnz**D2^1eAC2N5sOVeNN@>%rot{4N~+ zj2ybB7vN`#m9Aj|z6fz9*S}ieN$fP>Xrx>nPG7S3R4J83$TN%EhmZXSrY?jukaOq; zv+GoUH)!!nFb0QpkJW&)dSPq4+IHES4YynBFdEK#UGPH}cJLnvdh?A&1$a(Es+RzE zLh~b4_>d9jS!s9Z=4S$7+5CwTJ%LK=DKY?eUQ+R6Czv}<;(1EJ?_ow%)WgP;QJv6F zAe6t(Ri+v)(bLhvWr1ycPqf@Q+nLIx#)|E|+G~k?-=CcECPyCdC4u?#o{f;{II8et z;`40XqI~7+tGTGB{;9X@# zZRn0$qwG09P|E-V!lL#g%p6fp^kUMD8QF#yn$ZZ*D!?#Qc6N4~?P`*e{u<{bMUMNu zEl_%yh+pIe9vK!)jEouJ6y)>7B_yiQBg3z#k(o7N-Hs`|HW8rM7EgmZl7rJCj8byO zg5Br3St3vZlYys=PzhA;eMU_sD>Cg(FapRwI3z@@RKH2J2==^qm=(q`FJe276o^aV z_cvE1%XZ->z`$HwLx4V@MRv#HV};r8s-Aunxx03t6!v%&%cSII_3P^DYD$3R!7K|5 z-LQ-eYn^F6+wf#}=1HXRCClbWVQwJ1I}E@bNwB)o=)0l0#6X~2D<3j5TCeNgw2c_T zUc{tY6s4N*Awt{9F`m}@UnqtN8PKxsn6~$E-;EZT$kqo`rpR4ZeyFmo_f@Pr-w+O{ zVjqC=A7Au6F(^$>0iQe37Of9zAxcoN+9PqLJVlL=+CGUC?j6VE`oJRfB!H^9JS4-WWs zp$7ZNYeKf}WVU>S6Df8A$DvgsYTrX!RX8X+gBTSO@&tFw%F3d8HGz$xiHL}B3)Qp- z`kCTLzoJDB!sKIt#o+65=un7w2`+#942Y==Ko{cUz1Vrr0@FUIup@dV+bV{LN$dWW z3h*f6aHV$y?-i)$xWK8qcAn^4nvrspv{y2mBJyE_FUJdv~)W zaE^wK0_d(hM|0HGfgr_CM|2Kli(R@!EaiZXRs3|wH*DG8pL$8mcP(IRMQqE4&DTIK zTc0qLe{_-7+J-ST{inN&Qj8^u8I^#n%a={6ZU6kiLK?d<*imatXDhI@rEuQaK}tg( zzZ@adyRFv#lp46%jokK}J$jIHno^C~NZDgV;p%inT2UnzIh%i!cGbh{uC^|X9PICQ zZ7*h!lk%S~l2joD-y+xAo;Onu5fxS9S|oOVUV|)4v<2ZjjcsYhTYZ6%B?ey}%@g*# z{H~fI{+{k#As$+osMt7IIViX{8_B$8apH`QCj*^-kE=AQ6)D=@$Y z3q)4UlOL)d!w>yIu6Y#jtCttP(fN72dND6w{}EwP9QyT5bg>(cf6EM|7=gN|PmLb> z)f&ot&4Q2MEd^8>ZOi$rKb=T<5YCOZAImM%ckMogn{9b-69j8<^b`_@wmF6 zbtp5IuPkp-opYcezI%jmJqs5=QW}9eHel+QZAvb&C@YT;&myEN* zy1#8s?TauB2r8Oq62s27KJHG3Xg zbr|rW^m!0I{8L_6s{qeVDW{nGk#Hqt*&BTXYg!AV<)TXebHyK?@!LMYl$pb%*|}7} zm-KH|I0Z%C4hJHJ!(Z@(;gEoOdwXf&1l4fzNVJda0f}zurZOT%AJ?@EC0STw`~*KK zua0|_PXAF;hiNZ1n*IEhvr4V*++T=&z{(FysyTOLoS=p^a?ZNmT@vWT-=2#@g|4Y4 z_nw;2kK!jHHAk~ZnPhdv6fQUkjvl81kqZYH_6^p!AUO8%S)6Uh>>;h;kNY1nb58AC z;(LQzn9B+m{03c#u9JZuAE|=uo82D4%hPqH?8M=r{$_14rz%ew6qstl0{o;9<(|gH zsTASsb1LJzyj2q58I{cG$0Uyh(90?Is_k9bpPlg6)Tuac`y0yXghR-G!8O_HO$C*) zM*-ENu1&!TD4MQldJK@qcM|OCHsd1wOJeNHW__*A;EvbINMq=HDG{Sxy$m#zj;xA$`&^k6Fh(75-h)rwHML@#jM|x%7yKGfdk5|w%abXJ zBIr`ecBVONy^i)&N~uF#F%5=qQTWTp&sKau`@q~(K9M6l5N`%v|B~?2J-#(h+SYcU zZ1Jd?VzMczv(tu`kGN8zbw&gT(oMdRjze3Fm|E;1W!`;QG#CZRI~~-QG`JunC*IR% z#rL#lNn^h(g3LJNN5uM7eL8yImJK+Le4+V34h78r+8>@lRDrksMAfbZx-?DUgd zG=)j_7ha98-dface|-S`U_KE}h*6E5U_f-c7wswAb%=rAP9kpw5u8A@d zy`~x_hI@0Rec#xeu{7e#%?KY=W6N;vDLJS+UAg*kqnS)r{8i;>Ol5C#1Xz0m-meVK z7)kGL2H8s6t?NH-RX3P9UUO8WXGQ4!_>soYxS@5PwK_a>r6JUqpr|w9NQ8dleT;%o z4BpvjymppT`zXhVN@)z{Xz04yhI+%&+C9=K2{ZVle22&94xK&l-Fyg@%TKK_Q?X3h zn|hD@Ww)`Z1IzV28braCGOfT7h52LgPsA4tJeMl%W{a;dvrHp_$J0ta5a^eR#lrwe z#jVo@DbCeCV0gzb#tKWH9uni&oRKwK>bw!AUk<}gXuK(WeZW^@s`q)_p~Inz`K(y= z1FhnDCu*0sRuL$;wU&Zzbd;BJIr~-KL;AOfp!^pckpVwSajzPQY=582w_`)fob=!y zGmN{BM8^q&mBQp)(%G84R=SfhsHiu5&Cf!5R<4=t$ZX@RoCGcqhjAg#hl$L|99kCL^)KB5>ID2g;o|D7& zBaFBOaCaGDBM|EL;;1=7r;jOA{n;B5zfC+fwzz99&PY2!F&=h2Tw-avvdHsAzc0y> zv`D|HZ>3vX;PLqdy~Bg*5AoXY9`XkN?F)^0*Pqm>4Q{_P&#qFw7@l}GmaYsqM(j<5 zOH73`26|kRVjL#b#n0ZQs==Xt)KeBYQUt60PS_ffxyoHPB_Xk!kgErULCG|gXbOz5 zpjj!`)2+Po`InLgmjpyHM7qLirWxjMf9p`tXxt1QA`huyoGujZQt9o=&n_|!T79pJ z*K>4y!K9R&E$nd`Cypq8gk?YqC4>;Nx2>${w@wb=rA!#v(iz}+kQ@jae`FU^ynZgA zTfa@u@C?<&BJ%Z5(;QX!SG(b!YhQdsPaHp%(Wflc?Rn>SI!8e{w<$cQFA>_WFXcRl zAN#GJ(^og&IK-#dV2{SJsy%beOCz<@wyOJS@6<7ql?M8SHTy=X{O2^fAq~OI67-c= z2{nR5yFCW@71(hDbt?lekR9 z0fYM*@E#Be_`CaxkigdBuaMv!ziz{^z;DV613%Gkexw@M};^XECz2+ z5XTG-*xLO?{9!mZG70H?&aL^#OXMt<-sUzeY^LOMmNL#L=oIfynBBSevUFd|CqP3k zPqv;=G+R5d!pL6NFa1pw42W3p=ws6jZMsYQ!hyg(gsV6>uY5vM!%6sgYPn{+SbkNi z$<1xLefgpjY_t;r6d<{{b(*F6_r0jPpIqEnf{2dak?C!8oKIQpgp{d*?ae<-qV{ff z4rH+*uN8Y=r!Lb3W09t}XKv*RY2-0P9UeGDcDjk4mKBkKczp^7TM7c&g@wTki706H zVQ`lav@m-3osEoJ(tMO9Ce}tfzM7{_z?1CHmi?`^ z&#tav60uvqAF=A4K1j+ZzwYmCY=)CY?|(61^a?dCjD@@NGX`_($smXs&9#(U1ahK`sTr)%jFzJ&D9Gb#>UaXVts%oZ9lbqk;T zG39yjMX5+WHPd(dS}5g)9S+=3Z;21t{b9O^5NJwf$}EpT(&tionelYK%QeoIRhaC| zGmLfy>mq4o!hwz@7er;+YSk~9P_K;EE}Ha@&J!Pp&5U_yVeHLNgG|)E=S7~s%IblX zd&(iR7~lJ{>a^xbt|lx7UONAabH*!rb5)$cNUS;LeCg1rIQ{Q9A+*jzoFl`q4D@|rpEeZ#%jPip^H_07gPrAghMT<0DwbdAkrI_baEz&o*i^HEb7Tf;U-p5>WwZB_s2 zutaSd5-w-Bzoi}Q?E-(8N*JQ<*U?AQ)wm-{E&K0kE6SUTA)A*T528yAL@ZqhXFnf>%%L0EUQ-tdH{gxgJQkTWayYkg(5x_2!gaTQ zw7hCYb_LSJwn@I=j^3RBS>&S+6=Rh%{EV{^ysBkb=^YH0IQX04?#*rP zp7qiB5gAFK7tf`bWbdy1VXGA7gemmfQPxQ@4XnzY^8L+gcwbCI62jun%?UtPqw=~^f`~}FG)J7+PerJn~P-Hvgq#^EjrV? zIL@M{-*at0oIzQL{oZ-U2Pb#>ingQyt(TMU(Wl88nQ3kj5w==A;m@RqbN{r>M zh3eZ}XQ-w%p03Cs$g(8^s->jA^+Uf~dD z1ne7!>qbtZYQ>KmPGa2^X@q@zT!7mac^@K~pPSuPc50Z)kI^{ZyEQkeD6_r?;GE}% zH%LVyWwT2(1S>j--AE@F$sz`75EgeFL_ZF8rwIu>=KWY?X>niSU0U~gwT7}TSzGu2 zb2J}za-&OCXlf`8p1rc=-w^h?a)r*ejOXQ>oUGX9=|->xNql1iwrXrl2Y8nNA@|eP zfdPWfNE*%jXoF8Hq`86angXEs`=g*8mEIZoM$>WP^)?Z7BDY7s2A{6p(av#zO$fa_ zm&ajDQE`@kVo5{$@FjevZHMwsc~A~<2}EJhQBgjiKTr4cDN@Xw5EL5b7eD)}Usfbi zz#Scu8^)dbmxUeO8B@dX_8QY>1e@Q$3YGuQuDMjn`sKlVxsj?er-(O9S^WZ>(h1Iny~kN34~VFoOgsRo9@+HN-B-cY1j*k>iauiy7`OT$ zN6EB#TpZ>%V6FU#iu9+x+geP+*AH*;7a{9ItTKL4J8})g?DZ%=bi!zM*zk zh?d9^p}Ex(Vxn$v#69d<-=#|;6Xy(kwG+4O}C`u~-IX5sj(?9#SCDt#;I_k{) zt2>PPVSH^xZrMpL4cwufOM<^53^QR{4NV9}yu))5 zA@q^31nCYb?zj5maQPSFpemx3As5UDHl&)i>rmCCE-u{*FU^RkR|U;36o%Y_X+?SQ z<7D;mqs~1(IO9#@%m!9M@LNNKA7=guNxAUpqjL3plrFqtitJwok)6Ic7!OIG)GgbP#@^KgziAH+-NY_ATv#ED=0S;`prduy zfTrD38lRrmnMF$eS{z815ci-gF;McqyKv|tjl;Y3_1z>K5aD~|B_?*tG;u^H+^wgi zDF60noFfElET&|kqyGutrwfLX>DtVgjqasysTtV&I6i`x-QGzxJpw2|QX0WmmIw${ zB$avxGN1%u?)} zsL*@RmQL|{P zIYPU|O9)_Ae;jg-r(9e_>E5?>mq^;03-lTPaHfh6ZQsCwN+AG9;0%E@hONI3XcD5^ z5R`Z33mJw9?=7$TW&z3R6pGO?~jF{90j0jS^tw2?lis%Xyz2(Au6oeSWs54 z{4V=?poHL(2!F}&J;VodG{?Z}`S7b2#uxry;(;Rhp3v4c`tPV~BKW^@^)cAbJQ6tf z2T>EKfbzaqJD%ERlLLMn1DqhGPahNDskeb#t2~Il3A`^pQD7fP?_P=lJr@BoXarC$ zEiFB_9EBEXSILh+Rlg&FMq~DE@BMwD_y6BG@J%4dX=`*L$t+C_=zHO|3*uJeaO4LV z_>M2%=nqX6KjF2z874(t?Z<9WywxAFbpCs)yBTwaqTo6lDcc>C zt~Wvigvgj!ShN^lz_Fks={GnlSvLbQ(G$>AmlhR8DlusJ*ecOcoD9Esu}DT$Yw0ub zgjB{BXmNb7aB=%;iCZ(GU&9rBJ`QDLb%ar53F#Gulr;1&kDjFP@f#Dz{AT`9Nf-se zp%c*bHF(N9H-kRgNvQ@O!a4^!#AqH%4S%4=N^ZG1ebNz06It4PDZdpElg6iF^NVSI zX!B8*SHiYVz$ny5aRHGwu729BfCai!Skr*}fBtcrugW`~RgDQKiywiGmt>i7d*h4c z?Uz`toZxkzbwmg;Bi9h|Id4Duw*cs-CX^JC3B=2rf|(_N4VFsViSbcFT_zN?yOTOM z>-R5;KOC<%t94jYW?*8<1hOSrdHFX%ISL6-tckV}brx8CSA`xC+}DS4w5fJ^o{IAo zo%UPX-r0r4_Ahq(^J0$cgNVSi6bH~2>n^Mhx!zj_bQ>2I}k7e?V_CZ4#GhVcFJJ!IV zzI{o0amYQ-=-~Ts+5F5lne1M@WZ_Ji5c!)jgw#9yOD5sfWxIs7IEccWYnXstv zPZ@`UoqYf)Ie8E$U6ugLe?I@2KzBE5hLjcNOq?S^WEkfPV{5f#FAR-REb~Cgs zAhFvdP5~{heBP%(v?-Oyq4$uG&`uM&JcY>1%ZvM*iTC8C5~Lo zAPZ!9la1;3`7><`=OOxu8kl zhk|hQV&$`$^&jIlhWV$y{|{wPn9H1}=9bJf5VhnlHGAKA;Zks+5;G~*TFT3^QFOoB zTr+zI`HM(u{~_Bd13c>X$p?NDx=hB6NHub;fbJ|~ad8y+IF=0k9cgLl%Px|IXL79$ zU>y?B*+{R{J{EMfO?HO?zU%eCLHStKhd*>sgJwZfMkZD>peH3o0Wr-v=keBbkk0^4 zg2yn_*mPNR+Oo7Mu*{^h;F!x^I3NI3mo|!B=cQ&r89o}s8YPCk5@=c2X6#NjNA%KI z!k_SV0R3F$N8$QuEe!@=T*+25P1xbru?jPl;B(N1U5bcG6a4f>=(4*2^75s|4Xb(q z$;X-c0HCHfTIvYjET+A0(^a5)Xuv^a@q3*mJ#&(T0J8a~S8v*a2s8`x?mLa%cef7- zV}*i&!DfI!go(p_TAWZ}WNOI}^&IQG$k4RU*8VL+R2gs{E@uz^S43T^z}+d>QYmq< z-dTZeT^N-?LhT5Td0AD_xJ_8y@i5V*C;7N`U%ngLzFVL+{NR0 zTGl2ks47xGuCmzb&j9Pa3=d3po8GBPbCU)eO{zC=UB-4dhWYpZPK}8=bxup@#>~nn zF)_PvEVq3zK-y>bdSx3+PE~J=?v0hEzpA)b0q5$B>5;O)cIgya7!0HSl|Adq#2{NE z_vAH5ptEDqlnH744ouP!lp#QVv=yFw3>wN9snL?&OeUh-M5?Hg@I`N}6)rq2qi^5% z4oXNh;Mzt)E9Er#UmJGZ&x&ZMsPOnzg+2^^k9#tR$6r&%g$AMeDou1V@rbtT1+|K7(_; zBh-k#nhGpVY2H%J9(AYajM{>bh09wmK!oap|y<^YW}HnSaZkL=uKcB894@8 z9k?mcAQo1()cmsV7ryeX)Lz?V#U8Sk00$+Zx4%En9L$H`cc7tY05K~oenmsPcPj4$8 z%!)nLskW+i8J=;64H*d&O;W$K#Q#fNG(2nIzQY5?aLB+RbO4_(;!(Ctr+qr{YUVY_ z0MF`5NuVy0{eCK0ej~MZCWGRvC8hR3EN0yQ_ohYf~lv zP0rJxbue{+@o5BNgQs<`!)BcS+xa-=buL*Ekn9?s?=M)8I$a){AMec>=>1_=xcqN1 z4#(l;8>?_sStJsuG73O*r2xVmCaacZ0qjiz{e3Th_+P(d|NL#qa=-l_(!yvU*k=Ix z2jtYkPZheh?QGz7WFTkg`m9;ZLdkVa*V)Ji81itx=Hj>0+WgthJC6KWZQ9&7IM2tI z2m-9GG}@yZ*-0PE(o(mjNu|EsD-4cyeI<%7*<03=4awI09Zl3k7=}sri0)@4>X!}E zTl8A@8M*7zlRNt_%qBN2V+!9DotOkjsCVT#O$@Q!J;8hTefWbBrZ|7b+bZJC@2ez2 zS@-3A{AIv_VX+4~i`jI+uIzlGoq3KB7g!p66umzSX9$^Y6ieAH*q5(i>r#8sfZS6g zk8;KYMiHI~u)xlO7@zEVd$=IU@qVOx8Y@!311)(ut0mjY0nY9L11oH!t={g2>34c< znC?Y6t=e}XaAjIr@@pc zSpU0A5wdqc!M^FIezATL!ki`wgp><=-e<_&0KDE4*+AP{5CAk%nE;ZY>l~GA{=YO+ z>$6P_Dtden-p`Rwc=16vkTf)Wn*7o_6wID1lsxTeTp!B$QV^@#e6jjuU#IPO4RoVD zrCE8vfIuLD$n`_eT9b5jc@NE$x_g?}bU zrsz6`PT&pkKQsGHDeD|qGv}qFMa5?;TFe3z`(GLwyIM-XxSYI}5ygiNNa5~mlTI;* zbok?(OU}Y@O#@H$Vrfs)l|cX=8jpt*@^tIUjh|+E0`F+N+HG+8bqt1tS_5#%2aB}0 zOT(&uD}yjINb}qnmD*!pY&c&{Mhhsx6;+db@)dxb7(hB-e}B|Cme(l7+EwE3-gWfj zR2jEpS7lxTjQB>i3p*3UClO5~ndofDuA0bP5EC@^Kk6AW;TF$kppjNm*PZv1p~ zAF$(TAS^7HKu9U`86B_DWgh=D)h`~qT9IKQS6=VF-wGiA5=d&Du`0qzhbDxj0AbEl z#njucU<1?$CM&aq)zwIaUb~r znMdJ5iB64jfA}z@Xc!9c7|JIjkPyraTHl$60k=6{K204TACD8lN_ux#nhSw2Pkc@E zww4eV$E{o!QU?gRP+yv1Z>_uMO^2XK2d;l$pxb~b^tcjW;XKV!>H_t`AB|upjr_h? z2Ew!5EvJLU_9*gK8zU8iRk$Yd2(E`cpjVRbjV6`7%)Zd-``|TDo}G+494^b9+(%V9 zi^JTv`EM+av`(S#L?mV(zaOSsvYKtIyUbDWQmA#_nZy-Z9WIOW6r7-z^-^FdRN!w0 zG8{A>jGhGc!c)-|yo~KF)Ba>quH0p%%w=u&^^72{&11N=E(!EA%SzSBuMpzsuU~`j zcbb|TeS^6{EZ|1VMQTJ__{!=;>V3BdbFcQaN&wY8ABZ@1 z?<=1o6AelNaT1nb3L`SnGi5N}n@YhBGR6zgc{N8Fcn=_I81Z*Q3NvXO5c-us97=Aj z&;lQqz{ifHKS25>N(}DxU$_0QL-_yD66WuamnhMnHwTw5g0X%iIVoky5^+Pn{|gmw B5K#aC literal 0 HcmV?d00001 diff --git a/test/image/mocks/legendrank.json b/test/image/mocks/legendrank.json new file mode 100644 index 00000000000..d8c5880d9bb --- /dev/null +++ b/test/image/mocks/legendrank.json @@ -0,0 +1,51 @@ +{ + "data": [ + {"type": "bar", "name": "100", "y": [100], "yaxis": "y2", "legendgroup": "two"}, + { + "legendrank": 2, + "legendgroup": "pie", + "type": "pie", + "labels": ["z","y","x","x","x","y"], + "sort": false, + "textinfo": "none", + "domain": { + "x": [0.55, 1], + "y": [0.35, 0.65] + } + }, + { + "legendrank": 1, + "legendgroup": "pie", + "type": "pie", + "labels": ["a","b","c","c","c","a"], + "textinfo": "none", + "domain": { + "x": [0, 0.45], + "y": [0.35, 0.65] + } + }, + {"type": "scatter", "name": "2", "y": [2], "yaxis": "y", "legendgroup": "one", "legendrank": 2}, + {"type": "scatter", "name": "1", "y": [1], "yaxis": "y", "legendgroup": "one", "legendrank": 1}, + {"type": "bar", "name": "200", "y": [100], "yaxis": "y2", "legendgroup": "two"}, + {"type": "scatter", "name": "3", "y": [3], "yaxis": "y", "legendgroup": "one", "legendrank": 3}, + {"type": "bar", "name": "300", "y": [100], "yaxis": "y2", "legendgroup": "two"} + ], + "layout": { + "barmode": "stack", + "title": { + "text": "legendrank" + }, + "hovermode": "x unified", + "margin": { + "t": 50 + }, + "width": 300, + "height": 400, + "yaxis2": { + "domain": [0.7, 1] + }, + "yaxis": { + "domain": [0, 0.3] + } + } +} From 4ffb06c5b283b72444ab3613e4bb8b573bfac057 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 16 Apr 2021 17:24:16 -0400 Subject: [PATCH 06/20] describe why dflt 1000 is used - mentioning ranking works within each group --- src/plots/attributes.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 0e79717baf2..8d1b9b705e5 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -46,9 +46,12 @@ module.exports = { dflt: 1000, editType: 'style', description: [ - 'Sets the legend rank for this trace.', + 'Sets the legend rank for this trace in each `legendgroup`.', 'Items with smaller ranks would be presented on top/left side while', - 'with `*reversed* `legend.traceorder` they would be on bottom/right side.' + 'with `*reversed* `legend.traceorder` they would be on bottom/right side.', + 'The default legendrank is 1000,', + 'so that you can use ranks less than 1000 to place certain items before all unranked items,', + 'and ranks greater than 1000 to go after all unranked items.' ].join(' ') }, opacity: { From 6e61bd855131998dc550f0373339f078aebde8b2 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 11:40:49 -0400 Subject: [PATCH 07/20] refactor - less variable and use push --- src/components/legend/get_legend_data.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index bc909f85887..b212f070b9e 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -70,7 +70,6 @@ module.exports = function getLegendData(calcdata, opts) { // rearrange lgroupToTraces into a d3-friendly array of arrays var lgroupsLength = lgroups.length; - var ltraces; var legendData; // sort considering trace.legendrank and legend.traceorder @@ -86,11 +85,11 @@ module.exports = function getLegendData(calcdata, opts) { }; if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { - legendData = new Array(lgroupsLength); - + legendData = []; for(i = 0; i < lgroupsLength; i++) { - ltraces = lgroupToTraces[lgroups[i]]; - legendData[i] = ltraces.sort(orderFn); + legendData.push( + lgroupToTraces[lgroups[i]].sort(orderFn) + ); } } else { // collapse all groups into one if all groups are blank From 737478e6370d9500bf20e8c333a229c7b44f9261 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 11:45:09 -0400 Subject: [PATCH 08/20] refactor - add sort step --- src/components/legend/get_legend_data.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index b212f070b9e..6545bccd0b2 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -88,7 +88,7 @@ module.exports = function getLegendData(calcdata, opts) { legendData = []; for(i = 0; i < lgroupsLength; i++) { legendData.push( - lgroupToTraces[lgroups[i]].sort(orderFn) + lgroupToTraces[lgroups[i]] ); } } else { @@ -99,10 +99,13 @@ module.exports = function getLegendData(calcdata, opts) { lgroupToTraces[lgroups[i]][0] ); } - legendData[0] = legendData[0].sort(orderFn); lgroupsLength = 1; } + for(i = 0; i < lgroupsLength; i++) { + legendData[i] = legendData[i].sort(orderFn); + } + // number of legend groups - needed in legend/draw.js opts._lgroupsLength = lgroupsLength; // maximum name/label length - needed in legend/draw.js From 274db06af1d169895d05b77affbc205889e90066 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 11:51:21 -0400 Subject: [PATCH 09/20] refactor - bring down the sort function --- src/components/legend/get_legend_data.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 6545bccd0b2..7c584fb87c4 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -72,18 +72,6 @@ module.exports = function getLegendData(calcdata, opts) { var lgroupsLength = lgroups.length; var legendData; - // sort considering trace.legendrank and legend.traceorder - var dir = helpers.isReversed(opts) ? -1 : 1; - var orderFn = function(a, b) { - var A = a[0].trace; - var B = b[0].trace; - var delta = A.legendrank - B.legendrank; - if(!delta) delta = A.index - B.index; - if(!delta) delta = a[0]._initID - b[0]._initID; - - return dir * delta; - }; - if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { legendData = []; for(i = 0; i < lgroupsLength; i++) { @@ -102,6 +90,18 @@ module.exports = function getLegendData(calcdata, opts) { lgroupsLength = 1; } + // sort considering trace.legendrank and legend.traceorder + var dir = helpers.isReversed(opts) ? -1 : 1; + var orderFn = function(a, b) { + var A = a[0].trace; + var B = b[0].trace; + var delta = A.legendrank - B.legendrank; + if(!delta) delta = A.index - B.index; + if(!delta) delta = a[0]._initID - b[0]._initID; + + return dir * delta; + }; + for(i = 0; i < lgroupsLength; i++) { legendData[i] = legendData[i].sort(orderFn); } From 915c67a6ccd862ccb87ad60ce25022e094b2a8b5 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 13:34:47 -0400 Subject: [PATCH 10/20] refactor - simplify logic --- src/components/legend/get_legend_data.js | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 7c584fb87c4..5e32df69ad8 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -69,25 +69,18 @@ module.exports = function getLegendData(calcdata, opts) { if(!lgroups.length) return []; // rearrange lgroupToTraces into a d3-friendly array of arrays - var lgroupsLength = lgroups.length; var legendData; - if(hasOneNonBlankGroup && helpers.isGrouped(opts)) { - legendData = []; - for(i = 0; i < lgroupsLength; i++) { - legendData.push( - lgroupToTraces[lgroups[i]] - ); - } - } else { - // collapse all groups into one if all groups are blank - legendData = [[]]; - for(i = 0; i < lgroupsLength; i++) { - legendData[0].push( - lgroupToTraces[lgroups[i]][0] - ); + var shouldCollapse = !hasOneNonBlankGroup || !helpers.isGrouped(opts); + legendData = shouldCollapse ? [[]] : []; + for(i = 0; i < lgroups.length; i++) { + var t = lgroupToTraces[lgroups[i]]; + if(shouldCollapse) { + // collapse all groups into one if all groups are blank + legendData[0].push(t[0]); + } else { + legendData.push(t); } - lgroupsLength = 1; } // sort considering trace.legendrank and legend.traceorder @@ -102,12 +95,12 @@ module.exports = function getLegendData(calcdata, opts) { return dir * delta; }; - for(i = 0; i < lgroupsLength; i++) { - legendData[i] = legendData[i].sort(orderFn); + for(i = 0; i < legendData.length; i++) { + legendData[i].sort(orderFn); } // number of legend groups - needed in legend/draw.js - opts._lgroupsLength = lgroupsLength; + opts._lgroupsLength = legendData.length; // maximum name/label length - needed in legend/draw.js opts._maxNameLength = maxNameLength; From 69c0b29c3442c8e4397f428f89d770224a4ead4b Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 17:39:15 -0400 Subject: [PATCH 11/20] update image test --- test/image/baselines/legendrank.png | Bin 16381 -> 15023 bytes test/image/mocks/legendrank.json | 18 +++++++++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/image/baselines/legendrank.png b/test/image/baselines/legendrank.png index 9a9bbf50eab57654a06f4be022ef75297f75839d..2f5d879126edfdadaff50a5f98b3fdc2b223aacd 100644 GIT binary patch literal 15023 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 literal 16381 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 B5K#aC diff --git a/test/image/mocks/legendrank.json b/test/image/mocks/legendrank.json index d8c5880d9bb..182b9c7afae 100644 --- a/test/image/mocks/legendrank.json +++ b/test/image/mocks/legendrank.json @@ -1,15 +1,14 @@ { "data": [ - {"type": "bar", "name": "100", "y": [100], "yaxis": "y2", "legendgroup": "two"}, + {"type": "bar", "name": "1", "y": [1], "yaxis": "y2", "legendgroup": "two", "legendrank": 3}, { "legendrank": 2, "legendgroup": "pie", "type": "pie", - "labels": ["z","y","x","x","x","y"], - "sort": false, + "labels": ["a","b","c","c","c","a"], "textinfo": "none", "domain": { - "x": [0.55, 1], + "x": [0, 0.45], "y": [0.35, 0.65] } }, @@ -17,21 +16,21 @@ "legendrank": 1, "legendgroup": "pie", "type": "pie", - "labels": ["a","b","c","c","c","a"], + "labels": ["z","x","x","x","y", "y"], + "sort": false, "textinfo": "none", "domain": { - "x": [0, 0.45], + "x": [0.55, 1], "y": [0.35, 0.65] } }, {"type": "scatter", "name": "2", "y": [2], "yaxis": "y", "legendgroup": "one", "legendrank": 2}, {"type": "scatter", "name": "1", "y": [1], "yaxis": "y", "legendgroup": "one", "legendrank": 1}, - {"type": "bar", "name": "200", "y": [100], "yaxis": "y2", "legendgroup": "two"}, + {"type": "bar", "name": "2", "y": [2], "yaxis": "y2", "legendgroup": "two", "legendrank": 2}, {"type": "scatter", "name": "3", "y": [3], "yaxis": "y", "legendgroup": "one", "legendrank": 3}, - {"type": "bar", "name": "300", "y": [100], "yaxis": "y2", "legendgroup": "two"} + {"type": "bar", "name": "3", "y": [3], "yaxis": "y2", "legendgroup": "two", "legendrank": 1} ], "layout": { - "barmode": "stack", "title": { "text": "legendrank" }, @@ -45,6 +44,7 @@ "domain": [0.7, 1] }, "yaxis": { + "autorange": "reversed", "domain": [0, 0.3] } } From e1e9a744c2a861d359bf0d0a5d1b5c7c584d797c Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 18:14:18 -0400 Subject: [PATCH 12/20] simplify and fixup legend sort --- src/components/legend/get_legend_data.js | 38 +++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 5e32df69ad8..eebd90d7c02 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -20,14 +20,14 @@ module.exports = function getLegendData(calcdata, opts) { // TODO: check this against fullData legendgroups? var uniqueGroup = '~~i' + lgroupi; lgroups.push(uniqueGroup); - lgroupToTraces[uniqueGroup] = [[legendItem]]; + lgroupToTraces[uniqueGroup] = [legendItem]; lgroupi++; } else if(lgroups.indexOf(legendGroup) === -1) { lgroups.push(legendGroup); hasOneNonBlankGroup = true; - lgroupToTraces[legendGroup] = [[legendItem]]; + lgroupToTraces[legendGroup] = [legendItem]; } else { - lgroupToTraces[legendGroup].push([legendItem]); + lgroupToTraces[legendGroup].push(legendItem); } } @@ -68,36 +68,46 @@ module.exports = function getLegendData(calcdata, opts) { // won't draw a legend in this case if(!lgroups.length) return []; + // collapse all groups into one if all groups are blank + var shouldCollapse = !hasOneNonBlankGroup || !helpers.isGrouped(opts); + // rearrange lgroupToTraces into a d3-friendly array of arrays var legendData; - var shouldCollapse = !hasOneNonBlankGroup || !helpers.isGrouped(opts); - legendData = shouldCollapse ? [[]] : []; + legendData = []; for(i = 0; i < lgroups.length; i++) { var t = lgroupToTraces[lgroups[i]]; if(shouldCollapse) { - // collapse all groups into one if all groups are blank - legendData[0].push(t[0]); + legendData.push(t[0]); } else { legendData.push(t); } } + if(shouldCollapse) legendData = [legendData]; // sort considering trace.legendrank and legend.traceorder - var dir = helpers.isReversed(opts) ? -1 : 1; var orderFn = function(a, b) { - var A = a[0].trace; - var B = b[0].trace; + var A = a.trace; + var B = b.trace; var delta = A.legendrank - B.legendrank; - if(!delta) delta = A.index - B.index; - if(!delta) delta = a[0]._initID - b[0]._initID; + if(!delta) delta = A._initID - B._initID; - return dir * delta; + return delta; }; - + var rev = helpers.isReversed(opts); for(i = 0; i < legendData.length; i++) { legendData[i].sort(orderFn); + if(rev) legendData[i].reverse(); + } + + var arr = []; + for(i = 0; i < legendData.length; i++) { + arr[i] = []; + for(j = 0; j < legendData[i].length; j++) { + arr[i][j] = [legendData[i][j]]; + } } + legendData = arr; // number of legend groups - needed in legend/draw.js opts._lgroupsLength = legendData.length; From f232f42cfbf2a01357ed6f8defb24a276a0a7ef1 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 19:02:33 -0400 Subject: [PATCH 13/20] no more need for _initID now that sort and collapse are fixed --- src/components/legend/get_legend_data.js | 10 +---- test/jasmine/tests/legend_test.js | 48 ++++++++++++------------ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index eebd90d7c02..344d5463b92 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -11,10 +11,7 @@ module.exports = function getLegendData(calcdata, opts) { var lgroupi = 0; var maxNameLength = 0; var i, j; - var initID = 0; function addOneItem(legendGroup, legendItem) { - legendItem._initID = initID++; - // each '' legend group is treated as a separate group if(legendGroup === '' || !helpers.isGrouped(opts)) { // TODO: check this against fullData legendgroups? @@ -87,12 +84,7 @@ module.exports = function getLegendData(calcdata, opts) { // sort considering trace.legendrank and legend.traceorder var orderFn = function(a, b) { - var A = a.trace; - var B = b.trace; - var delta = A.legendrank - B.legendrank; - if(!delta) delta = A._initID - B._initID; - - return delta; + return a.trace.legendrank - b.trace.legendrank; }; var rev = helpers.isReversed(opts); for(i = 0; i < legendData.length; i++) { diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index b080de5f936..f7db586dbed 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -285,14 +285,14 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_initID: 2, trace: { + [{trace: { legendrank: 1, type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{_initID: 0, trace: { + [{trace: { legendrank: 3, type: 'scatter', visible: true, @@ -301,7 +301,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{_initID: 1, trace: { + [{trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -347,21 +347,21 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_initID: 2, trace: { + [{trace: { legendrank: 1, type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{_initID: 1, trace: { + [{trace: { legendrank: 2, type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{_initID: 0, trace: { + [{trace: { legendrank: 3, type: 'scatter', visible: true, @@ -439,21 +439,21 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_initID: 0, trace: { + [{trace: { legendrank: 3, type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{_initID: 1, trace: { + [{trace: { legendrank: 2, type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{_initID: 2, trace: { + [{trace: { legendrank: 1, type: 'box', visible: true, @@ -499,14 +499,14 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_initID: 0, trace: { + [{trace: { legendrank: 3, type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{_initID: 2, trace: { + [{trace: { legendrank: 1, type: 'box', visible: true, @@ -515,7 +515,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{_initID: 1, trace: { + [{trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -564,13 +564,13 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_initID: 0, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{_initID: 2, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -578,7 +578,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{_initID: 1, trace: { + [{trace: { type: 'bar', visible: 'legendonly', legendgroup: '', @@ -620,19 +620,19 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_initID: 0, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{_initID: 1, trace: { + [{trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{_initID: 2, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: '', @@ -703,19 +703,19 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_initID: 2, trace: { + [{trace: { type: 'box', visible: true, legendgroup: '', showlegend: true }}], - [{_initID: 1, trace: { + [{trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{_initID: 0, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: '', @@ -757,13 +757,13 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_initID: 2, trace: { + [{trace: { type: 'box', visible: true, legendgroup: 'group', showlegend: true }}], - [{_initID: 0, trace: { + [{trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -771,7 +771,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{_initID: 1, trace: { + [{trace: { type: 'bar', visible: 'legendonly', legendgroup: '', From c00d56e2faed9b8c85aac2ddfe542dc6f6b4fd5e Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 19:29:33 -0400 Subject: [PATCH 14/20] more refactor --- src/components/legend/get_legend_data.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 344d5463b92..4d4ea2e26a3 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -4,6 +4,9 @@ var Registry = require('../../registry'); var helpers = require('./helpers'); module.exports = function getLegendData(calcdata, opts) { + var grouped = helpers.isGrouped(opts); + var reversed = helpers.isReversed(opts); + var lgroupToTraces = {}; var lgroups = []; var hasOneNonBlankGroup = false; @@ -11,6 +14,7 @@ module.exports = function getLegendData(calcdata, opts) { var lgroupi = 0; var maxNameLength = 0; var i, j; + function addOneItem(legendGroup, legendItem) { // each '' legend group is treated as a separate group if(legendGroup === '' || !helpers.isGrouped(opts)) { @@ -66,7 +70,7 @@ module.exports = function getLegendData(calcdata, opts) { if(!lgroups.length) return []; // collapse all groups into one if all groups are blank - var shouldCollapse = !hasOneNonBlankGroup || !helpers.isGrouped(opts); + var shouldCollapse = !hasOneNonBlankGroup || !grouped; // rearrange lgroupToTraces into a d3-friendly array of arrays var legendData; @@ -86,20 +90,16 @@ module.exports = function getLegendData(calcdata, opts) { var orderFn = function(a, b) { return a.trace.legendrank - b.trace.legendrank; }; - var rev = helpers.isReversed(opts); for(i = 0; i < legendData.length; i++) { legendData[i].sort(orderFn); - if(rev) legendData[i].reverse(); + if(reversed) legendData[i].reverse(); } - var arr = []; for(i = 0; i < legendData.length; i++) { - arr[i] = []; for(j = 0; j < legendData[i].length; j++) { - arr[i][j] = [legendData[i][j]]; + legendData[i][j] = [legendData[i][j]]; } } - legendData = arr; // number of legend groups - needed in legend/draw.js opts._lgroupsLength = legendData.length; From 69bf7ebe7ae74588493ee658d9d617a14a271117 Mon Sep 17 00:00:00 2001 From: archmoj Date: Mon, 19 Apr 2021 19:33:29 -0400 Subject: [PATCH 15/20] remove extra loop - update comments --- src/components/legend/get_legend_data.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 4d4ea2e26a3..ae5006a0f05 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -86,18 +86,19 @@ module.exports = function getLegendData(calcdata, opts) { } if(shouldCollapse) legendData = [legendData]; - // sort considering trace.legendrank and legend.traceorder var orderFn = function(a, b) { return a.trace.legendrank - b.trace.legendrank; }; for(i = 0; i < legendData.length; i++) { + // sort considering trace.legendrank and legend.traceorder legendData[i].sort(orderFn); if(reversed) legendData[i].reverse(); - } - for(i = 0; i < legendData.length; i++) { + // add extra dim for(j = 0; j < legendData[i].length; j++) { - legendData[i][j] = [legendData[i][j]]; + legendData[i][j] = [ + legendData[i][j] + ]; } } From d87ee126e88997e4e0cc44d62dc1bf9a646eebe8 Mon Sep 17 00:00:00 2001 From: archmoj Date: Thu, 22 Apr 2021 13:23:15 -0400 Subject: [PATCH 16/20] update legendrank description --- src/plots/attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/attributes.js b/src/plots/attributes.js index 8d1b9b705e5..d45d98f990d 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -46,8 +46,8 @@ module.exports = { dflt: 1000, editType: 'style', description: [ - 'Sets the legend rank for this trace in each `legendgroup`.', - 'Items with smaller ranks would be presented on top/left side while', + 'Sets the legend rank for this trace.', + 'Items and groups with smaller ranks would be presented on top/left side while', 'with `*reversed* `legend.traceorder` they would be on bottom/right side.', 'The default legendrank is 1000,', 'so that you can use ranks less than 1000 to place certain items before all unranked items,', From d35ff08123ad49388ec565fba174a83f9f935563 Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 15 Jun 2021 15:37:13 -0400 Subject: [PATCH 17/20] add fallback to have stable sort in Chrome < 70 --- src/components/legend/get_legend_data.js | 6 ++- test/jasmine/tests/legend_test.js | 48 ++++++++++++------------ 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index ae5006a0f05..958037e485d 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -87,10 +87,14 @@ module.exports = function getLegendData(calcdata, opts) { if(shouldCollapse) legendData = [legendData]; var orderFn = function(a, b) { - return a.trace.legendrank - b.trace.legendrank; + return ( + (a.trace.legendrank - b.trace.legendrank) || + (a._preSort - b._preSort) // fallback for old Chrome < 70 https://bugs.chromium.org/p/v8/issues/detail?id=90 + ); }; for(i = 0; i < legendData.length; i++) { // sort considering trace.legendrank and legend.traceorder + legendData[i].forEach(function(a, i) { a._preSort = i; }); legendData[i].sort(orderFn); if(reversed) legendData[i].reverse(); diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index f7db586dbed..23cd6d0f7a5 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -285,14 +285,14 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 1, trace: { legendrank: 1, type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -301,7 +301,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{trace: { + [{_preSort: 0, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -347,21 +347,21 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 2, trace: { legendrank: 1, type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -439,21 +439,21 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 2, trace: { legendrank: 1, type: 'box', visible: true, @@ -499,14 +499,14 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { legendrank: 1, type: 'box', visible: true, @@ -515,7 +515,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{trace: { + [{_preSort: 0, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -564,13 +564,13 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -578,7 +578,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{trace: { + [{_preSort: 0, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', @@ -620,19 +620,19 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 2, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -703,19 +703,19 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 2, trace: { type: 'box', visible: true, legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 1, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', showlegend: true }}], - [{trace: { + [{_preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -757,13 +757,13 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{trace: { + [{_preSort: 1, trace: { type: 'box', visible: true, legendgroup: 'group', showlegend: true }}], - [{trace: { + [{_preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -771,7 +771,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{trace: { + [{_preSort: 0, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', From a1f87b9dbe93a83e3f91da4f3599b22e9a4c166e Mon Sep 17 00:00:00 2001 From: archmoj Date: Tue, 15 Jun 2021 18:26:28 -0400 Subject: [PATCH 18/20] would be > are --- src/plots/attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plots/attributes.js b/src/plots/attributes.js index d45d98f990d..c1a9aa456a9 100644 --- a/src/plots/attributes.js +++ b/src/plots/attributes.js @@ -47,8 +47,8 @@ module.exports = { editType: 'style', description: [ 'Sets the legend rank for this trace.', - 'Items and groups with smaller ranks would be presented on top/left side while', - 'with `*reversed* `legend.traceorder` they would be on bottom/right side.', + 'Items and groups with smaller ranks are presented on top/left side while', + 'with `*reversed* `legend.traceorder` they are on bottom/right side.', 'The default legendrank is 1000,', 'so that you can use ranks less than 1000 to place certain items before all unranked items,', 'and ranks greater than 1000 to go after all unranked items.' From 32a890c9bf513155820857ff840a229051ac2588 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 16 Jun 2021 08:52:24 -0400 Subject: [PATCH 19/20] rank groups based on minimum legendranks within each group --- src/components/legend/get_legend_data.js | 37 +++++++++++++++++++----- test/jasmine/tests/legend_test.js | 24 +++++++-------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/components/legend/get_legend_data.js b/src/components/legend/get_legend_data.js index 958037e485d..f5a7b19fbbb 100644 --- a/src/components/legend/get_legend_data.js +++ b/src/components/legend/get_legend_data.js @@ -72,10 +72,7 @@ module.exports = function getLegendData(calcdata, opts) { // collapse all groups into one if all groups are blank var shouldCollapse = !hasOneNonBlankGroup || !grouped; - // rearrange lgroupToTraces into a d3-friendly array of arrays - var legendData; - - legendData = []; + var legendData = []; for(i = 0; i < lgroups.length; i++) { var t = lgroupToTraces[lgroups[i]]; if(shouldCollapse) { @@ -86,19 +83,43 @@ module.exports = function getLegendData(calcdata, opts) { } if(shouldCollapse) legendData = [legendData]; - var orderFn = function(a, b) { + for(i = 0; i < legendData.length; i++) { + // find minimum rank within group + var groupMinRank = Infinity; + for(j = 0; j < legendData[i].length; j++) { + var rank = legendData[i][j].trace.legendrank; + if(groupMinRank > rank) groupMinRank = rank; + } + + // record on first group element + legendData[i][0]._groupMinRank = groupMinRank; + legendData[i][0]._preGroupSort = i; + } + + var orderFn1 = function(a, b) { + return ( + (a[0]._groupMinRank - b[0]._groupMinRank) || + (a[0]._preGroupSort - b[0]._preGroupSort) // fallback for old Chrome < 70 https://bugs.chromium.org/p/v8/issues/detail?id=90 + ); + }; + + var orderFn2 = function(a, b) { return ( (a.trace.legendrank - b.trace.legendrank) || (a._preSort - b._preSort) // fallback for old Chrome < 70 https://bugs.chromium.org/p/v8/issues/detail?id=90 ); }; + + // sort considering minimum group legendrank + legendData.forEach(function(a, k) { a[0]._preGroupSort = k; }); + legendData.sort(orderFn1); for(i = 0; i < legendData.length; i++) { // sort considering trace.legendrank and legend.traceorder - legendData[i].forEach(function(a, i) { a._preSort = i; }); - legendData[i].sort(orderFn); + legendData[i].forEach(function(a, k) { a._preSort = k; }); + legendData[i].sort(orderFn2); if(reversed) legendData[i].reverse(); - // add extra dim + // rearrange lgroupToTraces into a d3-friendly array of arrays for(j = 0; j < legendData[i].length; j++) { legendData[i][j] = [ legendData[i][j] diff --git a/test/jasmine/tests/legend_test.js b/test/jasmine/tests/legend_test.js index 23cd6d0f7a5..054d2e36b07 100644 --- a/test/jasmine/tests/legend_test.js +++ b/test/jasmine/tests/legend_test.js @@ -292,7 +292,7 @@ describe('legend getLegendData user-defined legendrank', function() { legendgroup: 'group', showlegend: true }}], - [{_preSort: 0, trace: { + [{_groupMinRank: 1, _preGroupSort: 0, _preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -301,7 +301,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{_preSort: 0, trace: { + [{_groupMinRank: 2, _preGroupSort: 1, _preSort: 0, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -361,7 +361,7 @@ describe('legend getLegendData user-defined legendrank', function() { legendgroup: '', showlegend: true }}], - [{_preSort: 0, trace: { + [{_groupMinRank: 1, _preGroupSort: 0, _preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -439,7 +439,7 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_preSort: 0, trace: { + [{_groupMinRank: 1, _preGroupSort: 0, _preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -499,7 +499,7 @@ describe('legend getLegendData user-defined legendrank', function() { expected = [ [ - [{_preSort: 0, trace: { + [{_groupMinRank: 1, _preGroupSort: 0, _preSort: 0, trace: { legendrank: 3, type: 'scatter', visible: true, @@ -515,7 +515,7 @@ describe('legend getLegendData user-defined legendrank', function() { }}] ], [ - [{_preSort: 0, trace: { + [{_groupMinRank: 2, _preGroupSort: 1, _preSort: 0, trace: { legendrank: 2, type: 'bar', visible: 'legendonly', @@ -564,7 +564,7 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 0, _preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -578,7 +578,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 1, _preSort: 0, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', @@ -620,7 +620,7 @@ describe('legend getLegendData default legendrank', function() { expected = [ [ - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 0, _preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -715,7 +715,7 @@ describe('legend getLegendData default legendrank', function() { legendgroup: '', showlegend: true }}], - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 0, _preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: '', @@ -763,7 +763,7 @@ describe('legend getLegendData default legendrank', function() { legendgroup: 'group', showlegend: true }}], - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 0, _preSort: 0, trace: { type: 'scatter', visible: true, legendgroup: 'group', @@ -771,7 +771,7 @@ describe('legend getLegendData default legendrank', function() { }}] ], [ - [{_preSort: 0, trace: { + [{_groupMinRank: Infinity, _preGroupSort: 1, _preSort: 0, trace: { type: 'bar', visible: 'legendonly', legendgroup: '', From a1abb19809e6c0129aeae3f89687103670ff1466 Mon Sep 17 00:00:00 2001 From: archmoj Date: Wed, 16 Jun 2021 11:23:53 -0400 Subject: [PATCH 20/20] add legendrank2 mock --- test/image/baselines/legendrank2.png | Bin 0 -> 10898 bytes test/image/mocks/legendrank2.json | 44 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 test/image/baselines/legendrank2.png create mode 100644 test/image/mocks/legendrank2.json diff --git a/test/image/baselines/legendrank2.png b/test/image/baselines/legendrank2.png new file mode 100644 index 0000000000000000000000000000000000000000..35261255629d82e2327bdc95bd4535d1df42b47f GIT binary patch literal 10898 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 of the group" + }, + "width": 300, + "height": 300, + "margin": { + "b": 25 + }, + "hovermode": "x unified" + } +}