From 6bf9a69b12d3075330a5728b7bdb4443e6244985 Mon Sep 17 00:00:00 2001 From: Robert Monfera Date: Mon, 2 Mar 2020 16:19:47 +0100 Subject: [PATCH] feat(partition): add tooltip (#544) This commit moves the `Datum`, `Rotation`, `Position` and `Color` into `utils/commons`. It decouples the legend from axis position and moves the `scales` to `utils/scales`. It decouples the tooltip component from the XY chart to allow Partition charts and other chart to use it. close #246 Co-authored-by: Marco Vettorello --- ...-tooltip-visually-looks-correct-1-snap.png | Bin 0 -> 54492 bytes .../layout/types/viewmodel_types.ts | 13 +- .../layout/utils/group_by_rollup.ts | 7 +- .../layout/viewmodel/viewmodel.ts | 26 ++- .../renderer/canvas/partition.tsx | 40 ++++- .../partition_chart/state/chart_state.tsx | 31 +++- .../state/selectors/geometries.ts | 4 +- .../state/selectors/is_tooltip_visible.ts | 20 +++ .../state/selectors/scenegraph.ts | 11 +- .../state/selectors/tooltip.ts | 74 ++++++++ .../crosshair/crosshair_utils.test.ts | 160 +++++++----------- .../state/selectors/get_tooltip_type.ts | 16 +- .../state/selectors/is_tooltip_visible.ts | 3 +- src/chart_types/xy_chart/tooltip/tooltip.ts | 10 +- src/components/tooltip/index.tsx | 7 +- src/components/tooltip/utils.ts | 12 +- src/specs/settings.tsx | 16 +- 17 files changed, 286 insertions(+), 164 deletions(-) create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png create mode 100644 src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts create mode 100644 src/chart_types/partition_chart/state/selectors/tooltip.ts diff --git a/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png b/integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-sunburst-multiple-datums-for-tooltip-visually-looks-correct-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..92ff8661290125b438eaef4cf1a53d6ce36fc896 GIT binary patch literal 54492 zcmeFY^;cAH+%*h}pi%;V4(%s$NO6LGlQlq3ucZ1~6DP6+Q4Fe3#d-&e# ze%`g7f8l-S7uGtQnRBk|d}4q0-Y4plvMlCnlGjK`NSN|+Qff#@&vucJkgZ>$0!Q}# zSnLCTkloZ|C6LNT$$yqbzv5T-?(2_t)ShO? zBzh9O^dw4ut^XSFMpgCxl8^1ftqR6zF|By}LCTk7a%iGr_3-F$bQpnvQbS$P$efqh zP69y`pw`9g?CT>6@G`>xl4rHTB9<%i=_5B;x$lQW{q#}$F46qop(Ir~LLA^D&;5G$ zzyHX~P#H)8AKC%N{FtXxXovW&Oz<^esZxPQVy3Bl^REq);Pd(HW4099SiEqF^K(I9m=y?M5$faIO=9_>&`#>U}3ZMnlmcC(pI9F;}X; zh8IP2#((>k;N~r%1Lk^f)H}m!zTmX9*#n{y%Nsx5!t-zf%#05qmg9|OO3HfujwWV0 z3DYR16H;{`K5|w*x#-BhPcff(&*&8MGrx!7O{MkZUuudFAGh^k$Ub*axgxFoo`?S$ zwPfQs06hxhnW06`afq9(bhq`e!g2};6tHo#WTB`-ApDkPdN)%OYOen0>PoQ z${NC#b%J+qBj^uV)h01J6BG`psrd?kT2W0Jb{*bm$H>AL8^|!|g$KJ3%YWV#y&H}{ zg?E1-rc%4>)vhE%6Z2j{4Ev+h2GS4UJ8GC?A=4~kt|W_hm2;c0iA`3+ouh{@YGq+C ze}Z_p4X{6t()`)Ci&r^YmcY9Hdr*gs?4<^W6XhWzxnb;-9Y&+6BN3XA>xblqg_B)z z(Lbv}c_ zZas^bX=BYY4gf1i(Fy^ju%Buqj#NS6N8{Xfh}YotWu|rj_HIH=VAWcPtbT@PrQj}V zMAppfeQIi;kh;pb!ot1D0<||zSM+cQ*iJkj-_2L>SMavFG$;-o5lB{eS!^Z}RL9$tvZe4X)sV}SzXmN!4fgS1p;BZI+K6j5{cp$Lc#m?Bid(V$RhrBTJ zAAOJvCNLO(T}bWvNnMUGF+|M-S&9b4Q`B^KZ1H&vn;-{0kJ5eDKX$}MKx7U}+w*D9 z@-KJGl%%kPSyG#NU?3HcVPSA!f-e8`_IJB=*6-$#X|o7xDJfeBGqC*eNEy7gZ8xiy z)=P!j_6Qn}e=$XP@DhlRUvR9w^La5nHQKAslhPYM5+K{^x~AUiiIvZ8s4U?M-I19v zR+3}9S%{$&7VgtdzUM)Wj={i#_Fj#6^p3GC@UVlXA9(E?w1!3OmTGb3mtKic_Fla8J&|j z;N+8R&+j4D(^l{Qdknq9ooR&UD&w`06qfOSmu6<>j=g5?i|dZoX_Bz9S4r9W5CJ0i zk{oU80q*2~vCoWl){BRcn6w~Pe|Qj>8GsW*WcUy_G6_mw897LNJ1e z)MW>IG=A1o;@hvXHim~{P>zSg2{=l3)U1$T=fYw|q@2MgIv|c1+npLOX6at<)ES>j zK`27-EDnKfni=J3DHHYDF+1&5r>Iz7tF{SKc|^yfq_M&Lx70#r8MJr0x)1Y*SC0`l zpvXCCs}?&gPi-Cg89X5T2+_4c(x>r% zb@l2~$Rl@p{H*+QGKC)9KL+3T6GRJ}uEA7V(90L7-Qk+EoDgUUG9Xmpl#0d^ugI{l zUf{y%YQe0XZ+x2?=)?)yU_T!)_&4?ND>?zRua51s-;}c5mHP_hDAdT6ep#+kLUFrI z!=!Ibbb*%Eb@6(S+V$V3mY7J|Htpd`+pw`VuNhajbE;8<@h$=sFj_z0Xi;UE^V256 zVC)wzTiMlo+y@25ssK&(m@R{~-%yqtg)EKk8#;WkqqVMRs4hR5U$JCSAiV+ zw2lI|yJYr%DfLHN7~TVQ;7jto8wO6|LM~AG<{lW{@ z6l3Ei)#?aP5Ki9G8YY{%jlCD$9P(`UUa(jp1XcBa;i=1yB=gmfCrr$*4a|n_Gz<|V zJWAUmT*rqDDvwS0^j}hnZgB4~M^JU!$5zeN4s$+8gZFG<=zUkUVxoU6u+d%;l4y>W zF8&eS^#D&k%S29F$S<6|H&vphZZRV^(@=Pmn6IX0@4aPYV)=3EbD-b)#l*a*N0?!a z*hCd%WZXzF-)a^iCnd$K{`7!S)~UN|9^)B*C}wMOjac0&Qf!(`mq^hz{aTimN|JF& zG}%y8EmTpBQRLq9YVS_8(!Qc-^`BZ9Qx6)fAUm5Ou?$QmQ@3HyT!-~y6fNV9_vTY4 z@dD76mI;6+D*-fvp;em~VsDFOY*EieR<|}YVN%##-#N8Lxj*_(TaBlr`u+vZeY)D7 zfqc|j4S4AK!0g4jdCGS)95x;_i}#NcU%4oFKqpbsdw#Uk)W7p%xRg>t&26R4u$46dHNwFcW)q5%?=Va~DHD<4i zsCF5`tP$rKzUe8cKF;C?`bf=yjhUIhHfY3Z82-7Tgk?(hHlMFj(EgXwr!o#|H88NA z{FUTnqQU@5s~+bR_HXlBDdAuW521am2rWnX^ISxW@vKo#j!$NHI3-5K9lf%yq#)u5 z>BT;q<{{_wx{KBvE5y=!d(0&ZiFfzYqtS^n*=}O$i|qKSSK)hpywtA0em{Ci;t0{E zq>!{{zS+Jm!XL-x2hx+*(T>X|If67IiH#FzB<0V42X0)kn>SSU+uE-+Rl3-?>VLf1 z3=*AT<8EvY*OA(b0(ybkwm&5`PIt^$*uEcQHKjRUmGd*_&DD@a#~QNk)X$%bgt7#? zto{`6r-_mMC>`fK3;@%8OEMGg!G7FwoyPl6tv3Ti$i}C9AfY@);TT>^{%?%Fc)xTW zVmmkleNf|J31lDPNlqg+Yzsh4@uqGNxBK>WSBoxCn7CY$BhQS+;mIcjQ9QPYjVe{V z@ap&(DaZB{ZGVfCOuNmnQ*okbW$QF9N4HWyWxm=dYPcPg9)&fB?QA6&y_qKeRPe-> zQ!$0g*fP9|ZxW}PI;3?8h@K~!at3!?G*6UJU{Bgv-v@pqetx`sY}-eOQ|$H>6xpxR zh}$1>v*vjafmCGjSWV~v7$jj2qJ6J}T$*k;q3DR=f7S^7QDR^IAdAFct=hQ4c|G#G zroL8a$+CZp?HLonn#9!&G5E}xdaj5sG){5<(VX4ur^zJ)NH*I72g3%-1E-iYm87du+xg}wn z2Z4G(EhzwMNiklZ!0R0~v&_`Y{MS23$9KKc_CS zd|tMz(&`znqaM5-oFi#J+7A1-Q%>fQrr4@)^ggElp$K?Jx1VjI*6OsVH_GMXuPX|U z9JJSY5GD6|_>c2b$Qd3BkN4}nnHVs`lc;3oU%+>sVC8Kbslaf~+Svu+9*&^KJl5ON zyg`LLwCY~GfY&o$Cwz(`4`k7x2rzY0vORSDdk|1gInA*zTK>?NRQ1jg;!Yp`b#Dyx zPI%PEU*#7w&Fp3!Z5^RXMjk38>gzaIYjM6B;3vPGmr}rB6J8v?`P&^Mx&6RTc`wFQ zq5@|p2q|~BUYhA*6ZjY~X0@UP%D&Y*@0g=1iIT4E;e?w(nv7_ZPQbTusqepC8Vhkc zHbR@j!l}B+CP{U(E^d+9?b7d3pCJAkS!kH&~4(u?a^PRcb^>0uyel`_*zZr6@p(5gyYd10mBqia$jv4XN#$9q? z7`vOCe8sc@fTL}_fI*4YDP}AU@_S^{m=7)qwtgk0h%vQ-*yXQWhnaV)Ngj3W`F&`n z*sct$8q0O^_s1R_nj`3%9p-yms%YQfQapCX1S)IRI{#i_WM4m$*=}3^(*!-n_}9(p z_5UdtQTNmJO-5JA_CnqZ-cMCNiJiKR9hXb=(z&rcg6dXPe#KMViOZb#_sglZui0p` z!+oD>t8Q-f+^dM3izPjWdfL@^FzBxF5weObftSltD&7kH)D&gS9Z@v?sQR{U^WDgU zuQ7q!*5)q0e10Jd_SL`k4t{T2cC0IfQW($C&`Op-Cs%K& z6L0Kj0N^xVyEdNPYz9|=Xo|5u(;I`t3)k%7W(JIUzO>K@`T zz(`&9@er8g>;@GuAH8e09N2jZo6p7h-}^R83ue6(igRW4B_lAN3?+h+8}a>_4f9$p zZK<|a_$|#kKmZ*uFxD2Da=xYko`!Mn`ELeRMTT2_|DfIjb{^0#oo85tU1-=X7|nww z{hSfrJ)9Q?kP#MKI2hs*BdLR&j1NGJ-`7!-=Mx~Pkr|J9dUyV;XohFadH&*_#sOCXVEFN?gca*Ya!DyHwp1?qCJ?#QM({>cIPsJyMNp%A!y>9Wt4#ZJ z!rEA<&|}gYr&f~9%&MrEk3p`E%naG7U7=N6>}}jVDTD+LeoHD9N_i@)NF%y}$*DwJ z!_n|PMV!I<64P)Af;2;7(?5USVUjFtY+uLT{?05(u0JGyaMvwBGSRMj~4+&9+O&r^w^#2-8#}^>F~75 zCR|@?;SN4SaZKVU-i&fvR<{;At zky`!xm+U}w-v|rw7x;PeW1bPZ9)cKT>WABmkFOuVC6}(D!wx13^=n7sR+IJFBP~uc zIyHlNF6xb`)&b-4^HV>ef()g*)k?y*5b6hRtjM#tl`TEeJmVObB&#_ zC3MLpUoBw)BcqQx#humvM*VoKr)Jg;XY=eAc3kcf-X1k`d=qhGvg~e-{(Yje9Y%Ef zStzKvB>DXtHTAVGQ`I};6DFI{(b4DsetrOMyF&GS+Ks@GG}yE4`Qkz-Kx%}IBBaeB zKIJBZUVd7g*}PPvII=JL2dix+{}w7XAbs=1orFBdroS>X8_tAr+elqS9`|EEzcND7bfzj8f0 zds3NpB9n<{X=bGhzabp(_V(n;f$lRzj!8^Wha8ixr1;N z$EANyMJk<#Jx+!b0vlJDf))&slzY2`;QBb<8N=Z!a)B=iS$U zDas;rIZ--@@o6i;%b(XIy0P&B{8fxT#q_tU1CR3ZJ6^acm|gEe*<{CbEL|^9NeC9L1x2`cg9l#JRMzr2r_!ZQ+SdIxSwuZJZ6O;Dr#h{bXH`~8;Hl%e!Cj|^rQa}8 zghv*fU3qgljoOLM(hI@3Yip_KLie zImEjhz|;tbiClR+Qw&j%oG-nIgNM!H$`%k{X8>!n5+tJFqMF;gF%e{evy@T6Q^WfD z%*&{kb-6}Jr?8Khmo^N%B3}7>$FIxta@C&YVj!lwR0X4LtkQXJyV(jyNbadGgXYti zZ@~0Qu`UmERIKPZa|6_JPz*UL@iEvu1Iv-qO0H{#k4@B1J@EZ1g=+RBE+lS0fFj_B ztx>yC$WcTRZudEpi?Sfoi=pcN{>pUDdPhzB!==^S$6hQQCDvTJD4&k)z(m1B>%Xtr zZ`=}eAXn~3PKg;NT>I)^Aqd@(ihRg7$Cu-m&M`@B;RWBk9?T=e@Qh~|Kn7?FC|^%H zR$F-Y&AF~fBKjQgz!1(!v%}Wkw{!DEHF~-jtzA?J8P?#a)J{^bmN{f1=PF5{(r3M6 zh3xT|ghsSn(2+YAcds!lK267lhdFE?eMj=fboS`+69b;OZoe}A-#bWln%A-S+%lqA znF6214-IYmsjDRej|G0X7Q3#HrA-AU4VuWJdM=)+EpCmt2yMn{Jo^wc&4OS*kGrqV z5;-2pH<64H;(={!PmkyyR=xDUR7v~lG2R+8;)4*h*EXGNrN&8>)QNJ!U1@~U&_LG* zq0fLDRL?*Qsc>;7^WNf&(CWrxvL zzCU(ejiE8j$7Wh)S-gWKlSi%xbXX(8I<411>{Y+k{*pciu$$$i9Y_py(Vf)#bnCG< zDz(c-Kr0(9&?o@WEc+NkRwl(v?Wa_9LtQ;!tqp{U$BeOtP(!C@rb@;XLHXOY1$1dWPR$l zU#|de`%wL$k4`(l`l@`pjJj>1u!7$&M!oi!km+u%@C+~o;so2tF7=3i+!Cgyebd%? zmC!QI;Ppl2Ht%;JX>|+N(XUx~g4MPo9Cq%;P0mdRa5Yj`yKvWWq=@3)?}fj zE{K~KCP~>rZ}bx&u|t8`tXj*qB-&8@iKn|!3$+VO>{IHf(XLv883l2`D!`hh7e}`z zJ4Qo|2fyD~K`5zdrl^Br#?mV;9|Z#_DVjQxW7t(-u{G+}$#}W>bCveRLkyIEOBsP}4pQ~B9)>xSH?)>0V>FH^z04ADtL2mgP}F{UtM>*- zXJS;sPsrFyuY^j9rQzky(=hziNWtRp3&r2zi_38?eoQt|DW6o@29Uqatr$Dk=S*3c zOP+2WLR+WSj=nh=o(j37=P?mWn^!z#v>}kuzM__mcdcCpjyyNnt}-3!LQ8?7%~@bs zt#$|Ky%cqL+9T=O=A))~eP*Dl{$~9vnCV9%Eu0M_??PAUpo287zCazmoySY96a;=u z=Pw)_nYg*@eYNG&2Cog(5A*>r%_cdIAmMv2tkt+P9qs`r5x^K*5puOi_z*3*$~mRK zJVV;Wv<+OE6x9|PJDKnBT+OH*;yOy$HN%l)#ej$&B|`N0PSWt{`|k3@h~YWfyer!q zq_!O?GE!KWPbge$tEo^yJGISfalFYcSg`_QqPD4bn^JcF{Hl02p`p(hgV)-2qS$Ff zoFw(*rO?@V&*c}59S^k;%G>{?f=2s1|Gau%l0)!44q+d=W-6&U)f3Alo`I+v`)5ba z?S`ug%EPHW&h{QoI`;R)tb3JDd$b?0^>E9mFE|SQJ5un1+BHG-soiMPniBCl%eBIg!Vnq~lj zYD?hT9d^>sqx+|@o_jD1^1d>{vWm4Ra zHZ=VMMbx5xO2vjli8M#Td-43M^}6V=1w+wIde83>zABW5Qx}4n#v|;CZ*b=Oxchbn z32^3VfQT0LHkvL*#kLd|5=e35__(PhzUa}S5a0_Sd=&%LhyZRi8p2h|AJVfo-$^v2 zu=M}iZ`>#%kiSY9J2fBQg&5?l`i3GblFa&P76ubhZVhYHY3`|n6cPxuYSe6s`e9WG z-NxS>9_aH;LA8vUBM+!9fSDrh8WelYEmX1tpyDSiX(Ed)zgmC3_+gS@WI`?dQb14=0Z3~#v(v^kxNsI}fQq@5Nazs8 zqo0t1PO(Y}KNg{3h61DcX==_ z3`JPzH#P6Wl$WG4epxP>k+Ap*olVV4jz#&82*Jnw=ll6KqebCgv>1QH$c+F}gIt`0 z{&9_ux@&kI{*ZwtYel+~AJ8`BdbS7crdYz<7sC@dWVCDFRUHC=Nm)oNY$f39$BZ|5 zs`0&1Mfsd(y)Pj_Sywa0N2O{x{_Rws={G>3S%Ec@6ZDb*r#d3CSZZW{b+^hM61X?+ z6Rb3_)#JbYV*d};AL0hV+Jl^QU6*cLNEL^7@>XYp3Dzzq+vrUI(kkD_z#+3!6v-N8 z0-(zPBYv)Vj0rI7*1>Tr5CD+@kZwi$?=#=O0o`Rn@xiFxN83?O;;8zjQ!tc>zp*{P zi|}NxHrPvVj(Lf`ehb3?p~Ug$Yi``h%00Uyd6t)Nxyb-6#4+Tr1v53@QzzxajiZ0G zeS!bKUVz7?ou`>%pr~NHG)$5apLLP?P4K6FY7!+BkeTtodNd~`i@;j~5?2z2-&p_@ zM0v(}y%>E-I2OXR+KY8<>kID0q6F=!wGO}l@}2$KE?B<8Czkw(>*RF=jNR*M;}!OQ zoO|z-3#W6}7N*uTS5?01@0p-@rc{IX4>-&@c zYCtS+$OVc95V(Ci4J?}b2Qy}4&s9ynfE^@#!=)Qbb8$1j7i7pZ{#v(c@Q+wEv(!VX zvlqNni%`C2eMVJTDK{dwWVpPSsiOoCgkdEV6@rEXf2;LcHjeO z1Iv3!8M;Cm&(BFKw-1?E()=3P$vmZki@&ibf5Z}3b4ufzc8gR3=&qIf7{z=PZqn|t zoY$s0dM!6fn+duZXm;Clz6%HqPxKhPa?Fz>BmJ`(+J^_NN1k8{oda*oFQgFzPFEiT zyNgs=1h=t~`OAVg)%TC|lEPpCy6!d8X8?^~Fqsf}plO*pa#k=Pwz=Fo?}Cb@{L=s} z0?-xRI4~yi(rTc$O#K3}kIh*nffTutMLq58?V?g8{Jt%v#mVlBBLY$ixE%;k-q|XZ zzInf+DvnwZz(?G0W|l+g*m*skjsGr^wuCKHvZSLrHvP1gq)zY>T_`kx?>%?U>@IVF z#wF7(xcXN(a-bUWOS1voBo8xYRKMeWd0wN2=S!faXQs{vs6QK3z+qSrZ zV#m<1oBoHBAT?v1b2NSKBZDo z$ZOPTjs99pIP$A}K$9#v0@SB!yHqZZ`8Wn@G!g+v@9Qu|qkz<B5Ee50<#K7P zo#uT+9?MOknThuU;dD@*>jdO^?b1)E?~p?Gz8Ay8q5FZI%Yg+JW~N^97HMI7rnbe~ zcXT7BCw2VfVCBEDTLGI~*CTr>uSF0Ig7BFhXe!qyO=E4&8LPoXv=#Y{)X(9bvf4jp z+;Qe#0aEOIv0M)zRo68HtKMQ^5gOqL8-KNRQ0UtZJSe~-kFW0^uCjJZ?_J^u*b4ZJ z;|+b7H9HOMIc{n8-EBkDKRE2#67muPluqyWjOe3R2EnJ@VgyR(Hd90Er6SMlbqR`$ z$uZjd=y}i#+r*R=GJ5B|gp=|AEOC@zuSk<(KDI2sK+mXe074r}Pm?57MWm8>stMg6 zOT1U}saq#67)Y5Hsi~FfmgOHu84q@J(b($!`{h zuf`d4p8;&QTgS;RpZhGOQhS=^ZGruwam(mKyeN&3 z_|^=EOaM@y6aj*=U%71Tf{~*l`}O9ZPNpSyJ5%8vfZcyy>HQN5Ibt^1DQ3dBwIIQM zEQl=$%#m5V#G)~L$>X4q1z<&&Ih+sGHEi|vT(OjRux_usD?RiwanR+1c&f{x2i1bd z%SxBFvB))?tusB(@k_l=G5jW8-Zz z0^^gW?bSwzE6BgXb<^);{N1*}+gu;?h+GLsg*eCdeZx@0=M1N2zHC%X)$tM{X6s=IZY6wvFE38_E;?7@5&*h^9Y z?<_x)NWEi)?XCS7!1clFWCL#z;knwYA)+ygeuE*g&5q2?%>q7M*q`#=ZQ@iBRZ}-pK$Q*yt;Q3U? zT^T`pMleZi)WLbvYy{n^51Ef|0{W5LiC9d%>|E)0y$Mrpgd*6Ps*wxOr}?*6@c)fG zx_JqsU4xq;#yl6x(c|_XO+jaPL2J&Fm{>XJM&d9W`Bse5tX;QrA*{^Qmi$mwAUFE3 zg30AHhP?h(HuXBmd`|vBs8Z^G_OFF=yQT-uI3%!9c4;%YlFQ3+yPM}Q`gEY#V)(4A)OWEw>b5O_N~&r##ei=$N>PKf3^NUx z@)!AI{kghwUqm^`#>Ki0?lPSp;h6(OGP$tuRnA($cK0F$kDc%I_T}Lf(KmY9cyWM@ zGiZ@*+NmC|PYB#qk^BfD0TfES+wVdnbVQ+$tZ##8$1+STN}}RWt~>a##aqJ9DBT8P zh?`HE2|Lf7{2E7G(sVG0Eo*$jdbd^RpI!de!8e0Ed;x3&PbQ>|via-58bApIya?L- z1?yEKE!tWBA>B}LQ)#x7^<;bQv8dP2u}~Ze08%!dRlPfP?Ou-p*nJ{P9-os@lG zcd@dJ?;Yr7|bHOs8z(l6$3d;Ha8^QwfM6W=1aot(BHKDfUUu)m28C zDKB4qFBIfBZSFZ~!<6N(`x>B~L)8Ei-ZSKvVXV0mQYYL`?3}m#|FCoO3ksE%Dd59D zY#n0&03(Xof-+TDIu!c2F;fP0>Vs=WYX*j^BU4Bh>bD}S+-kjER$X# z>E08Hf;#FtYIhI;>S|&O=Y%DEnuj7mZU(AvT;rlMV%Re2-(AfVo;O;m4^CawEmrV#gPOsmPbl4Go*e~QL&iA-2M3d;A}3$V#&kqRX0RQqbg+4 zzNt~Kn9~CczuO?zP?asJ!*DyDb+-bjhRNb9fts$M~9h7f1 zARyFS*@ANBe7A7&(sKm@xM0MDAWm0H^s$iKzSTPyms~E)@wo#x$p^-v+}Tf=mjU*g zqaW=v6SJ*_Z01_mPrtm~5&dI2aWz;mR=63SHACQlrK10Sp*BPs7@zet=?wE^%HFkv zNi$C64k5*?h*y2TJSsO5UzEQ~o2GR^wqMEE2x==)ua$pgAmyo?;*L|S$<1o)zI6J5 zJ|4_MQEdc&EtKviqFP5AICh)R!vG5|5^JV z-l8wEL&(7()M=da)Xzr#zQfS3t9mWjNXVSGT5crRvklWobaTBW%1ISS+N~ZV!8t%T z74Uz^hxQfdS=rlU{W49npWR+=>QKrToam}<0~Bquh?cA?IMR2ecWM}J;s~9a`x(3+ zsRWYYLiqxfjM`R!E3oSP^g4f7)MKs*55Ll+WrP$^O@&8>cL{GM@^~K9FUtOTNR{Rxc2m14R)HLK5YmTwD=+7G#3$2d=rN6dr zg7;W6O}+P`vyEKzDzOZ&EH-at#WjMEK|Ii0U@80&^SqAe#F%Vfci*stp6E*M>Py;? z#>mJrC}&7X)jxso&pl?A9iit-bx{Ei8(D9+#x7o_5zlNM-3h`ip~Swn%&_+ODvNJp zOjh}md9yhjCV*a8?0$H}=@+i}pxnNHWgiTz=a&m5 zDPsr>SgSK78K=6i-n-)^m+`)h`{H3IO5J2!e_JC!`Wh3v#<})aV&uVlz$p0J*+9GP z_vQ1AjL#sa2aZ7-arw>a68luvl?(l~<2HOc5$IMs8n+8s%sdaQ_1r&nbt$!77HUw} zm3Pebq5N8!gqS!1atzlBdM%h@N4|!@)zTAhr!6_U$<9F`_ZIDYWN18BUta#kb!h+uvr_8Lup9J1NnIEAXQ|f^^8xZelt&fHG6l0FThE&{9Y6L!={bsFvQ__){GS z`WjzgL5$G8ZtF`mlIhh`XNE6x4RL{g)5CfcOc=dBm(rQp>m9gUsWTm^Xw}3dCB4P{Iie3+3d^D zONTssj#hCr*(-KJfX>R^A>0%Eg~{Af2QVC;?hWB=jkz?4JwAb61_~y#lPD&#c?LPa z%3eg73P0WH;f;F|bAJ}mPtNV<)gXHxm$|NZ^HpnATz8f(C-NWqGHg*e&C#loqeYeq z07+_p&B`@xLMw2s!t6z-`Eu*et%RJ^B@Zwi755M^E{}AHPTM4#oz8YxBto{~T|6fG zgrhBtDJ_p#oyS4y(c8qV=nu|LsLDtW@CwepKLuKaiK(bD1b>w&@9JiYPAA<=a=i1C z0X}W%ss$tZbwE3@-h}RCVRrX5;bJRXCOKInhNS}2Qcd|wOXPHB4b%F5;i!g-lWs-k z0hd{H*Q_}IE+R5YS|KAp7F;+&mg8TF5nNsUT*UmS$vZHtJGUMq(o-6`5GK_S9D1q_2B6rE3cRK19In( zRlc03@2>MpP0nfj&-#jp;=q_<>`xcG7<3NI)1{8?*n8%xWnhWe;)&7`%2-2bzUPOO z`Lk=tfHKb%LrAa8XsP4{$fRqnF}UFSi8&6lk7SU!F~Aj|EX)^%{5)hho4hE$r${tI zd)fnDC|TjZP?{$VZRclemQYsA{EHESSIxMJVgY!^R*Cq_!<@^(L6Nlw(6dh0x8u_J z-2b!E`9oa9v{0^B!u%9$@tN>@mnxHMTyT6g8GURR`!bp9tT%9!)>2cat@^TXCS9P& zRXXthK)U+6(^B1C2lO4J{Tcee96p-%WrrxPP2PH)_P?;ml@Dk2SBuYXz`$DpfLv=_ zu;{sDIVA(hqK`0qxi9*sa2bbxMEHYdUe91w-*Pkewrh9zaR9X)=7bVZ1a9y-P!`LN zRU_r9fmaL?709fP7YwRNv@t}f`l~`nG*^uNuY$dhP|lMQe8PkfkC%{ z!d6(@Q{tKFy=1>!zs^6a6uOt3-1AsNqP@XePkgPpxost)nKJL3J2BXCOM$|&eHEgh zslMa?(bQs;zKp8vXV^E#$W2La%;eGFuXR!LpO*mg-Qg?>-?|*g6osc+@(QI?VfrM4 zneFtu02Hh*f|=S|Kc; z0|Av*^ya-y%I#f99$&Kz;Jn;%PF9L>#3yvqota4y(jqx)=Pe!K3!T*nR27AB`!9ri z93hV}LwosRveE6N!)JYQu`ysAuXcFv6ASEiH&YhAP{3o`dO!G8T2%ZnkV5#PPFE<< zqdc%hoQ5N(dg>O|i^|~9K@J$%KRxw;f%wn=GBO09n>%K>Y*d?@(@r(^QPBDA2B9V5%)ZuSyQaKL`w>u@X}b zH0K%17C>FBp8r&x(b(}Vi1GH{v|{1R4QT(X9_p9jC&_~r?mUu770Ri9%$A_#?=PpWH1^4%9&}mzm9*rBf&dPz9SEJkxT!29bUDHUx|AX;WYk z`J8?-S>5p%&S$%P`)E6pBWoE(`CPy;np+fNLBTL9$P(gJO%d=zK(6~eWOYUy_iDFI z(usBFs;2>ObO0`qlE9Eko+dj{Wl^D&z~^59zs=i?+qWCDMndg2Xfc-oxNhL>+S->E z67Mi`Gx4TRfoY7?jjFZp;G#E+lGQ(okRv_Pfv$bmI&s=@F~Dg!Aj5->g^7vdUlq?P z4NZD)n&Rrly)*}IZOFMJS0scofG}@Mbf9Zw%H>|rE++MK9~r^pyB4!SQ1P$+19+-v z)41GspUAMq9kqA!g(&ZXR!^&|KkO7rHC9vT%|v7QKpy<#+h9o1UBx8PBmACHCrKaz zI3So1*yAQqqT-gxMy?1x-~`prt|D86&NX`BfceP@=QY4ysjJW zCGMtb-bPho=L%_zl3RYl+X^$~D=o|}AKKS|+SUZ9vRasmG;Ao>*C6yQ-%_2FUm3R` z(VJQB@)oW*fKgC3`MI_%kasRt=-PeRD#T7>6zhCFO%-^Z=?-)`pX&}B#|{y*1{lCg zyaY6<&H5Jgs8Gy`He8dX*;|lOMBd_l~HF?K64 zdnSM3*0+JI{}}Zlo6t&hSbzm&{OOj*$Kw*O)qSmDF10%wV$_w9ONYdZvFUvKBk1Hh zX_lA^ek|&Cq*cnO-Cv{rC1oO+ACf=+8FgxrJSdogCy#W(nzM(WU4rK$dp zHh&=DSXLID)P1Z$UJVuCZ<-;5s!~`?Pw6WUEA$f|>Df{OsfbnW|GwzMq`+R$YO3&q zCSaWmplyEcI^NvIcf4(?{8Mp$3yzNVe|RvSZa<|Iy9fV<-3F?)4z`PqX*97BgaET} zEQxBskCn#u8!j<~#-^D}UU-mrdr-xX;bHmG;rZqwB^ZBrY&d`CaF2V?THPn%MXM2@ zMlB0#H}Z-6P4DVYwGHt_&<7w{r%puYo{7#7a1=GRCUnHNQ3T7i#Yz#o0j(6 zxtl8}S8FP;#h>-P@-JdwlN!XJxz-dVuL-A0=DCpEt^bA$Cl3;mYC+{c;{%2HiThD_ z=Bp+61Jh6Vy=`;Y;d}28s@B)9p|pj`v881lDQshh*KXls-U*ASpf46W_UwAHf0eRY z?I~5_zRX|>PcW9fLSWr<)Nk6iEmvB@F<47X_YVVm4o-T61@?LmE>J*giWvU+Ix^*LvhZBgW$TtN@Sxts*g9=FjcuH&Ris94LZI=@(CXxz+9#=R2dK$T+@&NIj1N>kh1pFvzZQu> zhX6x&qCqC|J{oTw0e!4la3C$*a^6gAI9mjdQPI$$hpZiSKF;UC?hkfayshslh-&V{ zkRCX%RBfa=bZ1SrCXNEKig%hkIGr}ob2g| z0~Wxr7r@C?dSsMWT0Ov{y06i9J-LwVAHXp=~S~0g|+rU_^ z2ne%pk{EWq{bjoF{N|zRL|e|iWe@<7w$_3QYpBl1$w|r4+a>SdoL}%JPY2mt#rf=W z()GcVA6NnBtDMgp*9IM>NFR0Ml( zK91FXaUBjGV(LfOOb%}iyQ%Fi@%;gG#5Z7K`^E9xf|2}JT!36Hr&uGjdH(j{YWCw% zQ(5{_SxJQF&W5x1F_I!dOa6iM)svSp+ykVHB<%6ZdGtlGIbS943d$`={i6f1t7`&F zc&C@%!=HbeUSF$Brfr|eHc(%VJv(q@suf|&uHRm?&ob^OFRRQcJ@^5Lg&I#r+!VnU zA1&*}m9^>;3HQANz?|vk-tn$P%SRNU;k~GklXk}&S0LWF$Ud3HPp522ukY>?SpkY! z^O=t54TBpHj8^C$8n#Vt#y<|_D{4gmht^%L;L9lu*NzfdRz?Hsb03&mYdp9LoI0Yo zT>MQn(^?*rKR9Dcj_1B$A4;RQZ2m4Rq^wDRAxy<*BXNV-kUx1D>EU0uORsG0BN9LT z=H%4Pt?HH^x|m2Y1|I?-Oq2KZ>t@)?s2Nk%&L5Sr=NE>CHV5Iv!Yr_43=e`aFjg9M&Vcg_J7<#_a>Wp zP4ih|G%YBdiK@)&UQMHzD8FiMFm?IB|8q>+~%d4v*;LJ3}+S=P4$%e z2EElz{?^ONL+al~{2x-Vk2y|qSA&>?cKZdkGB{^ehA$Pd$NzZo+G2eb<7;5n$omi< zBg^yW-;yE7rn`Ovw_auHbY{_7NK2`W;j?usa|PH{h($x|=ze@ej7d(Dxt{2=%ivT{ z3Wd2Iox9AN(98@Xc|w#btIF)6wa*}f!~hgCIB;AM-TI!vmFn*a3z zpxSHBGR}Ki{60(I#79FAuA-GGOO$*GeuwY1YWI$*v-6>?{6mK>rfykiQCc!75esf+ zV9AyLj*q+WtB?;8WnW16rri&JZXS!jmQh2*@m0I4VnK$-3RTtF@aQuHa~NwxM(zOy|9O1{! zUkpCyY|qz8VUjhZz%&jnI6bbSv7ku=dB`b<^xuy#OuhSs@5NpwTl5QgW3HNbcF(8q zo*zp->C@Y=?UsO02v_5Hz4-hovnt4A7bk#0mgC4`O}r+3#tbKdqX&MJ`uKA;{bbx{ zqOLDU|E6M=NV^IlDe7)0DQXt;>$|0VsRZ_yU(w|U@3rRS+y-aLOw-2ABB95EH6VY--67RW|Q9g`KJ0mKi(an&)^-)7P*&78XivLanZ-? zIj8(()zj9*^2gkgvGvrJzDRvZpyQt+kHM_$v%u9y!BD zH6(TUCi??MI%EQw^4Z&Gr}Z|ibNA{J}JIm*rFX|3%)&Vxdbw`9FR0& z-9LX!D7=vALS{Ei&}rV`_FAr9N~<`!^(&2getU3rwKd8OKYzd`BXypwWOFxbmK$3V>khK9};2H_R{?{ zJApGXBy|;}k#((3<>PVv^}@OX6^X2^F)feK3w{>Pg|x$q${Jt7gSmb+|s zj$G6A;DyyIVP6;QfwcHjt4XkyVS>b2ftl1Tp9Ed;r(%yD6keuUN+3UwPWVuhQr#$! zg48(;eZ`TICb3++U{GXhqQB8jmqBXgi%A++VHV+ZBXsXOW*Rj)&UWL8^Eb1s-Y-v_ z#}Vu7crueSR;M!+pSuOA8Hk`KYGj?wnqf&D# zhVc%boQ$|$uh!#F-Y`FL)hCNSw?()5cHo3q+|3n!wIgVmCp@qsXMph1;J+NR20M$h z80l@=$lCPe%M~B}BXRXwYVB-R_U2dB^D=sgr&!JNvK*QB9_G4g?nFcTMMs`CqaHZd zICp1iXQILX$%y)4>)019)8qF+`>Rs&;P=u5*!X#+m$z$NLYM(M|J(ix^sfo?$e6Qt z?vKH)S>2BWHAaRQhdR35va>$p=Rnfu9pQ|E;rLThM`J`2PcLIXxr${A`fjU@W_QguBBFDdXA7Fms^MM{ZEdIl{^w;LL`k;ry`$wvIZ z<>q}pw)ZnMckMHM`7k@)OGrpi?65x^KpEZ02)N=(Dd9h1aE( zQtcP@{6k>5lFmQ&4Ib?Hm3bRVHTQ(%-~XerI}?Bkcc!gcJFD%((_2VQOf{r5#p?A` zVBs8Efp+-w$AVjIy`_hb`;qh_fg#)P)A1MWUg+i={oPt4?h=6n3K`N~+-NpP%RCxW z6tc>nxZQK2@(Q%vQWQLMM37)GbTcj`Egq6Tgb4*UtmJN|w_Pl~#Zta*y>M27G((9+ z67sWfEVx)#z+v~f`@Hp2d~`scPqN?T0Pd}xB)4e5&UaF5((g_}m?+4v82Kf}$1M~A zMQ%X!KmH#4tpNT^n0@)Hz;}Q3hs&{Kr_DYdWz#aF;&pldwDS70X7ix6Ou@3I;h9(m zorp^Kvd-~2gjT@aZSyVP<&x3a&}1U=)UjA|%o39%?Yp}b=>%gA7%^FCBQCVfwk2Y8 ziuy%k4uL+4z+a!841214w9X9V#vUuMley=NzwUgYSB>6HZLD;-b@$p=N3mkfoyUSa zuN`+*be+q)BpsfeUu1-$TCuiGl2cxFiu^jfsC3L6F_aEEbb4A_d;5FIyOYLw>G-f2TfGI?ZYD=z{8u@!vB=Qu3kX`ny@E3)D-Mv@_Vd-16~Q=82!mP@ z&Bh~h+R&`_B9FR z+ZH<_Gef8{*jovijv-W3I_nM&r?A`Y7@K2+jzHKh84 z%1h;(Asygq4LPsu=l>Ordg{e-BM(G`hcB`4V^;#qs|taLj7brl2m$YnPw459y-R<^ z6Bzud7CS|A|8n4X$kOr^{n9rS!mM~t6VjUafC>N4q|rArJu#DxpPh_XNwzVA8`oQ@ zMF(P8JbSXMM6%tH-Z#had=#(Kayi3n6!)tq*Y@>6dPoovn5qz{rZC=T*Cr#IE*R3R*yu?V>{i{tZjfXL~mBG_nL4`~|h<@UEu11>YPyD=0` z4v~@Gxc3h&BKQ+#))}-syQ4_2USRdAlZBR163ZeUH@@UcUhBU(TiMFDTZ3pHt2Y1Ns& z>)`cR^^CWS8QcgS}%A1Krhs)VgH1%cc+PhHc^_a^*Sq(Vx(bp%f7aORcY?ZvLQh+Z#_0O-ur$+ zU`KWGsI?(ELEFf}`NaP;#%yuRX>^#)wBWtXq{M>7Y^H1Ke0#Yg z^_v?!5v2=}wN)yI(??qyU4Ghcwi?;oZzq8{8803U@R%bVB>&x9u9k0l`NO|$&6g~i6*r())Z8*1p- zI%`rRiU@$+I#Wy>z*&gMWAu?wljI7_(Ggo2(>TR*uwLRo@U-Gcm}kt282?~W_IVng z_63hBFS0exV!FDg&rc~OW-wf8z+&DmM{I`E|MU%3_wdsDA`m+V7p@gbA$d{49tN}s z?8$CK^77IFexfy{WRLPOoZxth$S1dszoKut{LA&$4?EUscE&p0&3hlm2FSe)U{08C zVly1GC%h~3nWc(QGewm!1AMt&=b?UA_%@0N&RCr)SzZyA7#_TFSzWr~7?=^5`ooFT zN?tr+O>i3_lzZ^`{Q$Iva53dyFsc6u()0}!{OZd-Dq~WAmL91lN7`>KrtUPtT2;=z zCBXXXKk}SDTy3np!Wx4pRGz$SOzfL}^YR-U#N})(;rX=xkdE1sFMX-dXtUyXigBw) zqlxM8hn{xJXzP)`!dK=Q_qW6aPhJ>5&5>@u?4ueju|!vH+V`bD^%_U#^dvbE>(mq| zX_zM}ncdn5^&{IECAOuLB0uw!0X-WaObw5qe$=+{EH>U@MlnF{*C&a}>}K@Fu<3^` zqt`F?o?JTqJ3$r(5?r^jvcm``nhwFYMbs?VF|oSe`o24@!!J(GRx{8)F7Ix=C#fML zsmVc^#hmGrIpMKTa_y~_A6A>Gy1l`8aJaBkVx+8L{)%R(&5zMl7ydk|8@7|UJj?sM zbcT?kdOQeM%5%EWPIY7QT=?L%Nq6y%d=*hZZ|Caehw?(lu?y71@so>4|F>&*I4
M7A>`HiQ=A1A~1s~zYZ7y+WfWa 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 aKXhv%nRnN(>Kmir&tAQQx&_*H5&r`h?M%7= literal 0 HcmV?d00001 diff --git a/src/chart_types/partition_chart/layout/types/viewmodel_types.ts b/src/chart_types/partition_chart/layout/types/viewmodel_types.ts index cb484618c8..2716b22389 100644 --- a/src/chart_types/partition_chart/layout/types/viewmodel_types.ts +++ b/src/chart_types/partition_chart/layout/types/viewmodel_types.ts @@ -1,5 +1,5 @@ import { Config } from './config_types'; -import { Coordinate, Distance, PointObject, PointTuple, Radian } from './geometry_types'; +import { Coordinate, Distance, Pixels, PointObject, PointTuple, Radian } from './geometry_types'; import { Font } from './types'; import { config } from '../config/config'; import { ArrayNode, HierarchyOfArrays } from '../utils/group_by_rollup'; @@ -54,6 +54,8 @@ export interface OutsideLinksViewModel { points: Array; } +export type PickFunction = (x: Pixels, y: Pixels) => Array; + export type ShapeViewModel = { config: Config; quadViewModel: QuadViewModel[]; @@ -61,16 +63,19 @@ export type ShapeViewModel = { linkLabelViewModels: LinkLabelVM[]; outsideLinksViewModel: OutsideLinksViewModel[]; diskCenter: PointObject; + pickQuads: PickFunction; }; -export const nullSectorViewModel = (): ShapeViewModel => ({ - config, +export const nullShapeViewModel = (specifiedConfig?: Config, diskCenter?: PointObject): ShapeViewModel => ({ + config: specifiedConfig || config, quadViewModel: [], rowSets: [], linkLabelViewModels: [], outsideLinksViewModel: [], - diskCenter: { x: 0, y: 0 }, + diskCenter: diskCenter || { x: 0, y: 0 }, + pickQuads: () => [], }); + type TreeLevel = number; interface AngleFromTo { diff --git a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts index 4f943f6cfd..f5e284ba6f 100644 --- a/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts +++ b/src/chart_types/partition_chart/layout/utils/group_by_rollup.ts @@ -4,12 +4,14 @@ import { Datum } from '../../../../utils/commons'; export const AGGREGATE_KEY = 'value'; // todo later switch back to 'aggregate' export const DEPTH_KEY = 'depth'; export const CHILDREN_KEY = 'children'; +export const INPUT_KEY = 'inputIndex'; export const PARENT_KEY = 'parent'; export const SORT_INDEX_KEY = 'sortIndex'; interface NodeDescriptor { [AGGREGATE_KEY]: number; [DEPTH_KEY]: number; + [INPUT_KEY]?: Array; } export type ArrayEntry = [Key, ArrayNode]; @@ -71,11 +73,13 @@ export function groupByRollup( const keyExists = pointer.has(key); const last = i === keyCount - 1; const node = keyExists && pointer.get(key); + const inputIndices = node ? node[INPUT_KEY] : []; const childrenMap = node ? node[CHILDREN_KEY] : new Map(); const aggregate = node ? node[AGGREGATE_KEY] : identity(); const reductionValue = reducer(aggregate, valueAccessor(n)); pointer.set(key, { [AGGREGATE_KEY]: reductionValue, + [INPUT_KEY]: [...inputIndices, index], [DEPTH_KEY]: i, ...(!last && { [CHILDREN_KEY]: childrenMap }), }); @@ -91,7 +95,7 @@ export function groupByRollup( function getRootArrayNode(): ArrayNode { const children: HierarchyOfArrays = []; - const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children }; + const bootstrap = { [AGGREGATE_KEY]: NaN, [DEPTH_KEY]: NaN, [CHILDREN_KEY]: children, [INPUT_KEY]: [] as number[] }; Object.assign(bootstrap, { [PARENT_KEY]: bootstrap }); const result: ArrayNode = bootstrap as ArrayNode; return result; @@ -109,6 +113,7 @@ export function mapsToArrays(root: HierarchyOfMaps, sorter: NodeSorter): Hierarc [DEPTH_KEY]: NaN, [SORT_INDEX_KEY]: NaN, [PARENT_KEY]: parent, + [INPUT_KEY]: [], }; const newValue: ArrayNode = Object.assign( resultNode, diff --git a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts index 313e1b12db..0fed9fd8cb 100644 --- a/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/partition_chart/layout/viewmodel/viewmodel.ts @@ -9,7 +9,9 @@ import { sunburst } from '../utils/sunburst'; import { IndexedAccessorFn } from '../../../../utils/accessor'; import { argsToRGBString, stringToRGB } from '../utils/d3_utils'; import { + nullShapeViewModel, OutsideLinksViewModel, + PickFunction, QuadViewModel, RawTextGetter, RowSet, @@ -117,7 +119,7 @@ export function makeOutsideLinksViewModel( }) .filter(({ points }: OutsideLinksViewModel) => points.length > 1); } -// todo break up this long function + export function shapeViewModel( textMeasure: TextMeasure, config: Config, @@ -158,14 +160,7 @@ export function shapeViewModel( facts.some((n) => valueAccessor(n) < 0) || facts.reduce((p: number, n) => aggregator.reducer(p, valueAccessor(n)), aggregator.identity()) <= 0 ) { - return { - config, - diskCenter, - quadViewModel: [], - rowSets: [], - linkLabelViewModels: [], - outsideLinksViewModel: [], - }; + return nullShapeViewModel(config, diskCenter); } // We can precompute things invariant of how the rectangle is divvied up. @@ -273,6 +268,18 @@ export function shapeViewModel( valueFormatter, ); + const pickQuads: PickFunction = (x, y) => { + return quadViewModel.filter( + treemapLayout + ? ({ x0, y0, x1, y1 }) => x0 <= x && x <= x1 && y0 <= y && y <= y1 + : ({ x0, y0px, x1, y1px }) => { + const angleX = (Math.atan2(y, x) + TAU / 4 + TAU) % TAU; + const yPx = Math.sqrt(x * x + y * y); + return x0 <= angleX && angleX <= x1 && y0px <= yPx && yPx <= y1px; + }, + ); + }; + // combined viewModel return { config, @@ -281,5 +288,6 @@ export function shapeViewModel( rowSets, linkLabelViewModels, outsideLinksViewModel, + pickQuads, }; } diff --git a/src/chart_types/partition_chart/renderer/canvas/partition.tsx b/src/chart_types/partition_chart/renderer/canvas/partition.tsx index 9564403fa7..d5f2e899cd 100644 --- a/src/chart_types/partition_chart/renderer/canvas/partition.tsx +++ b/src/chart_types/partition_chart/renderer/canvas/partition.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { MouseEvent } from 'react'; import { bindActionCreators, Dispatch } from 'redux'; import { connect } from 'react-redux'; import { onChartRendered } from '../../../../state/actions/chart'; @@ -6,8 +6,9 @@ import { isInitialized } from '../../../../state/selectors/is_initialized'; import { GlobalChartState } from '../../../../state/chart_state'; import { Dimensions } from '../../../../utils/dimensions'; import { partitionGeometries } from '../../state/selectors/geometries'; -import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; +import { nullShapeViewModel, QuadViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { renderPartitionCanvas2d } from './canvas_renderers'; +import { INPUT_KEY } from '../../layout/utils/group_by_rollup'; interface ReactiveChartStateProps { initialized: boolean; @@ -69,6 +70,38 @@ class PartitionComponent extends React.Component { } } + handleMouseMove(e: MouseEvent) { + const { + initialized, + chartContainerDimensions: { width, height }, + } = this.props; + if (!this.canvasRef.current || !this.ctx || !initialized || width === 0 || height === 0) { + return; + } + const picker = this.props.geometries.pickQuads; + const box = this.canvasRef.current.getBoundingClientRect(); + const diskCenter = this.props.geometries.diskCenter; + const x = e.clientX - box.left - diskCenter.x; + const y = e.clientY - box.top - diskCenter.y; + const pickedShapes: Array = picker(x, y); + const datumIndices = new Set(); + pickedShapes.forEach((shape) => { + const node = shape.parent; + const shapeNode = node.children.find(([key]) => key === shape.dataName); + if (shapeNode) { + const indices = shapeNode[1][INPUT_KEY] || []; + indices.forEach((i) => datumIndices.add(i)); + } + }); + /* + console.log( + pickedShapes.map((s) => s.value), + [...datumIndices.values()], + ); + */ + return pickedShapes; // placeholder + } + render() { const { initialized, @@ -84,6 +117,7 @@ class PartitionComponent extends React.Component { className="echCanvasRenderer" width={width * this.devicePixelRatio} height={height * this.devicePixelRatio} + onMouseMove={this.handleMouseMove.bind(this)} style={{ width, height, @@ -103,7 +137,7 @@ const mapDispatchToProps = (dispatch: Dispatch): ReactiveChartDispatchProps => const DEFAULT_PROPS: ReactiveChartStateProps = { initialized: false, - geometries: nullSectorViewModel(), + geometries: nullShapeViewModel(), chartContainerDimensions: { width: 0, height: 0, diff --git a/src/chart_types/partition_chart/state/chart_state.tsx b/src/chart_types/partition_chart/state/chart_state.tsx index 53bd8f9ac2..c36f46c4a4 100644 --- a/src/chart_types/partition_chart/state/chart_state.tsx +++ b/src/chart_types/partition_chart/state/chart_state.tsx @@ -1,7 +1,10 @@ import React from 'react'; -import { InternalChartState } from '../../../state/chart_state'; +import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state'; import { ChartTypes } from '../..'; import { Partition } from '../renderer/canvas/partition'; +import { isTooltipVisibleSelector } from '../state/selectors/is_tooltip_visible'; +import { getTooltipInfoSelector } from '../state/selectors/tooltip'; +import { Tooltip } from '../../../components/tooltip'; const EMPTY_MAP = new Map(); export class PartitionState implements InternalChartState { @@ -21,19 +24,29 @@ export class PartitionState implements InternalChartState { getLegendItemsValues() { return EMPTY_MAP; } - chartRenderer() { - return ; + chartRenderer(containerRef: BackwardRef) { + return ( + <> + + + + ); } getPointerCursor() { return 'default'; } - isTooltipVisible() { - return false; + isTooltipVisible(globalState: GlobalChartState) { + return isTooltipVisibleSelector(globalState); } - getTooltipInfo() { - return undefined; + getTooltipInfo(globalState: GlobalChartState) { + return getTooltipInfoSelector(globalState); } - getTooltipAnchor() { - return null; + getTooltipAnchor(state: GlobalChartState) { + const position = state.interactions.pointer.current.position; + return { + isRotated: false, + x1: position.x, + y1: position.y, + }; } } diff --git a/src/chart_types/partition_chart/state/selectors/geometries.ts b/src/chart_types/partition_chart/state/selectors/geometries.ts index 164042ffe1..62b86523ee 100644 --- a/src/chart_types/partition_chart/state/selectors/geometries.ts +++ b/src/chart_types/partition_chart/state/selectors/geometries.ts @@ -3,7 +3,7 @@ import { GlobalChartState } from '../../../../state/chart_state'; import { getSpecsFromStore } from '../../../../state/utils'; import { ChartTypes } from '../../..'; import { render } from './scenegraph'; -import { nullSectorViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; +import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types'; import { PartitionSpec } from '../../specs/index'; import { SpecTypes } from '../../../../specs/settings'; @@ -15,6 +15,6 @@ export const partitionGeometries = createCachedSelector( [getSpecs, getParentDimensions], (specs, parentDimensions): ShapeViewModel => { const pieSpecs = getSpecsFromStore(specs, ChartTypes.Partition, SpecTypes.Series); - return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullSectorViewModel(); + return pieSpecs.length === 1 ? render(pieSpecs[0], parentDimensions) : nullShapeViewModel(); }, )((state) => state.chartId); diff --git a/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts b/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts new file mode 100644 index 0000000000..ea65d20c06 --- /dev/null +++ b/src/chart_types/partition_chart/state/selectors/is_tooltip_visible.ts @@ -0,0 +1,20 @@ +import createCachedSelector from 're-reselect'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; + +import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; +import { TooltipType, getTooltipType } from '../../../../specs'; +import { getTooltipInfoSelector } from './tooltip'; + +/** + * The brush is available only for Ordinal xScales charts and + * if we have configured an onBrushEnd listener + */ +export const isTooltipVisibleSelector = createCachedSelector( + [getSettingsSpecSelector, getTooltipInfoSelector], + (settingsSpec, tooltipInfo): boolean => { + if (getTooltipType(settingsSpec) === TooltipType.None) { + return false; + } + return tooltipInfo.values.length > 0; + }, +)(getChartIdSelector); diff --git a/src/chart_types/partition_chart/state/selectors/scenegraph.ts b/src/chart_types/partition_chart/state/selectors/scenegraph.ts index f6db36c72f..c08b1d549c 100644 --- a/src/chart_types/partition_chart/state/selectors/scenegraph.ts +++ b/src/chart_types/partition_chart/state/selectors/scenegraph.ts @@ -1,7 +1,7 @@ import { Dimensions } from '../../../../utils/dimensions'; import { shapeViewModel } from '../../layout/viewmodel/viewmodel'; import { measureText } from '../../layout/utils/measure'; -import { ShapeTreeNode, ShapeViewModel, RawTextGetter } from '../../layout/types/viewmodel_types'; +import { ShapeTreeNode, ShapeViewModel, RawTextGetter, nullShapeViewModel } from '../../layout/types/viewmodel_types'; import { DEPTH_KEY } from '../../layout/utils/group_by_rollup'; import { PartitionSpec, Layer } from '../../specs/index'; import { identity, mergePartial, RecursivePartial } from '../../../../utils/commons'; @@ -23,14 +23,7 @@ export function render(partitionSpec: PartitionSpec, parentDimensions: Dimension const partialConfig: RecursivePartial = { ...specConfig, width, height }; const config: Config = mergePartial(defaultConfig, partialConfig); if (!textMeasurerCtx) { - return { - config, - quadViewModel: [], - rowSets: [], - linkLabelViewModels: [], - outsideLinksViewModel: [], - diskCenter: { x: width / 2, y: height / 2 }, - }; + return nullShapeViewModel(config, { x: width / 2, y: height / 2 }); } return shapeViewModel( measureText(textMeasurerCtx), diff --git a/src/chart_types/partition_chart/state/selectors/tooltip.ts b/src/chart_types/partition_chart/state/selectors/tooltip.ts new file mode 100644 index 0000000000..43da080d19 --- /dev/null +++ b/src/chart_types/partition_chart/state/selectors/tooltip.ts @@ -0,0 +1,74 @@ +import createCachedSelector from 're-reselect'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { partitionGeometries } from './geometries'; +import { INPUT_KEY } from '../../layout/utils/group_by_rollup'; +import { QuadViewModel } from '../../layout/types/viewmodel_types'; +import { TooltipInfo } from '../../../../components/tooltip/types'; +import { ChartTypes } from '../../..'; +import { SpecTypes } from '../../../../specs'; +import { getSpecsFromStore } from '../../../../state/utils'; +import { PartitionSpec } from '../../specs'; + +function getCurrentPointerPosition(state: GlobalChartState) { + return state.interactions.pointer.current.position; +} + +function getPieSpecOrNull(state: GlobalChartState): PartitionSpec | null { + const pieSpecs = getSpecsFromStore(state.specs, ChartTypes.Partition, SpecTypes.Series); + return pieSpecs.length > 0 ? pieSpecs[0] : null; +} + +function getValueFormatter(state: GlobalChartState) { + return getPieSpecOrNull(state)?.valueFormatter; +} + +function getLabelFormatters(state: GlobalChartState) { + return getPieSpecOrNull(state)?.layers; +} + +const EMPTY_TOOLTIP = Object.freeze({ + header: null, + values: [], +}); + +export const getTooltipInfoSelector = createCachedSelector( + [getPieSpecOrNull, partitionGeometries, getCurrentPointerPosition, getValueFormatter, getLabelFormatters], + (pieSpec, geoms, pointerPosition, valueFormatter, labelFormatters): TooltipInfo => { + if (!pieSpec || !valueFormatter || !labelFormatters) { + return EMPTY_TOOLTIP; + } + const picker = geoms.pickQuads; + const diskCenter = geoms.diskCenter; + const x = pointerPosition.x - diskCenter.x; + const y = pointerPosition.y - diskCenter.y; + const pickedShapes: Array = picker(x, y); + const datumIndices = new Set(); + const tooltipInfo: TooltipInfo = { + header: null, + values: [], + }; + pickedShapes.forEach((shape) => { + const node = shape.parent; + const formatter = labelFormatters[shape.depth - 1] && labelFormatters[shape.depth - 1].nodeLabel; + + tooltipInfo.values.push({ + label: formatter ? formatter(shape.dataName) : shape.dataName, + color: shape.fillColor, + isHighlighted: false, + isVisible: true, + seriesIdentifier: { + specId: pieSpec.id, + key: pieSpec.id, + }, + value: valueFormatter(shape.value), + }); + const shapeNode = node.children.find(([key]) => key === shape.dataName); + if (shapeNode) { + const indices = shapeNode[1][INPUT_KEY] || []; + indices.forEach((i) => datumIndices.add(i)); + } + }); + + return tooltipInfo; + }, +)((state) => state.chartId); diff --git a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts index 3508b06bac..dc1118c7f2 100644 --- a/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts +++ b/src/chart_types/xy_chart/crosshair/crosshair_utils.test.ts @@ -15,132 +15,100 @@ describe('Tooltip position', () => { }; describe('horizontal rotated chart', () => { it('can position the tooltip on the top left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y1: 0, - y0: 0, - x0: 10, - x1: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y1: 0, + y0: 0, + x0: 10, + x1: 10, + padding: 5, + }); expect(position.left).toBe('25px'); expect(position.top).toBe('10px'); }); it('can position the tooltip on the bottom left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 90, - y1: 90, - x0: 10, - x1: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 90, + y1: 90, + x0: 10, + x1: 10, + padding: 5, + }); expect(position.left).toBe('25px'); expect(position.top).toBe('80px'); }); it('can position the tooltip on the top right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 0, - y1: 0, - x0: 100, - x1: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 0, + y1: 0, + x0: 100, + x1: 100, + padding: 5, + }); expect(position.left).toBe('65px'); expect(position.top).toBe('10px'); }); it('can position the tooltip on the bottom right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: false, - y0: 90, - y1: 90, - x0: 100, - x1: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: false, + y0: 90, + y1: 90, + x0: 100, + x1: 100, + padding: 5, + }); expect(position.left).toBe('65px'); expect(position.top).toBe('80px'); }); }); describe('vertical rotated chart', () => { it('can position the tooltip on the top left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 0, - y1: 0, - x1: 10, - x0: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 0, + y1: 0, + x1: 10, + x0: 10, + padding: 5, + }); expect(position.left).toBe('20px'); expect(position.top).toBe('15px'); }); it('can position the tooltip on the bottom left corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 90, - y1: 90, - x1: 10, - x0: 10, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 90, + y1: 90, + x1: 10, + x0: 10, + padding: 5, + }); expect(position.left).toBe('20px'); expect(position.top).toBe('65px'); }); it('can position the tooltip on the top right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 0, - y1: 0, - x1: 100, - x0: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 0, + y1: 0, + x1: 100, + x0: 100, + padding: 5, + }); expect(position.left).toBe('70px'); expect(position.top).toBe('15px'); }); it('can position the tooltip on the bottom right corner', () => { - const position = getFinalTooltipPosition( - container, - tooltip, - { - isRotated: true, - y0: 90, - y1: 90, - x1: 100, - x0: 100, - }, - 5, - ); + const position = getFinalTooltipPosition(container, tooltip, { + isRotated: true, + y0: 90, + y1: 90, + x1: 100, + x0: 100, + padding: 5, + }); expect(position.left).toBe('70px'); expect(position.top).toBe('65px'); }); diff --git a/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts b/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts index 7207474d6e..9d88fcd24e 100644 --- a/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts +++ b/src/chart_types/xy_chart/state/selectors/get_tooltip_type.ts @@ -1,23 +1,9 @@ import createCachedSelector from 're-reselect'; import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; -import { SettingsSpec, TooltipType, isTooltipType, isTooltipProps } from '../../../../specs/settings'; +import { getTooltipType } from '../../../../specs/settings'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; export const getTooltipTypeSelector = createCachedSelector( [getSettingsSpecSelector], getTooltipType, )(getChartIdSelector); - -export function getTooltipType(settings: SettingsSpec): TooltipType | undefined { - const { tooltip } = settings; - if (tooltip === undefined || tooltip === null) { - return undefined; - } - if (isTooltipType(tooltip)) { - return tooltip; - } - if (isTooltipProps(tooltip)) { - return tooltip.type || undefined; - } - return undefined; -} diff --git a/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts b/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts index 225b76a32a..4f6313a9d5 100644 --- a/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts +++ b/src/chart_types/xy_chart/state/selectors/is_tooltip_visible.ts @@ -5,8 +5,7 @@ import { getSettingsSpecSelector } from '../../../../state/selectors/get_setting import { getProjectedPointerPositionSelector } from './get_projected_pointer_position'; import { getTooltipInfoSelector } from './get_tooltip_values_highlighted_geoms'; import { getChartIdSelector } from '../../../../state/selectors/get_chart_id'; -import { getTooltipType } from './get_tooltip_type'; -import { TooltipType } from '../../../../specs'; +import { TooltipType, getTooltipType } from '../../../../specs'; import { isAnnotationTooltipVisibleSelector } from './is_annotation_tooltip_visible'; import { TooltipInfo } from '../../../../components/tooltip/types'; diff --git a/src/chart_types/xy_chart/tooltip/tooltip.ts b/src/chart_types/xy_chart/tooltip/tooltip.ts index bd80731086..2558efc0be 100644 --- a/src/chart_types/xy_chart/tooltip/tooltip.ts +++ b/src/chart_types/xy_chart/tooltip/tooltip.ts @@ -29,13 +29,15 @@ export function getSeriesTooltipValues( tooltipValues.forEach(({ value, seriesIdentifier, valueAccessor }) => { const seriesValue = defaultValue ? defaultValue : value; const current = seriesTooltipValues.get(seriesIdentifier.key) || {}; - - seriesTooltipValues.set(seriesIdentifier.key, { + const tooltipValue: TooltipLegendValue = { y0: defaultValue, y1: defaultValue, ...current, - [valueAccessor]: seriesValue, - }); + }; + if (valueAccessor != null && (valueAccessor === 'y0' || valueAccessor === 'y1')) { + tooltipValue[valueAccessor] = seriesValue; + } + seriesTooltipValues.set(seriesIdentifier.key, tooltipValue); }); return seriesTooltipValues; } diff --git a/src/components/tooltip/index.tsx b/src/components/tooltip/index.tsx index 47028c9aad..4c936c6dca 100644 --- a/src/components/tooltip/index.tsx +++ b/src/components/tooltip/index.tsx @@ -75,11 +75,10 @@ class TooltipComponent extends React.Component { } renderHeader(headerData: TooltipValue | null, formatter?: TooltipValueFormatter) { - if (!headerData) { + if (!headerData || !headerData.isVisible) { return null; } - - return formatter ? formatter(headerData) : headerData.value; + return
{formatter ? formatter(headerData) : headerData.value}
; } render() { @@ -90,7 +89,7 @@ class TooltipComponent extends React.Component { } const tooltipComponent = (
-
{this.renderHeader(info.header, headerFormatter)}
+ {this.renderHeader(info.header, headerFormatter)}
{info.values.map(({ seriesIdentifier, valueAccessor, label, value, color, isHighlighted, isVisible }) => { if (!isVisible) { diff --git a/src/components/tooltip/utils.ts b/src/components/tooltip/utils.ts index 6dece3cf88..99a7ec45aa 100644 --- a/src/components/tooltip/utils.ts +++ b/src/components/tooltip/utils.ts @@ -21,6 +21,10 @@ export interface TooltipAnchorPosition { * the left position of the anchor */ x1: number; + /** + * the padding to add between the tooltip position and the final position + */ + padding?: number; } export function getFinalTooltipPosition( @@ -30,18 +34,16 @@ export function getFinalTooltipPosition( tooltip: Dimensions, /** the tooltip anchor computed position not adjusted within chart bounds */ anchorPosition: TooltipAnchorPosition, - /** the padding to add between the tooltip position and the final position */ - padding = 10, ): { left: string | null; top: string | null; } { - const { x1, y1, isRotated } = anchorPosition; + const { x1, y1, isRotated, padding = 10 } = anchorPosition; let left = 0; let top = 0; - const x0 = anchorPosition.x0 || 0; - const y0 = anchorPosition.y0 || 0; + const x0 = anchorPosition.x0 || anchorPosition.x1; + const y0 = anchorPosition.y0 || anchorPosition.y1; if (!isRotated) { const leftOfBand = window.pageXOffset + container.left + x0; diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 68120c9b15..4725845889 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -101,7 +101,7 @@ export interface TooltipValue { /** * The accessor linked to the current tooltip value */ - valueAccessor: Accessor; + valueAccessor?: Accessor; } export type TooltipValueFormatter = (data: TooltipValue) => JSX.Element | string; @@ -267,3 +267,17 @@ export function isCrosshairTooltipType(type: TooltipType) { export function isFollowTooltipType(type: TooltipType) { return type === TooltipType.Follow; } + +export function getTooltipType(settings: SettingsSpec): TooltipType | undefined { + const { tooltip } = settings; + if (tooltip === undefined || tooltip === null) { + return undefined; + } + if (isTooltipType(tooltip)) { + return tooltip; + } + if (isTooltipProps(tooltip)) { + return tooltip.type || undefined; + } + return undefined; +}