From e5a206d13c1b1bdfa2d42b6f9c11652040de5971 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 21 Apr 2020 11:16:22 -0500 Subject: [PATCH] feat: allow colorVariant option for series specific color styles (#630) - allows the use of `ColorVariant.Series` to set a series color style to the computed series color - allows the use of `ColorVariant.None` to set color to transparent. --- ...-variant-visually-looks-correct-1-snap.png | Bin 0 -> 33477 bytes ...-variant-visually-looks-correct-1-snap.png | Bin 0 -> 18595 bytes ...-variant-visually-looks-correct-1-snap.png | Bin 0 -> 37078 bytes .../layout/utils/__mocks__/d3_utils.ts | 25 ++ .../layout/utils/d3_utils.test.ts | 219 ++++++++++++++++++ .../partition_chart/layout/utils/d3_utils.ts | 72 +++++- .../renderer/canvas/primitives/rect.ts | 13 +- .../renderer/canvas/styles/area.test.ts | 87 +++++++ .../xy_chart/renderer/canvas/styles/area.ts | 7 +- .../renderer/canvas/styles/bar.test.ts | 152 ++++++++++++ .../xy_chart/renderer/canvas/styles/bar.ts | 12 +- .../renderer/canvas/styles/line.test.ts | 95 ++++++++ .../xy_chart/renderer/canvas/styles/line.ts | 7 +- .../renderer/canvas/styles/point.test.ts | 179 ++++++++++++++ .../xy_chart/renderer/canvas/styles/point.ts | 12 +- src/mocks/index.ts | 1 + src/mocks/theme.ts | 116 ++++++++++ src/utils/__mocks__/commons.ts | 32 +++ src/utils/commons.test.ts | 22 ++ src/utils/commons.ts | 34 ++- src/utils/themes/theme.ts | 32 +-- .../stylings/17_bar_series_color_variant.tsx | 87 +++++++ .../stylings/18_line_series_color_variant.tsx | 61 +++++ .../stylings/19_area_series_color_variant.tsx | 69 ++++++ stories/stylings/stylings.stories.tsx | 3 + tsconfig.lib.json | 2 +- 26 files changed, 1295 insertions(+), 44 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-area-series-color-variant-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-bar-series-color-variant-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-line-series-color-variant-visually-looks-correct-1-snap.png create mode 100644 src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts create mode 100644 src/chart_types/partition_chart/layout/utils/d3_utils.test.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/styles/area.test.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/styles/bar.test.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/styles/line.test.ts create mode 100644 src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts create mode 100644 src/mocks/theme.ts create mode 100644 src/utils/__mocks__/commons.ts create mode 100644 stories/stylings/17_bar_series_color_variant.tsx create mode 100644 stories/stylings/18_line_series_color_variant.tsx create mode 100644 stories/stylings/19_area_series_color_variant.tsx diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-area-series-color-variant-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-area-series-color-variant-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..5853bba4e2cf87f964810b516ae2deab78332ed6 GIT binary patch literal 33477 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

1S#3!Ze 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 B$0Yy& literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-bar-series-color-variant-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-bar-series-color-variant-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8f66e0bfe6212e9feff348cb05c09cfcb4f55c GIT binary patch literal 18595 zcmb`v2Ut{D)&+{%Y8${dfq;O4pdg?`$tJ)81tn)xat6tvs!&u~5zrzDNCpW7lt`vX zih_XTP^2P=#3E;k^!BB@|L&PLGp}d9=f?+Sg?mrkbI#stueJ7len&~3c0bd88X6i} z)Xf_zG&DQMXlQnr|GWpD880`Xz@HtCD)QH8GV4#mE2n5sH?FF=y`3O?>IIwhR?SY^ zGl^Z^#eIt1^vCH}*Jf`-`pJKO)%<~XG&YyTi@SSow~b)F!1BGxb`_~e7LwAtsGMD$ zR~^+86N8#;cP8yT!@>QaK4sVCa!IUK_Tg9`+KXpnmMLE-U-XuRNgKo+vy>rca{k8w z`^!JUNXm^?oSb*S1Dc4liM!wr>$6vM@W<`MHF5aUfB$C~B8|82>HqZNEMF0ER9cdiU0Hc}dD|$2vdqd#Ia+3sTNV};yuE!L9VsC^nrX$wdXFAG zs;Q|_xpCtO`*|4|!$?uP(2w`-cz6^YIdVjNvIsfz(o9OY6Bdh2N=h1ApKq{hpR&K4 zW7)>7?>-b#G?~|(gI;q{S5wPz!`{Ci$Hc@Wgx&N?diRc7R8$kErmC9o`0-8?Gc&9D zw_-JQbs=Qi>0qm!aNMrigliswtWrYv%4t?tg=6{&X=y6=?!{7%H70$|NdP~T|J3P^*GMJz;pWaRk?(_rrFw;E?w%%GLlc$I|>JFG+;g6 z-2EU+%UC2bicn{oovvZ!hta^Ra|WSs#urZ*3xpfz>O|%$s+gOb}2icu^;|#}+wPrfNqfubAts@(L-WKVw^VOUDa zxlU^lb#?XBtgO3FpFU+0cS>PTjgSAtSUxy7m}^o?k5WOQp2x?>8w)WVXJaG8$De5< zktqEiebUm?TgxBq(wD~C+t2Ki*|~FP4|V{?KN>1&$yt(`idrVS#!R>nVFuLTl35W- zG+w`c9UN}iFCac+5ex<+v~GGoT}Ij)aHbWE~4wWfBK4`%i3zT!Ij zy%wM2vFORo%R^c?(Cl2bu1gk}G798xYiZ!KYbv#!WERN(aB}U}x_4z4Fjpey4j2n8 zw&s%bQo|VxM&!Q6P8>CWE{kKP>bM7BJo=W9z*QI6@Ev!8@Vo>?ty7SY53UcwEM?BXyeA6Ew7JE&-)v{UnL$&1`z!;_C_W{*8UCZdwNz z$as0SRf*FqG)n4I(ZYMZh5LfXp52}nvRX6m%dc4{Q9LY~W%+h5R>gS9;rk5E;@i^R zV3No3=ZCq^pU)!aN^X5p>?+E=fhF3#&Ys-dyQwT)Dovd%E{wQ*dPob+hE1z{R_PMh zM{j6oxJspp8dAiS|;8*HtpH0sT0s_}egHR?W68n(gWM5ac=92H8{)4!| z=l@#71QyZ$AOazfFXHv-gr<}i`h7XXP-(OWkHw0Z&N9Itmsqzu=A2M#dO`M{e9!ct zT#rSj+tES>j~+?sNEuk+Lt59h#ZRC9_yOH#psS1T?;mdH6m#j3y9sNeykTTK>Siy- zEW=cPLngxIV^YyQxzIHHGNu2jq8|otP}`RB<2h4^3_S2r-3YMGqzE$`Jo*P|TDlfmnBG$Yf7hV;xH;a=6 zzFj8c(Zl3nYY#82n4_gw%^^{bQzL91zl7G-*2E$H(zNrbN0n2(`JVeH zz_T;bxw*~}4>!NO7-vq!O0JVK`Kl(j6ZDKYtlr?V*{Gwg3n~{}k%TUJaQoWKTzujL z|0}Qfr5Snl&~;=P|G!>zt3x--tj}CWFEq(pT3R-bHYEt#_7u>gnV6Y#9}X#mgoW{3 zym&`jTl>YYzd{Tj8L^rlt`j(MP(fK)^V+p*W`;;o?LR_zgX+sR3zUXK)kR5Y!h2fU zGjwy!8lvep_8&OV*4P-kIzPfAEUaEwScprEh+sN??ASR830=qGZ+C5NGbda;%?uHs z1c&;0Y;9>$wG6WX$p}uE+|i+R{P^+QQO&=XlK*lX^x9HerasZ>$JfIwQXXpE`F5m_ zK6`Z+vUM^9!f)Z&eLkKFs%FQxb*1a$u3f)wlB*+;fq_B9N))nwsWjEmo%w_*OXdkhm;=OX8YOoKQ@{mcerVOdKeW`8v@yye5Gj{mnG{0=-{+ zLc);!<+p;P00Zd#zH~M`$TDw?y~-CuEGp_s!d$(2l`n7~oG#}JPbl$b^P^pR*-=E- z>2|nsA;H1ysI-(6`#m!&D{eNOA7pWADk|}i$Qk|RzEoA=Q&Mg&E-s3@&BSu^@Ce)Y zUkPhqVP(a93heK{Cu(>Jj;i#GY)e9tiy&X=HKVHA>g^#UiMssh8XBz|p{fE^Y^>^Neg(ZLfe1n6N6EYGCV4F^{Th`;pkAp)(I8ftc zVm>Y?C?qK8ETAkCW8<%VePulSNMz;Npwx1fyidJqi0)3jp4*)ANV@1!opQonkNTeJ zyF~pxj5#a(u6R4+CnCe>*j21nVwk36#+x@SnD!3zr?t_gJ~JNiGy-6d(2|)C^n68{ z%3JfVHP&QS8WY54$X81iEB(5(vlO=^>`EUP?KNL{@3=5I#Z7i!j>$D2U}2K7e6qPz zUZGPkY9K}Z+E>DrXo%fd%*bnq5UyPABpeAEHj&}ePPg#WzZ;&CzSSXW?%O(zEJ=mgM+1eq(hWjWja%v zWZC!Z*`5q-qQM~!bvuc zVwAiCNty%6)WXJ%*g#o1xv$m&sp-~SzlH79^qkZ>Bg1LiwT{4~+u@15C7*s!C<%V1 z#$U~a(jJgRCo_|jV|o>sFahlwRtfADjvu-9(+cxUrGKoWvPfBy*rupKe5L2En;pDd zLLRa3Pc>7{&{_%(m0mn#V&IXrht?8JFv`f#P??YPd-CL`+9kB7jZMa5pU6J99~}?Qc@<>jSjc<^c_;J7T+=5Ev#IvC)MOl~Q* z5T{vmE$5~@r+GHkTTQE&nV6ia*yzs-38|Guxhk&ljb1*u#|NNn($!rJtrMML6vz`A z`uh5Qm`7J1;n*YVhOH&MR(^e4YIo@1!PeJj^Gv73TqM7Gwj)JWtd+lHOLAT3Lbj5` zY|nINjorePdYOgR!9!O4OrdUh z>=xZiDqkw;Ua`ZkMza|7)I^!9VJ^%u?3Ih#If${0kF)|98(ph29KE%?@Hmx07S;^T( zVpBItuu^;&V7Bq>Huw5>nGaeSX&vgRU$Av|x87i0ynOkJ9i5kShmh`d9rpu5f6n=% zi>=KgCb_K5VY+^~wq!Y&h9LH&B&9~k>Fw{<>ppZ^F0LF^wrc%4@Z#mqX1_tFaHqUL z)=g*c&>GMS=9wkWFEB3YKA>_CWi0m>VDB@|r%S z`lj7PuW71_rd)q%q7$#k7nvbEoi3jA@ILj)&to^|5h2+O)Jf?L6>cK;1*S0&RWyAqNEwtbRWtqc=Lb70Q_ruV7;h_ zMiYS))R~Gy9H@bTfue~IwUEvu7cH!+o$oY=RylrO;KPhDtH)?MI%V&(dEpW%iW zKuu~x!@~+VfG}!z?!-<^Sl5LM$^+E+&^;N2W<(C9SbQc?;v^kP1wH~-$vD5;L?B#e zfrp#B`Pai|*-;J-4rxFIpwT5S{QN>*y*ic9bS}Fy{mq*blO0(Jsssc?spwE|g@lH> zIy161K+67U_il^v)>LYD-{w1SZ<;6!bxeHZt)mWtFY0G(|ET)`R+s=Uu<=)f;gqQ? z-5HOSF1f{FXrl`8UZZsGZFgm6s#xsahpbJ$kutczkMgOhM;Cl0Fx{4YkeOlS?Xaog ze^uS8N-}UvGBxmun_sCcKD5wS5t!<+g`s5&O?m3NVK6X&EFG-uJYLa?^WwQX42DjREG_9q7ezIQHhR7lak|*$y|j{o8vK%5 ziAZdC@uEiLf6l%}g^KR(g>abD*_{^vD$ufss{#7srH0p;YG0s7Rh4?nd`pkK#c4)l z*S-*{5muY?Wx&2*0j9^EJG*$cu!fK7OJ}#P;J-KU_5;@5m5@B;6+ApqGvpw>I^n4A zR2>o_WKuL$!atI@w4eSsHtGC`VLI?sX5CcUNT<~hk|M5N=~q%~4r;txG@fk4ri{Tn zx-`Tbw-*=_@Nsf4dFk>bkc)tPqqe5#S(Buu(<_etu`0pmXJ=T$pWzo@aEMJoqfv<(s?J~M*!k6u3xi&R8NtoO5<|W)fH_D|Gz##W;K=c{#c9;`#a(9EajU z`L08wP)3MP7tgb!+}+)~3g0ZaFD8`sVl0tZ$~<*7!_Lfm0H?#ydf;42IVf1NzPX*SE-XWt~Jdym?|CJj6|a~ z9_%kij$G(op3Ke7-A_knxfE?wWz(C*sgJ7+WFyQDR^^+=2wR2DI|5T;VrrU4-3$&s zmXw(2q=kl&k+nTTH|69uUH2*UrQB4X?(2(hsE;Bp;J@Lc2r?TfCa+|+*43oYoz|V! zxjOl_3C}p96M%%s&z@Vl#N^K;IeIJUw%AhOBSU=w{RZiLJwuFkde(?=A3x)XW9P;? zHY)+ZqnZaQuc5Cr4FJz-S2|4aV$u<#V2(!YW` z2EV#pgT(_U$(p==ZD}1HJGOH}0p}S{g~+P-sqlfh%lsFxvh;rH^mz3KNunMECZ(cx z7GtUl>mR~$5^`=Gt=wwv?1kEuN=>jdH-A+za0k6{H(&DGL)Zdo>%+PkYa}^gA)(P+ zQ4M@%sexGx3qOi#tr%yZ?QV@Ojfsgt>UCAcn6NgFn})W$?M2SaA(wb>fbFkd->O2bpS)y_zshWDN8&xf zb(<*}Pbfh#7(NWyL-(?60{;O9y>GB8&9cG3pKV!j_nsyDJnWKoTxCFH8c=e6F6x+s ztigBV)U9givHA5_SHeZ9nS2=va0M+ZC`OM#|F#3rF47m8~{vB&DZK8df#-Z?XB<`6|_?2{Q84C8js+qI20 zoh>RlmVbX6<``H3io$)2eQnc&@6mOyBO?&Nn=R6DP%>EuGj|`LH~;u}chO*g#IYks;^VIS=8bA< zX{A7AR2L&-n3|f3F#YUpt*t4su~#}fJ0k?m0vw@8&L+BAz^*Y&?1w> zl$ZX+vx>?x#Gc8Kf@Q)X1n2|pR8S`^Z!N-GFdvA#CCSh}Rih-ddjiR`LWAFVoD38q zhBt?op-kYfR=h?Hoa`x7{rK_YIEWs8Mi8c%Euymz6jEIdKf&@j$( zsz{$lqc|-VCpInPGo$B66t)ANU<7gFfFT<_SG%*JodB}**M$gpY-Fl@bM zM-zz(uDu&(WP#;9CCf|PPoIY8+BcQpiZ3Q&XTLBoiCAmltI{>_w>X2?tM6U>YAjG~ znJZ!yme|;MM?0fkDU|`Rq}uCcx#z=%3pnY%0#)Bb5NVMIqRMW0`d2rC8l*|GVS1@3 z)pwNMwTDWUPp{vd)kG*NOKWSVyo|p0s4zqK685oSWX-$8%s>6=07RuRI`R(>Pp5u z&{XZXr78SU<|`!TWa#GSF*dW%RXG^Oq#(SYjpu6IW0Am(`Dj|c!1>KJ=7n!EVWYX4 zVPg~<2_$57>DM-{WP@6>Fk2NP*4^vAj82M)>53;n-P9g`gx0A=d__~UH172EJ?Euu zzdEc zDY`A&tU)6%avQe%MY{Vxm9b@U zyrP`V$7H{Lao6;nH=0euS(976l)1~B9>8Di(ap3`> zhlGUGeEa6KS`1*HYr-hI^G)1d8aJuhCJ(8FAujkn*A~W}EZD|MiJcP=__XT&uac)Z z+E02UCMGSJKaw{xLxa+8)B66iV{5YVv?)5;b5j<{>s)M}H+SsVWal-A@@&(td?nx?@LeT9Izr@e4 zEaA$!#_yAu$iBL|YBBr$iw@BxgRgkz8cJolwxK3enj>#MS@hAmD2 zSx#IPRZ+deSsyE?MSsn`89l!kmwRtg7ZMih1KV#rS#IjwcRlwKe_2}+U#(_w8mZ4q zH&ANMb#eUo@I!Rf{Fqq`WtJ;i1FDInq=sO!t>#jO>u89kpJYBBQ6eMEi+&O9iFul~G|~2Cw#+H7Y;K&Q zv-utGVO`w^<&l~pR;{#DnyZ+(_IYuRS1Qy^ZwdTwUMI=)W?w;79OXd8*qm3>c7MuO zV*G497uVvUWjNKf{Lz^)B8H590K1X(RO}`tcVk?AlvtxumYmrQh;o z)<`|qj8GZ4mb7!1=jHA&UaV|e+nRHAQnoOV`Ti`T>iPTyBZ&?3KJVY(S)3x*uAWA5 zsw$5Z=G*c%b>(hFt(H0W)(I?5q%IubYqQDk%g`O}wVz-+hf|W^)xqlIT){TD-$+$8 zODQUn^P}%QjS{%5b#szlCLOhPzvM8DQ*Mi<_zp(o;D<9c@(_UqXRz7CtHbJ8w zsp3+^x)NsjD*33)5l+4S_Dhf0B7soVxwxg_&noD0)!`2>fK)tQ(Sez;&Dr(f&x$fT60mc!>LH|p|1S~YKPn9W`bCVszBfMLq*y#+2>9?pWpi`0 zXgm#njD>}qu54tKppmJUIyd)l_ntjKZz~=?d>EIwzV2nyTdcju=jzzln8MAQqc3V< zXf#S+t*%i)c7C_Hv1Zktt}X7d0D2R%?!x&$>4ERx9{@ctUPQ@+GQofU{{6vGQ5g`< zd9^>>xc=#{N|gC4@f+j|*g^$dZbp{vj_xzeAR3lsv75@Tay={F$k2|f7yGfG8I z?=3I|YKDdcI4%b}J8+gY)*?KdoN^K_6QdK`qtg3+TyRWSSa?4zZ5t1vINq*oLt|rY znY1w0a{QNH8q;IiEJLb|aqIxf!*6kFXK26=RJdkkzh~eQL<=cb2jqh!I5N?dM@&pR zzt;oLex9G7AS?5`2w~?}XDTt^O9s6A20m~_w@WI>0L%|vT{?>tl5(Hdynn9;g%`?G zi{WqKEP0jPFi>{(8{2|B41F|6S=u5Ove8{9ryM9o9ZkuA1s-HwUV~bj_796eH}iT^ z)(e>p4N?H6Im76kzI(uwwn$5c(M}q1jm3&-O60_GTNc6LCVcBi<#St>qbsQ{z6;%U zW%@vA)rRqt{6%|H($ct5%^=1stbs;8xmy8=v&R9(!SjHdEI{%Z@6IP6u7qoGad85X zbuR(~Nuj*D#~2ur|DhOG4C8D2Q5~cX5^YV50#sksu<|W0WhA$R_si#{K*Ex%v6S*z2OD zbYrlah0$KkC5i_yY1pmJn)-U%CTXko3nL??rKL9YuaXlJ&v9@hRg5E^o1P@{X{ier zZo#^}3!W5kiU7xc#irx^)zha>v)pBaw=_=XNF*vpirj9HUIW>8?nm|hef#c$Mqe*E ze=DD1Z$Ui7iJdgX%JSAnb{8oSuTy&p9feK5ooH)sA7?0Ac?z>?TRbZ#d*g=F4Wxj* zl0T?&vybRX04v8adir-Ip3BHUaRKCjySN8NMiQCV;9aqi4(GQ|3tXJXQm2`)O&5sP zXn17Ha}Zu1&D7gJCM_*Jx47txL4?cL!GdGW*mb9d_UMIuZaynb$rfW(qB-V<+3PYz zBQ@&!cMj9U@;ot9zFW38TPH`exA@i?n||N!<2!hBJ$2T5J*U-yD9_G=O+pFPwROPnyD`#e@px%CSZue&a>Rm3ZRq(^r((5rA3pqGfLS%k zKaQAQoXjTa#&+!3(Uor(lV?j#n=V~bqVuhApTH^dIavMQP&PmAWgA`Uw$ntT(SAnT z37~((3XaMZ?RE{(2Z4@cMkifzmWVuNG)vL5c*xpRTo zw~;9u0~}h0UZD#3eD)tasHUM2dgja-b`;>Nu44CW5Js9?THIHT!1*OLV1vT;&vhJ8 zLN*jPw}Kx%FOL1`lP3iw1#I)PvrUh89|-79hP+KnOB*4BEk!ul$VfaTlc)qymHXVe zJ1#DHAUNe1S0ABe6u1HTgGEOWj%_c{RnlVuGY>1XttmlX81^2xkl?2-LKuH`+$s#b zP5vh?UBe-P1%eOwobUw&TCmQ6SVqFm^XE+gED}WpI&idD=gDYP*>Hr_+ZaO-IhiC} z(!uD|N-&hkr(c3xa=U12b8U!1!Oz!AC>vG2v}mfb%&jpNK;hu~LUjc-7W{x3oNZ`s zo&v5g-}VD=*th8rA7jSTX%q5$9{PRy^vS8edLw7RNU_JI@hEeq4`*%EW1jI0=Jbe5#!nynV`y=x`P~p1>E1UA^0DrL4 zMChykJ~+?J%p666^S8!b2JeasbQBqEoG5QJ`@)+iuTwvsrIX65EIEz--YE_1m;3@0 zb?`R9u1GB{H2_-(lc4F#J#;6}p=xStRUbTvKXUXah~tUP%__VG#af`DgOL#DG!Y>5 z8=>@0f(#+wmr{YdzR_+dSc<*_6AAy;jl=^W^)> z*ux!jyJCqBVu{$rHqH8mvb8A*G`Q&M0wB!Sq1AVIM>>DzY|>y9-#ixm`TimMHO{8? z6Gfe*<1~77V~3^?5Mff*}KNZiH>KvU|ilYRY|WfV@YA-<9LF` zZ-z>zc5O2x1DeQ_5LDr$$Y*nsmxrfiicI7{>E_#pv{_Cz#v>n|`j!;FAdRMuR&&L) zGTRlOMggPJR_V`6a>+C(=_+a4;Y}GpA|*Y&~!BNle|d+T$* ze2@F6(%un@9SWXy9xdT#QeMFTiX63tWPyaihYug-m^HAXs-iP3vOC3dF4EZZFu1Zt zhyM~snF?==-{>r}e#o2XN~8_QOd2sby@xEBJ7g$E$rmX+*4%k{KU}r*iDsszmNmiW zsSx&@>x4<>kP>aAg?@(dmp2T~k@Yb z70Fq9o{%6P*bdPw1&C*V76}bD^ulY~(pA%v)#1p$YAD(?Vq+Ua~fGf zdTo)C4BJ?BgSr^R4l8;T4&6MK95eNQAzE-O%n(z!G})c}^ZZX5cA98rU%TOBQf|aE={6p7ib8EihR8k<=VjL@&o&GJ0nE?ahFfO?Ic# zXya)Rgj#H#Zijhw2=h6^?;>rRpX}phOF;Oy*o~ee+n%9<1{AOB7&o2o^+0KAikFv{ z)1bd--sRrE>Ek7O!NJNo_h;RbT&-&`T zx}IKoPmdlXX=nI8cduT3^dUPNu30jNVyOAoSEms}XmAmX*?Y-e41p#YXDOHCDtr%t z=W;W@xf%R+#Ob`QWH81+Ab!pM<;xd~wzPjD;lSPc8wm$gMy4P?as}+4lhRRGNX{Va zak9naDdzoqb5OAz!Y zDBZCG&am89tV58Zrs`I$mpNw(TdJh%Ry|psd6C-p{3w^@_s`F9iTXsBvB~^CmpuzF zUhIGR>={zIfWib);a#X}z?lmxhyzLlrw@BK2o1ai%ETslb;g3Q)IEUvg>ArNmLyKr`%->Pe)y{kJ~#j>|6wyB3mJ$B#D` zPu_`gnqqO*SN#Rp*YA1qf=;vk2|77joQq=UqRjLI+>b?n!^rjW2hzsDhN%Gdp>;laP0752)qpqq~W?q zlZz*UqoXsQJb9v2hV1$B;|7PDY4`Y;HbiGcOL?Zn#a&gFUQI+q#M9TG2~qyShr!r&?N0UV^66q!fpwyN53Mw85lR{oA_7D*ZzBu69 zxWQXD)-geZB@ERr{B&BoEFGYwMeU}(|r>w%4Qwi z5e{~EWR1=IWN^Hh<2<+!efA#GK%r26uWUskO|rCrEa3I67xM+%+Rbul2f6-p~^P-|XsV881z1cI8J;v|qYj&zu;YdYvrJ zsiS#!vT>Z(ABf5y2EIFc#LqN3gJFP8+Di-6aVYT8GBVVG%&(VP*4vh9|7&7&{ZC@l z^{Oh`XX%0mRM4y^#mh4Wcg)Z&8csW`O$UALi&4Hg%}!jBSm=n@lONzd630m^<{(2Me$;xcdBa!c6{V#T4E3yL%f=E4&55t2|K5$~N^j4RB z%uMs_wQ3(u$tG^yu-;9h1<(=05Ngr<$i8@eW=E2=c3I&q#$mJMfFw$?tEPY2Q6wL8#kw0nxo-o;dky&Uqs z2Z<5?yb&uRoSaTvmk^m*(7#+F8@bUvtv26qn+qez{M1`D`r{Xw(aO{4manL-kIQo!OG{ifeItVFv0KYn@%e9p#~B$r)0O}1qd_`dppS;6?;7=|qXzNi1EyjUF#dIY zV}m$q2{YuK=l)f;aBM6bb8>ARk~ z)*{{e)}VY{U3G5@Lt1oLYphgE!1@!7-E>FL9@NAZxq+S5p?`8;d;XdGa)kC8;J$Xa zGS5>5yHq_t+YBzwEVhusHUhgVtpdBSn&bHUu0-c==#l8j_F32^H-SD(0yKt@UeG)v z{P4$D(<;dJZ+A%kmSkTeP!B11p^IpCb{1Yj97lAlk}1Gt(Vo2AI5fX^@7|c1v!_m# zqZxo{@wTnwF>S7`tyKs6FTx@MC51~IYe}{O3gSPNMC`zL|8B4N$C@Zq^T`4ztEy0P z7~-o;>mr8Mdmx&Ghlgh@DgK^*f8YF?zd6c_j)J{=Z{u4?zb;7u9qjvQyz`B=CFpTtrcZ5KkuF(iR_w~NiS$^SNOhkd&h1KuE+`(&Of}#LckbE+%2m>@zXGp| z!IX;F^Z-H4=3#A}4yOt1Mxg0gIj}fFE=^5KQviO3n~zW9+O@}^_y9-n;K73& zqpE=VbIkBbb5X*+vCQ}X7K;+oZo#|y3zSk{L1SFN8ui8`GFYye2>*o4i`%VZF9HHW zp}(v?7Ti)m=bFg?uDt5Ar%=-N4`QH8KbyyZm!~OTp03cn)v-sCwa8DNBvjyBykjl$ z*iif79m`ZNqr(03+&$vMh%#fb&It5wdFmI+b&vl=9X$C*5)`+7w%PXK2)fuVU$;s! z>Qz9{!l>sI?U<^k(D?rRfS(IUBh0RrWATuX=ewQ`LcdQyE%5vwOlsBc-aQ>z_YO&M z&`ppa=dWU55RH%mZ{DPUoChf0668GK2UUQ9Vo!CQ?p07y(oj+gfcho)_3P6J>?z%b zJ$0qz9qUcWY`>W-g*rBYcX3@gKZ!|85Ae~mcrM`x#nDn2|PVeA`@`RE<%T!9cmPqu+K7yX4m|du)SxYku(2ZA;N?F zy>8AwhPDP{`i3p(JwJjf#AjACG=UB9T7@tRCi=T_Xd* z;G^@?kD7CwBWS)@AZQkm8mgfSacsshc+%Oars4UJe}lx(h%rp2PLa8hZ%mb{+Q~e4 z0fj-SCa=F$lgZI&JCrdwGv9-4u_R+##_41ZDc#=I1^-=F>MX2s=rHiuSR}PU4!mFS z>^v`T#^0hK!P?o_83q@Hrrq0 zmJ#qBU2fH$xb@~QZKO&aRkr!iC|c0E>;`%*rhQG{dBW=6XrrT$aPBn4Yi?xhEkXPG z^@;|N+!Q^r&!oZ=f-p)TnMn*ux)3+aw%(pNxQDRH>e?Pt_U>K%>ZWu1V!`0ZXBJ4d z@D_jns0GiWpv||g?bAU;MMW_ij^C%J`GFr1Rew@%Z1485?BAJab<)V$H?nNM+zFOvrukq{V-2!;42DPZ-hzvL+ zJ^522FN36aaL=x?llFQLGohk}ELFXvX=B^X^pG~uo zYGKdyDy3`gL14_?WL;80JVa30LgYXMryDm?fLn$(Q(WTzS_VsuH!E2HH3G5@O#8&R zO8-ZM8isZl7>bkg`I&p*wpWLWZhgJ@JAv2fHE43X_wGH~?4U670_ty{tAb|rtpMjw z5y_bV#;nS{LFz9qmP8t8pidSG9dP!yegEPIol=SE=@ex~9sj<1Zc`tBe6=tA6kbtB$ThIFlgr-FXzNqLz$A=Ym=zA-ja1vOP) z(G7ncHU99PGm17U>F1P!!MkUEb)+jhP<38qZRpTCx@>&j;mc~pNO9>~T7$j@_h~;7 z>yA@E%ED5v=Z0u_PlxoS;6SD_kO32kgJt{o_ckZZrbIB*lX+7c5qa8rF z`$akR{BmLnS*f?4;gACM%|Q;NsJqChfV7$46A0dJQZqicUBy@1?~{XP%Z)n_tK;AM z$enGfDkyH-%f@;Aw-0i*kJ1!eLPL14w6hBXhzHoV$2Py|^*sRwKD|4D13f%Efah>zP819O@L zLQ*es+KW?<&y(8PIP$wQfT013i0smBgE)zsvhE3hh5;TNHR}s(qXRWl1%q_Z^o>H& z;Ny?6AD`co17+-k4!=gkP4)-&O!q8jNQYIYfO&g(`D9Z!EH`oNX7QuvG(A5fa8zQt zxMlRj#q%)@$-v%mAKP!SIyWR&wS&_?%1O`hmfDUt>AUvQo%`kIbNu`bqvtNBLVF(t zBvlP4l1ta;6cYw%g5*$D!P(uDN2PIeutd5i_i*mCm!gc1;~GXP8|rBmh|*w$InW*%L6!N1xwVXxFu~ zaB@u{O^P2vb3l!N5uh^YkT#)+}z*>=$2io5ty=ezc`OjQRZ|}f# zEabIWs)HyRAe4Gqq+J_3-+Ko=?FgoK0=x4Gf2t}Y=obs<&8+v*g= z4%>WPrR0{;b2RoX35mN3#*ki1uqPN0G$mZ8)D#q6K;7{&U=b^7R(}%oFJ_iqG^b!c zrhpMcDOnZnTY#w8nAM@MQKP7+D1?r6byNPScbZ+_1iSn{CWfckzNNiPw?R>(?3e>j_rSn^rXP7f2AzeOfTc1WB{82!q%SprSa3#1F#U@ z5vNOa0fkV11Szv44UaGU{ng>(f=HAs<+=2A=;0W|1vLmC$TL}4S^E-1d3kx~+$3Qf z?aDG6;20EueheN#K~+qdA%#RB$+MxII*r6!Cf>}=&HZo^Z)<9bLlp7WR8_d!gfG%b z+uqJeAmQP56o_WT#K_nL(B({6Z4%s4W1kgzWx#jNqXg>0pzcWxL&F%b*gWLz-APm4 z2g|T$0RTa~VV?&j1N;p^?1-Gd|LHYQQyamQ4;ygt>)F7Rc>^6A zFE|FhG)=foq}HY`j6uZjf^OeLKd#+yomm69bPBd~nzOUB7dO|Y@r8xjgi>mnoSa;a zGeyJy?&K$OvKQ z)p~#LlhsxmX!3k0h#yF&0CX4xM?{EsvWe348}^U$(b#$rT_z!-s6suW6eA50(-Ng( zXGdU1j2pOPK=*^bgHkax>{BnPT$wjAG8%eV$eF&jjHpq@aLbPTZVnV;)dbfEXe5Wx z^`KXB5DxEun&U7ihcrBIuCD?ct9kikC_5xL6!g};v0n@YqL#M2o;N9mbm@( z42nxR8tH!mn+qqZ>-~M7YjKSxBt_`Uh9_a75j_O5mHuA3lZ}U<$D1$*3*Pl5k)~h_ z%p6F+6C$B;prBV<6$o9J<^_^i7lAia8<@Jv7>(R8&-&gMaL% z(PNYtg9!)So%-F6{}`Qb2<%ZrL~H)iIRhj&*|9-tZq-X1vt9t38R;^IBykfL?LN$i zI7lFgYQX$E-2e=z04gtHt^nK%3m?2E8L$~;&m4x)n?q}zPKn3jakEbZ4FC?Tn3elc z(r6uMXbAuhLxLEG1z3E?Cnj1qmirJ77(y^0cRWE@cxay0|IiBxOQveF5^}p0VB8_W z>f~C6z(SGWaNj|rY0AjV%q%F5+=j)Q1|_R> KBlFt5$Nvu->j8cM literal 0 HcmV?d00001 diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-line-series-color-variant-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-stylings-line-series-color-variant-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..0200cf56bee16c67fc55ee81709fda2673688e6c GIT binary patch literal 37078 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

g#_Dk{SH&=%GG`HA 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 AEC2ui literal 0 HcmV?d00001 diff --git a/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts b/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts new file mode 100644 index 0000000000..5e11ced284 --- /dev/null +++ b/src/chart_types/partition_chart/layout/utils/__mocks__/d3_utils.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +const module = jest.requireActual('../d3_utils.ts'); + +export const stringToRGB = jest.fn(module.stringToRGB); +export const validateColor = jest.fn(module.validateColor); +export const argsToRGB = jest.fn(module.argsToRGB); +export const argsToRGBString = jest.fn(module.argsToRGBString); +export const RGBtoString = jest.fn(module.RGBtoString); diff --git a/src/chart_types/partition_chart/layout/utils/d3_utils.test.ts b/src/chart_types/partition_chart/layout/utils/d3_utils.test.ts new file mode 100644 index 0000000000..144914746d --- /dev/null +++ b/src/chart_types/partition_chart/layout/utils/d3_utils.test.ts @@ -0,0 +1,219 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { + stringToRGB, + validateColor, + defaultD3Color, + argsToRGB, + RgbObject, + argsToRGBString, + RGBtoString, +} from './d3_utils'; + +describe('d3 Utils', () => { + describe('stringToRGB', () => { + describe('bad colors or undefined', () => { + it('should return default color for undefined color string', () => { + expect(stringToRGB()).toMatchObject({ + r: 255, + g: 0, + b: 0, + opacity: 1, + }); + }); + + it('should return default RgbObject', () => { + expect(stringToRGB('not a color')).toMatchObject({ + r: 255, + g: 0, + b: 0, + opacity: 1, + }); + }); + + it('should return default color if bad opacity', () => { + expect(stringToRGB('rgba(50,50,50,x)')).toMatchObject({ + r: 255, + g: 0, + b: 0, + opacity: 1, + }); + }); + }); + + describe('hex colors', () => { + it('should return RgbObject', () => { + expect(stringToRGB('#ef713d')).toMatchObject({ + r: 239, + g: 113, + b: 61, + }); + }); + + it('should return RgbObject from shorthand', () => { + expect(stringToRGB('#ccc')).toMatchObject({ + r: 204, + g: 204, + b: 204, + }); + }); + + it('should return RgbObject with correct opacity', () => { + // https://gist.github.com/lopspower/03fb1cc0ac9f32ef38f4 + expect(stringToRGB('#ef713d80').opacity).toBeCloseTo(0.5, 1); + }); + + it('should return correct RgbObject for alpha value of 0', () => { + expect(stringToRGB('#00000000')).toMatchObject({ + r: 0, + g: 0, + b: 0, + opacity: 0, + }); + }); + }); + + describe('rgb colors', () => { + it('should return RgbObject', () => { + expect(stringToRGB('rgb(50,50,50)')).toMatchObject({ + r: 50, + g: 50, + b: 50, + }); + }); + + it('should return RgbObject with correct opacity', () => { + expect(stringToRGB('rgba(50,50,50,0.25)').opacity).toBe(0.25); + }); + + it('should return correct RgbObject for alpha value of 0', () => { + expect(stringToRGB('rgba(50,50,50,0)')).toMatchObject({ + r: 50, + g: 50, + b: 50, + opacity: 0, + }); + }); + }); + + describe('hsl colors', () => { + it('should return RgbObject', () => { + expect(stringToRGB('hsl(0,0%,50%)')).toMatchObject({ + r: 127.5, + g: 127.5, + b: 127.5, + }); + }); + + it('should return RgbObject with correct opacity', () => { + expect(stringToRGB('hsla(0,0%,50%,0.25)').opacity).toBe(0.25); + }); + + it('should return correct RgbObject for alpha value of 0', () => { + expect(stringToRGB('hsla(0,0%,50%,0)')).toEqual({ + r: 127.5, + g: 127.5, + b: 127.5, + opacity: 0, + }); + }); + }); + + describe('named colors', () => { + it('should return RgbObject', () => { + expect(stringToRGB('aquamarine')).toMatchObject({ + r: 127, + g: 255, + b: 212, + }); + }); + + it('should return default RgbObject with 0 opacity', () => { + expect(stringToRGB('transparent')).toMatchObject({ + r: 0, + g: 0, + b: 0, + opacity: 0, + }); + }); + + it('should return default RgbObject with 0 opacity even with override', () => { + expect(stringToRGB('transparent', 0.5)).toMatchObject({ + r: 0, + g: 0, + b: 0, + opacity: 0, + }); + }); + }); + + describe('Optional opactiy override', () => { + it('should override opacity from color', () => { + expect(stringToRGB('rgba(50,50,50,0.25)', 0.75).opacity).toBe(0.75); + }); + + it('should use OpacityFn to compute opacity override', () => { + expect(stringToRGB('rgba(50,50,50,0.25)', (o) => o * 2).opacity).toBe(0.5); + }); + }); + }); + + describe('validateColor', () => { + it.each(['r', 'g', 'b', 'opacity'])('should return null if %s is NaN', (value) => { + expect( + validateColor({ + ...defaultD3Color, + [value]: NaN, + }), + ).toBeNull(); + }); + + it('should return valid colors', () => { + expect(validateColor(defaultD3Color)).toBe(defaultD3Color); + }); + }); + + describe('argsToRGB', () => { + it.each(['r', 'g', 'b', 'opacity'])('should return defaultD3Color if %s is NaN', (value) => { + const { r, g, b, opacity }: RgbObject = { + ...defaultD3Color, + [value]: NaN, + }; + expect(argsToRGB(r, g, b, opacity)).toEqual(defaultD3Color); + }); + + it('should return valid colors', () => { + const { r, g, b, opacity } = defaultD3Color; + expect(argsToRGB(r, g, b, opacity)).toEqual(defaultD3Color); + }); + }); + + describe('argsToRGBString', () => { + it('should return valid colors', () => { + const { r, g, b, opacity } = defaultD3Color; + expect(argsToRGBString(r, g, b, opacity)).toBe('rgb(255, 0, 0)'); + }); + }); + + describe('RGBtoString', () => { + it('should return valid colors', () => { + expect(RGBtoString(defaultD3Color)).toBe('rgb(255, 0, 0)'); + }); + }); +}); diff --git a/src/chart_types/partition_chart/layout/utils/d3_utils.ts b/src/chart_types/partition_chart/layout/utils/d3_utils.ts index dbf820ec4a..84002fc871 100644 --- a/src/chart_types/partition_chart/layout/utils/d3_utils.ts +++ b/src/chart_types/partition_chart/layout/utils/d3_utils.ts @@ -23,16 +23,76 @@ type A = number; export type RgbTuple = [RGB, RGB, RGB, RGB?]; export type RgbObject = { r: RGB; g: RGB; b: RGB; opacity: A }; -const defaultColor: RgbObject = { r: 255, g: 0, b: 0, opacity: 1 }; -const defaultD3Color: D3RGBColor = d3Rgb(defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.opacity); +/** @internal */ +export const defaultColor: RgbObject = { r: 255, g: 0, b: 0, opacity: 1 }; +/** @internal */ +export const transparentColor: RgbObject = { r: 0, g: 0, b: 0, opacity: 0 }; +/** @internal */ +export const defaultD3Color: D3RGBColor = d3Rgb(defaultColor.r, defaultColor.g, defaultColor.b, defaultColor.opacity); + +/** @internal */ +export type OpacityFn = (colorOpacity: number) => number; + +/** @internal */ +export function stringToRGB(cssColorSpecifier?: string, opacity?: number | OpacityFn): RgbObject { + if (cssColorSpecifier === 'transparent') { + return transparentColor; + } + const color = getColor(cssColorSpecifier); + + if (opacity === undefined) { + return color; + } + + const opacityOverride = typeof opacity === 'number' ? opacity : opacity(color.opacity); + + if (isNaN(opacityOverride)) { + return color; + } + + return { + ...color, + opacity: opacityOverride, + }; +} + +/** + * Returns color as RgbObject or default fallback. + * + * Handles issue in d3-color for hsla and rgba colors with alpha value of `0` + * + * @param cssColorSpecifier + */ +function getColor(cssColorSpecifier: string = ''): RgbObject { + let color: D3RGBColor; + const endRegEx = /,\s*0+(\.0*)?\s*\)$/; + // TODO: make this check more robust + if (/^(rgba|hsla)\(/i.test(cssColorSpecifier) && endRegEx.test(cssColorSpecifier)) { + color = { + ...d3Rgb(cssColorSpecifier.replace(endRegEx, ',1)')), + opacity: 0, + }; + } else { + color = d3Rgb(cssColorSpecifier); + } + + return validateColor(color) ?? defaultColor; +} /** @internal */ -export function stringToRGB(cssColorSpecifier: string): RgbObject { - return d3Rgb(cssColorSpecifier) || defaultColor; +export function validateColor(color: D3RGBColor): D3RGBColor | null { + const { r, g, b, opacity } = color; + + if (isNaN(r) || isNaN(g) || isNaN(b) || isNaN(opacity)) { + return null; + } + + return color; } -function argsToRGB(r: number, g: number, b: number, opacity: number): D3RGBColor { - return d3Rgb(r, g, b, opacity) || defaultD3Color; +/** @internal */ +export function argsToRGB(r: number, g: number, b: number, opacity: number): D3RGBColor { + return validateColor(d3Rgb(r, g, b, opacity)) ?? defaultD3Color; } /** @internal */ diff --git a/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts b/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts index da553361c3..ce09ed5426 100644 --- a/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts +++ b/src/chart_types/xy_chart/renderer/canvas/primitives/rect.ts @@ -31,8 +31,6 @@ export function renderRect( return; } - // fill - if (fill) { const borderOffset = !disableBoardOffset && stroke && stroke.width > 0.001 ? stroke.width : 0; // console.log(stroke, borderOffset); @@ -58,12 +56,16 @@ export function renderRect( drawRect(ctx, { x, y, width, height }); if (stroke.dash) { ctx.setLineDash(stroke.dash); + } else { + // Setting linecap with dash causes solid line + ctx.lineCap = 'square'; } ctx.stroke(); } } +/** @internal */ function drawRect(ctx: CanvasRenderingContext2D, rect: Rect) { const { x, y, width, height } = rect; ctx.beginPath(); @@ -100,5 +102,12 @@ export function renderMultiRect(ctx: CanvasRenderingContext2D, rects: Rect[], fi ctx.strokeStyle = RGBtoString(stroke.color); ctx.lineWidth = stroke.width; ctx.stroke(); + + if (stroke.dash) { + ctx.setLineDash(stroke.dash); + } else { + // Setting linecap with dash causes solid line + ctx.lineCap = 'square'; + } } } diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/area.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/area.test.ts new file mode 100644 index 0000000000..869760d07f --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/styles/area.test.ts @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { MockStyles } from '../../../../../mocks'; +import { buildAreaStyles } from './area'; +import { Fill } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; +import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; + +jest.mock('../../../../partition_chart/layout/utils/d3_utils'); +jest.mock('../../../../../utils/commons'); + +const COLOR = 'aquamarine'; + +describe('Area styles', () => { + describe('#buildAreaStyles', () => { + let result: Fill; + let baseColor = COLOR; + let themeAreaStyle = MockStyles.area(); + let geometryStateStyle = MockStyles.geometryState(); + + function setDefaults() { + baseColor = COLOR; + themeAreaStyle = MockStyles.area(); + geometryStateStyle = MockStyles.geometryState(); + } + + beforeEach(() => { + result = buildAreaStyles(baseColor, themeAreaStyle, geometryStateStyle); + }); + + it('should call getColorFromVariant with correct args for fill', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themeAreaStyle.fill); + }); + + describe('Colors', () => { + const fillColor = '#4aefb8'; + + beforeAll(() => { + setDefaults(); + (getColorFromVariant as jest.Mock).mockReturnValue(fillColor); + }); + + it('should call stringToRGB with values from getColorFromVariant', () => { + expect(stringToRGB).nthCalledWith(1, fillColor, expect.any(Function)); + }); + + it('should return fill with color', () => { + expect(result.color).toEqual(stringToRGB(fillColor)); + }); + }); + + describe('Opacity', () => { + const fillColorOpacity = 0.5; + const fillColor = `rgba(10,10,10,${fillColorOpacity})`; + const fillOpacity = 0.6; + const geoOpacity = 0.75; + + beforeAll(() => { + setDefaults(); + themeAreaStyle = MockStyles.area({ opacity: fillOpacity }); + geometryStateStyle = MockStyles.geometryState({ opacity: geoOpacity }); + (getColorFromVariant as jest.Mock).mockReturnValue(fillColor); + }); + + it('should return correct fill opacity', () => { + const expected = fillColorOpacity * fillOpacity * geoOpacity; + expect(result.color.opacity).toEqual(expected); + }); + }); + }); +}); diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/area.ts b/src/chart_types/xy_chart/renderer/canvas/styles/area.ts index 898643d709..bf09e43fea 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/area.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/area.ts @@ -17,8 +17,9 @@ * under the License. */ import { GeometryStateStyle, AreaStyle } from '../../../../../utils/themes/theme'; -import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; +import { stringToRGB, OpacityFn } from '../../../../partition_chart/layout/utils/d3_utils'; import { Fill } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; /** * Return the rendering props for an area. The color of the area will be overwritten @@ -33,8 +34,8 @@ export function buildAreaStyles( themeAreaStyle: AreaStyle, geometryStateStyle: GeometryStateStyle, ): Fill { - const fillColor = stringToRGB(themeAreaStyle.fill || baseColor); - fillColor.opacity = fillColor.opacity * themeAreaStyle.opacity * geometryStateStyle.opacity; + const fillOpacity: OpacityFn = (opacity) => opacity * themeAreaStyle.opacity * geometryStateStyle.opacity; + const fillColor = stringToRGB(getColorFromVariant(baseColor, themeAreaStyle.fill), fillOpacity); return { color: fillColor, }; diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/bar.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/bar.test.ts new file mode 100644 index 0000000000..a66da315ce --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/styles/bar.test.ts @@ -0,0 +1,152 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { MockStyles } from '../../../../../mocks'; +import { buildBarStyles } from './bar'; +import { Fill, Stroke } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; +import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; + +jest.mock('../../../../partition_chart/layout/utils/d3_utils'); +jest.mock('../../../../../utils/commons'); + +const COLOR = 'aquamarine'; + +describe('Bar styles', () => { + describe('#buildBarStyles', () => { + let result: { fill: Fill; stroke: Stroke }; + let baseColor = COLOR; + let themeRectStyle = MockStyles.rect(); + let themeRectBorderStyle = MockStyles.rectBorder(); + let geometryStateStyle = MockStyles.geometryState(); + + function setDefaults() { + baseColor = COLOR; + themeRectStyle = MockStyles.rect(); + themeRectBorderStyle = MockStyles.rectBorder(); + geometryStateStyle = MockStyles.geometryState(); + } + + beforeEach(() => { + result = buildBarStyles(baseColor, themeRectStyle, themeRectBorderStyle, geometryStateStyle); + }); + + it('should call getColorFromVariant with correct args for fill', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themeRectStyle.fill); + }); + + it('should call getColorFromVariant with correct args for border', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themeRectBorderStyle.stroke); + }); + + describe('Colors', () => { + const fillColor = '#4aefb8'; + const strokeColor = '#a740cf'; + + beforeAll(() => { + setDefaults(); + (getColorFromVariant as jest.Mock).mockImplementation(() => { + const { length } = (getColorFromVariant as jest.Mock).mock.calls; + return length === 1 ? fillColor : strokeColor; + }); + }); + + it('should call stringToRGB with values from getColorFromVariant', () => { + expect(stringToRGB).nthCalledWith(1, fillColor, expect.any(Function)); + expect(stringToRGB).nthCalledWith(2, strokeColor, expect.any(Function)); + }); + + it('should return fill with color', () => { + expect(result.fill.color).toEqual(stringToRGB(fillColor)); + }); + + it('should return stroke with color', () => { + expect(result.stroke.color).toEqual(stringToRGB(strokeColor)); + }); + }); + + describe('Opacity', () => { + const fillColorOpacity = 0.5; + const strokeColorOpacity = 0.25; + const fillColor = `rgba(10,10,10,${fillColorOpacity})`; + const strokeColor = `rgba(10,10,10,${strokeColorOpacity})`; + const fillOpacity = 0.6; + const strokeOpacity = 0.8; + const geoOpacity = 0.75; + + beforeAll(() => { + setDefaults(); + themeRectStyle = MockStyles.rect({ opacity: fillOpacity }); + themeRectBorderStyle = MockStyles.rectBorder({ strokeOpacity }); + geometryStateStyle = MockStyles.geometryState({ opacity: geoOpacity }); + (getColorFromVariant as jest.Mock).mockImplementation(() => { + const { length } = (getColorFromVariant as jest.Mock).mock.calls; + return length === 1 ? fillColor : strokeColor; + }); + }); + + it('should return correct fill opacity', () => { + const expected = fillColorOpacity * fillOpacity * geoOpacity; + expect(result.fill.color.opacity).toEqual(expected); + }); + + it('should return correct stroke opacity', () => { + const expected = strokeColorOpacity * strokeOpacity * geoOpacity; + expect(result.stroke.color.opacity).toEqual(expected); + }); + + describe('themeRectBorderStyle opacity is undefined', () => { + beforeAll(() => { + themeRectBorderStyle = { + ...MockStyles.rectBorder(), + strokeOpacity: undefined, + }; + }); + + it('should use themeRectStyle opacity', () => { + const expected = strokeColorOpacity * fillOpacity * geoOpacity; + expect(result.stroke.color.opacity).toEqual(expected); + }); + }); + }); + + describe('Width', () => { + describe('visible is set to false', () => { + beforeAll(() => { + themeRectBorderStyle = MockStyles.rectBorder({ visible: false }); + }); + + it('should set stroke width to zero', () => { + expect(result.stroke.width).toEqual(0); + }); + }); + + describe('visible is set to true', () => { + const strokeWidth = 22; + + beforeAll(() => { + themeRectBorderStyle = MockStyles.rectBorder({ visible: true, strokeWidth }); + }); + + it('should set stroke width to strokeWidth', () => { + expect(result.stroke.width).toEqual(strokeWidth); + }); + }); + }); + }); +}); diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/bar.ts b/src/chart_types/xy_chart/renderer/canvas/styles/bar.ts index 898595d123..7b951eb0ae 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/bar.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/bar.ts @@ -17,8 +17,9 @@ * under the License. */ import { GeometryStateStyle, RectStyle, RectBorderStyle } from '../../../../../utils/themes/theme'; -import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; +import { stringToRGB, OpacityFn } from '../../../../partition_chart/layout/utils/d3_utils'; import { Stroke, Fill } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; /** * Return the rendering styles (stroke and fill) for a bar. @@ -38,17 +39,16 @@ export function buildBarStyles( themeRectBorderStyle: RectBorderStyle, geometryStateStyle: GeometryStateStyle, ): { fill: Fill; stroke: Stroke } { - const fillColor = stringToRGB(themeRectStyle.fill || baseColor); - const fillOpacity = themeRectStyle.opacity * geometryStateStyle.opacity; - fillColor.opacity = fillOpacity; + const fillOpacity: OpacityFn = (opacity) => opacity * themeRectStyle.opacity * geometryStateStyle.opacity; + const fillColor = stringToRGB(getColorFromVariant(baseColor, themeRectStyle.fill), fillOpacity); const fill: Fill = { color: fillColor, }; - const strokeColor = stringToRGB(themeRectBorderStyle.stroke || baseColor); const defaultStrokeOpacity = themeRectBorderStyle.strokeOpacity === undefined ? themeRectStyle.opacity : themeRectBorderStyle.strokeOpacity; const borderStrokeOpacity = defaultStrokeOpacity * geometryStateStyle.opacity; - strokeColor.opacity = strokeColor.opacity * borderStrokeOpacity; + const strokeOpacity: OpacityFn = (opacity) => opacity * borderStrokeOpacity; + const strokeColor = stringToRGB(getColorFromVariant(baseColor, themeRectBorderStyle.stroke), strokeOpacity); const stroke: Stroke = { color: strokeColor, width: themeRectBorderStyle.visible ? themeRectBorderStyle.strokeWidth : 0, diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/line.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/line.test.ts new file mode 100644 index 0000000000..347fcd7099 --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/styles/line.test.ts @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { MockStyles } from '../../../../../mocks'; +import { buildLineStyles } from './line'; +import { Stroke } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; +import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; + +jest.mock('../../../../partition_chart/layout/utils/d3_utils'); +jest.mock('../../../../../utils/commons'); + +const COLOR = 'aquamarine'; + +describe('Line styles', () => { + describe('#buildLineStyles', () => { + let result: Stroke; + let baseColor = COLOR; + let themeLineStyle = MockStyles.line(); + let geometryStateStyle = MockStyles.geometryState(); + + function setDefaults() { + baseColor = COLOR; + themeLineStyle = MockStyles.line(); + geometryStateStyle = MockStyles.geometryState(); + } + + beforeEach(() => { + result = buildLineStyles(baseColor, themeLineStyle, geometryStateStyle); + }); + + it('should call getColorFromVariant with correct args for stroke', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themeLineStyle.stroke); + }); + + it('should set strokeWidth from themeLineStyle', () => { + expect(result.width).toBe(themeLineStyle.strokeWidth); + }); + + it('should set dash from themeLineStyle', () => { + expect(result.dash).toEqual(themeLineStyle.dash); + }); + + describe('Colors', () => { + const strokeColor = '#4aefb8'; + + beforeAll(() => { + setDefaults(); + (getColorFromVariant as jest.Mock).mockReturnValue(strokeColor); + }); + + it('should call stringToRGB with values from getColorFromVariant', () => { + expect(stringToRGB).nthCalledWith(1, strokeColor, expect.any(Function)); + }); + + it('should return stroke with color', () => { + expect(result.color).toEqual(stringToRGB(strokeColor)); + }); + }); + + describe('Opacity', () => { + const strokeColorOpacity = 0.5; + const strokeColor = `rgba(10,10,10,${strokeColorOpacity})`; + const strokeOpacity = 0.6; + const geoOpacity = 0.75; + + beforeAll(() => { + setDefaults(); + themeLineStyle = MockStyles.line({ opacity: strokeOpacity }); + geometryStateStyle = MockStyles.geometryState({ opacity: geoOpacity }); + (getColorFromVariant as jest.Mock).mockReturnValue(strokeColor); + }); + + it('should return correct stroke opacity', () => { + const expected = strokeColorOpacity * strokeOpacity * geoOpacity; + expect(result.color.opacity).toEqual(expected); + }); + }); + }); +}); diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/line.ts b/src/chart_types/xy_chart/renderer/canvas/styles/line.ts index 4c5d0351b6..c86dc2e8d8 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/line.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/line.ts @@ -17,8 +17,9 @@ * under the License. */ import { GeometryStateStyle, LineStyle } from '../../../../../utils/themes/theme'; -import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; +import { stringToRGB, OpacityFn } from '../../../../partition_chart/layout/utils/d3_utils'; import { Stroke } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; /** * Return the rendering props for a line. The color of the line will be overwritten @@ -33,8 +34,8 @@ export function buildLineStyles( themeLineStyle: LineStyle, geometryStateStyle: GeometryStateStyle, ): Stroke { - const strokeColor = stringToRGB(themeLineStyle.stroke || baseColor); - strokeColor.opacity = strokeColor.opacity * themeLineStyle.opacity * geometryStateStyle.opacity; + const strokeOpacity: OpacityFn = (opacity) => opacity * themeLineStyle.opacity * geometryStateStyle.opacity; + const strokeColor = stringToRGB(getColorFromVariant(baseColor, themeLineStyle.stroke), strokeOpacity); return { color: strokeColor, width: themeLineStyle.strokeWidth, diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts b/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts new file mode 100644 index 0000000000..5cb71d12de --- /dev/null +++ b/src/chart_types/xy_chart/renderer/canvas/styles/point.test.ts @@ -0,0 +1,179 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { MockStyles } from '../../../../../mocks'; +import { buildPointStyles } from './point'; +import { Fill, Stroke } from '../../../../../geoms/types'; +import { getColorFromVariant } from '../../../../../utils/commons'; +import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; +import { PointStyle } from '../../../../../utils/themes/theme'; + +jest.mock('../../../../partition_chart/layout/utils/d3_utils'); +jest.mock('../../../../../utils/commons'); + +const COLOR = 'aquamarine'; + +describe('Point styles', () => { + describe('#buildPointStyles', () => { + let result: { fill: Fill; stroke: Stroke; radius: number }; + let baseColor = COLOR; + let themePointStyle = MockStyles.point(); + let geometryStateStyle = MockStyles.geometryState(); + let overrides: Partial = {}; + + function setDefaults() { + baseColor = COLOR; + themePointStyle = MockStyles.point(); + geometryStateStyle = MockStyles.geometryState(); + overrides = {}; + } + + beforeEach(() => { + result = buildPointStyles(baseColor, themePointStyle, geometryStateStyle, overrides); + }); + + it('should call getColorFromVariant with correct args for fill', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themePointStyle.fill); + }); + + it('should call getColorFromVariant with correct args for border', () => { + expect(getColorFromVariant).nthCalledWith(1, baseColor, themePointStyle.stroke); + }); + + it('should set strokeWidth from themePointStyle', () => { + expect(result.stroke.width).toBe(themePointStyle.strokeWidth); + }); + + it('should set radius from themePointStyle', () => { + expect(result.radius).toBe(themePointStyle.radius); + }); + + describe('Colors', () => { + const fillColor = '#4aefb8'; + const strokeColor = '#a740cf'; + + beforeAll(() => { + setDefaults(); + (getColorFromVariant as jest.Mock).mockImplementation(() => { + const { length } = (getColorFromVariant as jest.Mock).mock.calls; + return length === 1 ? fillColor : strokeColor; + }); + }); + + it('should call stringToRGB with values from getColorFromVariant', () => { + expect(stringToRGB).nthCalledWith(1, fillColor, expect.any(Function)); + expect(stringToRGB).nthCalledWith(2, strokeColor, expect.any(Function)); + }); + + it('should return fill with color', () => { + expect(result.fill.color).toEqual(stringToRGB(fillColor)); + }); + + it('should return stroke with color', () => { + expect(result.stroke.color).toEqual(stringToRGB(strokeColor)); + }); + }); + + describe('Opacity', () => { + const fillColorOpacity = 0.5; + const strokeColorOpacity = 0.25; + const fillColor = `rgba(10,10,10,${fillColorOpacity})`; + const strokeColor = `rgba(10,10,10,${strokeColorOpacity})`; + const pointOpacity = 0.6; + const geoOpacity = 0.75; + + beforeAll(() => { + setDefaults(); + themePointStyle = MockStyles.point({ opacity: pointOpacity }); + geometryStateStyle = MockStyles.geometryState({ opacity: geoOpacity }); + (getColorFromVariant as jest.Mock).mockImplementation(() => { + const { length } = (getColorFromVariant as jest.Mock).mock.calls; + return length === 1 ? fillColor : strokeColor; + }); + }); + + it('should return correct fill opacity', () => { + const expected = fillColorOpacity * pointOpacity * geoOpacity; + expect(result.fill.color.opacity).toEqual(expected); + }); + + it('should return correct stroke opacity', () => { + const expected = strokeColorOpacity * pointOpacity * geoOpacity; + expect(result.stroke.color.opacity).toEqual(expected); + }); + }); + + describe('Overrides', () => { + const fillColorOpacity = 0.5; + const strokeColorOpacity = 0.25; + const pointOpacity = 0.6; + const geoOpacity = 0.75; + + beforeAll(() => { + setDefaults(); + overrides = MockStyles.point({ + visible: true, + stroke: `rgba(10,10,10,${strokeColorOpacity})`, + strokeWidth: 12, + fill: `rgba(10,10,10,${fillColorOpacity})`, + opacity: 0.77, + radius: 21, + }); + themePointStyle = MockStyles.point({ opacity: pointOpacity }); + geometryStateStyle = MockStyles.geometryState({ opacity: geoOpacity }); + (getColorFromVariant as jest.Mock).mockImplementation(() => { + const { length } = (getColorFromVariant as jest.Mock).mock.calls; + return length === 1 ? overrides.fill : overrides.stroke; + }); + }); + + it('should set radius', () => { + expect(result.radius).toBe(overrides.radius); + }); + + describe('colors', () => { + it('should call stringToRGB with values from getColorFromVariant', () => { + expect(stringToRGB).nthCalledWith(1, overrides.fill, expect.any(Function)); + expect(stringToRGB).nthCalledWith(2, overrides.stroke, expect.any(Function)); + }); + + it('should return fill with color', () => { + const { opacity, ...color } = stringToRGB(overrides.fill!); + expect(result.fill.color).toMatchObject(color); + }); + + it('should return stroke with color', () => { + const { opacity, ...color } = stringToRGB(overrides.stroke!); + expect(result.stroke.color).toMatchObject(color); + }); + + describe('opacity', () => { + it('should return correct fill opacity', () => { + const expected = fillColorOpacity * overrides.opacity! * geoOpacity; + expect(result.fill.color.opacity).toEqual(expected); + }); + + it('should return correct stroke opacity', () => { + const expected = strokeColorOpacity * overrides.opacity! * geoOpacity; + expect(result.stroke.color.opacity).toEqual(expected); + }); + }); + }); + }); + }); +}); diff --git a/src/chart_types/xy_chart/renderer/canvas/styles/point.ts b/src/chart_types/xy_chart/renderer/canvas/styles/point.ts index b1e5833d32..775ed077ad 100644 --- a/src/chart_types/xy_chart/renderer/canvas/styles/point.ts +++ b/src/chart_types/xy_chart/renderer/canvas/styles/point.ts @@ -17,9 +17,9 @@ * under the License. */ import { PointStyle, GeometryStateStyle } from '../../../../../utils/themes/theme'; -import { stringToRGB } from '../../../../partition_chart/layout/utils/d3_utils'; +import { stringToRGB, OpacityFn } from '../../../../partition_chart/layout/utils/d3_utils'; import { Fill, Stroke } from '../../../../../geoms/types'; -import { mergePartial } from '../../../../../utils/commons'; +import { mergePartial, getColorFromVariant } from '../../../../../utils/commons'; /** * Return the fill, stroke and radius styles for a point geometry. @@ -38,14 +38,14 @@ export function buildPointStyles( overrides?: Partial, ): { fill: Fill; stroke: Stroke; radius: number } { const pointStyle = mergePartial(themePointStyle, overrides); - const fillColor = stringToRGB(pointStyle.fill || baseColor); - fillColor.opacity = fillColor.opacity * pointStyle.opacity * geometryStateStyle.opacity; + const fillOpacity: OpacityFn = (opacity) => opacity * pointStyle.opacity * geometryStateStyle.opacity; + const fillColor = stringToRGB(getColorFromVariant(baseColor, pointStyle.fill), fillOpacity); const fill: Fill = { color: fillColor, }; - const strokeColor = stringToRGB(pointStyle.stroke || baseColor); - strokeColor.opacity = strokeColor.opacity * pointStyle.opacity * geometryStateStyle.opacity; + const strokeOpacity: OpacityFn = (opacity) => opacity * pointStyle.opacity * geometryStateStyle.opacity; + const strokeColor = stringToRGB(getColorFromVariant(baseColor, pointStyle.stroke), strokeOpacity); const stroke: Stroke = { color: strokeColor, width: pointStyle.strokeWidth, diff --git a/src/mocks/index.ts b/src/mocks/index.ts index fcd908eb41..b8a50aca7f 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -17,3 +17,4 @@ * under the License. */ export * from './series'; +export * from './theme'; diff --git a/src/mocks/theme.ts b/src/mocks/theme.ts new file mode 100644 index 0000000000..d0056acad1 --- /dev/null +++ b/src/mocks/theme.ts @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import { RecursivePartial, mergePartial } from '../utils/commons'; +import { + GeometryStateStyle, + RectBorderStyle, + RectStyle, + AreaStyle, + LineStyle, + PointStyle, +} from '../utils/themes/theme'; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +export class MockStyles { + static rect(partial: RecursivePartial = {}): RectStyle { + return mergePartial( + { + fill: 'blue', + opacity: 1, + }, + partial, + ); + } + + static rectBorder(partial: RecursivePartial = {}): RectBorderStyle { + return mergePartial( + { + visible: false, + stroke: 'blue', + strokeWidth: 1, + strokeOpacity: 1, + }, + partial, + ); + } + + static area(partial: RecursivePartial = {}): AreaStyle { + return mergePartial( + { + visible: true, + fill: 'blue', + opacity: 1, + }, + partial, + ); + } + + static line(partial: RecursivePartial = {}): LineStyle { + return mergePartial( + { + visible: true, + stroke: 'blue', + strokeWidth: 1, + opacity: 1, + dash: [1, 2, 1], + }, + partial, + ); + } + + static point(partial: RecursivePartial = {}): PointStyle { + return mergePartial( + { + visible: true, + stroke: 'blue', + strokeWidth: 1, + fill: 'blue', + opacity: 1, + radius: 10, + }, + partial, + ); + } + + static geometryState(partial: RecursivePartial = {}): GeometryStateStyle { + return mergePartial( + { + opacity: 1, + }, + partial, + ); + } +} diff --git a/src/utils/__mocks__/commons.ts b/src/utils/__mocks__/commons.ts new file mode 100644 index 0000000000..7186993cba --- /dev/null +++ b/src/utils/__mocks__/commons.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +const module = jest.requireActual('../commons.ts'); + +export const identity = jest.fn(module.identity); +export const compareByValueAsc = jest.fn(module.compareByValueAsc); +export const clamp = jest.fn(module.clamp); +export const getColorFromVariant = jest.fn(module.getColorFromVariant); +export const htmlIdGenerator = jest.fn(module.htmlIdGenerator); +export const getPartialValue = jest.fn(module.getPartialValue); +export const getAllKeys = jest.fn(module.getAllKeys); +export const hasPartialObjectToMerge = jest.fn(module.hasPartialObjectToMerge); +export const shallowClone = jest.fn(module.shallowClone); +export const mergePartial = jest.fn(module.mergePartial); +export const isNumberArray = jest.fn(module.isNumberArray); +export const getUniqueValues = jest.fn(module.getUniqueValues); diff --git a/src/utils/commons.test.ts b/src/utils/commons.test.ts index 1521459be5..e17d1b7ee5 100644 --- a/src/utils/commons.test.ts +++ b/src/utils/commons.test.ts @@ -26,6 +26,8 @@ import { getPartialValue, getAllKeys, shallowClone, + getColorFromVariant, + ColorVariant, } from './commons'; describe('commons utilities', () => { @@ -639,4 +641,24 @@ describe('commons utilities', () => { }); }); }); + + describe('#getColorFromVariant', () => { + const seriesColor = '#626825'; + it('should return seriesColor if color is undefined', () => { + expect(getColorFromVariant(seriesColor)).toBe(seriesColor); + }); + + it('should return seriesColor color if ColorVariant is Series', () => { + expect(getColorFromVariant(seriesColor, ColorVariant.Series)).toBe(seriesColor); + }); + + it('should return transparent if ColorVariant is None', () => { + expect(getColorFromVariant(seriesColor, ColorVariant.None)).toBe('transparent'); + }); + + it('should return color if Color is passed', () => { + const color = '#f61c48'; + expect(getColorFromVariant(seriesColor, color)).toBe(color); + }); + }); }); diff --git a/src/utils/commons.ts b/src/utils/commons.ts index 3838e64f8e..c86191d259 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -20,6 +20,23 @@ import { v1 as uuidV1 } from 'uuid'; import { $Values } from 'utility-types'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; +/** + * Color varients that are unique to `@elastic/charts`. These go beyond the standard + * static color allocations. + */ +export const ColorVariant = Object.freeze({ + /** + * Uses series color. Rather than setting a static color, this will use the + * default series color for a given series. + */ + Series: '__use__series__color__' as '__use__series__color__', + /** + * Uses empty color, similar to transparent. + */ + None: '__use__empty__color__' as '__use__empty__color__', +}); +export type ColorVariant = $Values; + export type Datum = any; // unknown; export type Rotation = 0 | 90 | -90 | 180; export type Rendering = 'canvas' | 'svg'; @@ -32,7 +49,6 @@ export const Position = Object.freeze({ Left: 'left' as 'left', Right: 'right' as 'right', }); - export type Position = $Values; /** @internal */ @@ -50,6 +66,22 @@ export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); } +/** + * Returns color given any color variant + * + * @internal */ +export function getColorFromVariant(seriesColor: Color, color?: Color | ColorVariant): Color { + if (color === ColorVariant.Series) { + return seriesColor; + } + + if (color === ColorVariant.None) { + return 'transparent'; + } + + return color || seriesColor; +} + /** * This function returns a function to generate ids. * This can be used to generate unique, but predictable ids to pair labels diff --git a/src/utils/themes/theme.ts b/src/utils/themes/theme.ts index b448750267..e1ec9b1810 100644 --- a/src/utils/themes/theme.ts +++ b/src/utils/themes/theme.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { mergePartial, RecursivePartial } from '../commons'; +import { mergePartial, RecursivePartial, Color, ColorVariant } from '../commons'; import { Margins } from '../dimensions'; import { LIGHT_THEME } from './light_theme'; @@ -28,7 +28,7 @@ export interface TextStyle { fontSize: number; fontFamily: string; fontStyle?: string; - fill: string; + fill: Color; padding: number; } @@ -58,9 +58,9 @@ export interface SharedGeometryStateStyle { unhighlighted: GeometryStateStyle; } -export interface StrokeStyle { +export interface StrokeStyle { /** The stroke color in hex, rgba, hsl */ - stroke: string; + stroke: C; /** The stroke width in pixel */ strokeWidth: number; } @@ -73,7 +73,7 @@ export interface StrokeDashArray { } export interface FillStyle { /** The fill color in hex, rgba, hsl */ - fill: string; + fill: Color; } export interface Opacity { /** The opacity value from 0 to 1 */ @@ -92,7 +92,7 @@ export interface AxisConfig { } export interface GridLineConfig { visible?: boolean; - stroke?: string; + stroke?: Color; strokeWidth?: number; opacity?: number; dash?: number[]; @@ -112,8 +112,8 @@ export interface ScalesConfig { histogramPadding: number; } export interface ColorConfig { - vizColors: string[]; - defaultVizColor: string; + vizColors: Color[]; + defaultVizColor: Color; } export interface LegendStyle { /** @@ -188,11 +188,11 @@ export interface PointStyle { /** is the point visible or hidden */ visible: boolean; /** a static stroke color if defined, if not it will use the color of the series */ - stroke?: string; + stroke?: Color | ColorVariant; /** the stroke width of the point */ strokeWidth: number; /** a static fill color if defined, if not it will use the color of the series */ - fill?: string; + fill?: Color | ColorVariant; /** the opacity of each point on the theme/series */ opacity: number; /** the radius of each point of the theme/series */ @@ -203,7 +203,7 @@ export interface LineStyle { /** is the line visible or hidden ? */ visible: boolean; /** a static stroke color if defined, if not it will use the color of the series */ - stroke?: string; + stroke?: Color | ColorVariant; /** the stroke width of the line */ strokeWidth: number; /** the opacity of each line on the theme/series */ @@ -216,7 +216,7 @@ export interface AreaStyle { /** is the area is visible or hidden ? */ visible: boolean; /** a static fill color if defined, if not it will use the color of the series */ - fill?: string; + fill?: Color | ColorVariant; /** the opacity of each area on the theme/series */ opacity: number; } @@ -225,9 +225,9 @@ export interface ArcStyle { /** is the arc is visible or hidden ? */ visible: boolean; /** a static fill color if defined, if not it will use the color of the series */ - fill?: string; + fill?: Color | ColorVariant; /** a static stroke color if defined, if not it will use the color of the series */ - stroke?: string; + stroke?: Color | ColorVariant; /** the stroke width of the line */ strokeWidth: number; /** the opacity of each arc on the theme/series */ @@ -236,7 +236,7 @@ export interface ArcStyle { export interface RectStyle { /** a static fill color if defined, if not it will use the color of the series */ - fill?: string; + fill?: Color | ColorVariant; /** the opacity of each rect on the theme/series */ opacity: number; } @@ -249,7 +249,7 @@ export interface RectBorderStyle { /** * Border stroke color */ - stroke?: string; + stroke?: Color | ColorVariant; /** * Border stroke width */ diff --git a/stories/stylings/17_bar_series_color_variant.tsx b/stories/stylings/17_bar_series_color_variant.tsx new file mode 100644 index 0000000000..52b502ae54 --- /dev/null +++ b/stories/stylings/17_bar_series_color_variant.tsx @@ -0,0 +1,87 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import React from 'react'; +import { select, color } from '@storybook/addon-knobs'; + +import { Axis, BarSeries, Chart, Position, ScaleType, Settings, PartialTheme } from '../../src'; +import * as TestDatasets from '../../src/utils/data_samples/test_dataset'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; +import { ColorVariant } from '../../src/utils/commons'; + +export const example = () => { + const fillOption = select( + 'fillColor', + { + None: ColorVariant.None, + Series: ColorVariant.Series, + Custom: 'custom', + }, + ColorVariant.None, + ); + const fillColor = color('custom fill color', 'aquamarine'); + const fill = fillOption === 'custom' ? fillColor : fillOption; + const strokeOption = select( + 'strokeColor', + { + None: ColorVariant.None, + Series: ColorVariant.Series, + Custom: 'custom', + }, + ColorVariant.Series, + ); + const strokeColor = color('custom stroke color', 'aquamarine'); + const stroke = strokeOption === 'custom' ? strokeColor : strokeOption; + const customTheme: PartialTheme = { + barSeriesStyle: { + rect: { + fill, + }, + rectBorder: { + visible: true, + strokeWidth: 10, + stroke, + }, + }, + }; + + return ( + + + + Number(d).toFixed(2)} /> + + + + ); +}; + +// storybook configuration +example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; diff --git a/stories/stylings/18_line_series_color_variant.tsx b/stories/stylings/18_line_series_color_variant.tsx new file mode 100644 index 0000000000..9313332d22 --- /dev/null +++ b/stories/stylings/18_line_series_color_variant.tsx @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import React from 'react'; + +import { Axis, LineSeries, Chart, Position, ScaleType, Settings, PartialTheme } from '../../src'; +import * as TestDatasets from '../../src/utils/data_samples/test_dataset'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; +import { ColorVariant } from '../../src/utils/commons'; + +export const example = () => { + const customTheme: PartialTheme = { + lineSeriesStyle: { + point: { + radius: 10, + fill: ColorVariant.Series, + stroke: ColorVariant.None, + }, + }, + }; + + return ( + + + + Number(d).toFixed(2)} /> + + + + ); +}; + +// storybook configuration +example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; diff --git a/stories/stylings/19_area_series_color_variant.tsx b/stories/stylings/19_area_series_color_variant.tsx new file mode 100644 index 0000000000..43903bf7c8 --- /dev/null +++ b/stories/stylings/19_area_series_color_variant.tsx @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ + +import React from 'react'; + +import { Axis, AreaSeries, Chart, Position, ScaleType, Settings, PartialTheme } from '../../src'; +import * as TestDatasets from '../../src/utils/data_samples/test_dataset'; +import { SB_SOURCE_PANEL } from '../utils/storybook'; +import { ColorVariant } from '../../src/utils/commons'; + +export const example = () => { + const customTheme: PartialTheme = { + areaSeriesStyle: { + point: { + visible: true, + radius: 10, + fill: ColorVariant.Series, + stroke: ColorVariant.None, + opacity: 0.5, + }, + area: { + opacity: 0.2, + }, + line: { + visible: false, + }, + }, + }; + + return ( + + + + Number(d).toFixed(2)} /> + + + + ); +}; + +// storybook configuration +example.story = { + parameters: { + options: { selectedPanel: SB_SOURCE_PANEL }, + }, +}; diff --git a/stories/stylings/stylings.stories.tsx b/stories/stylings/stylings.stories.tsx index a127587b26..ec7edca051 100644 --- a/stories/stylings/stylings.stories.tsx +++ b/stories/stylings/stylings.stories.tsx @@ -43,3 +43,6 @@ export { example as customSeriesNameConfig } from './13_custom_series_name_confi export { example as customSeriesNameFormatting } from './14_custom_series_name_formatting'; export { example as tickLabelPaddingBothPropAndTheme } from './15_tick_label'; export { example as styleAccessorOverrides } from './16_style_accessor'; +export { example as barSeriesColorVariant } from './17_bar_series_color_variant'; +export { example as lineSeriesColorVariant } from './18_line_series_color_variant'; +export { example as areaSeriesColorVariant } from './19_area_series_color_variant'; diff --git a/tsconfig.lib.json b/tsconfig.lib.json index b997b4ec4f..0f24550fa5 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -5,5 +5,5 @@ }, "extends": "./tsconfig", "include": ["src/**/*"], - "exclude": ["**/*.test.*"] + "exclude": ["**/*.test.*", "**/__mocks__"] }