From 1a31f4f1b3b6b03ef39f8c90ee883b67919aae1e Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 29 Dec 2022 02:55:42 -0500 Subject: [PATCH 1/4] Add styling support to Check and Radio buttons --- examples/widgets/check_buttons.py | 12 +- examples/widgets/radio_buttons.py | 20 +- .../test_widgets/check_radio_buttons.png | Bin 14567 -> 24315 bytes lib/matplotlib/tests/test_widgets.py | 75 ++++- lib/matplotlib/widgets.py | 269 +++++++++++++++--- 5 files changed, 311 insertions(+), 65 deletions(-) diff --git a/examples/widgets/check_buttons.py b/examples/widgets/check_buttons.py index d5894fdad705..934e0ecfefb2 100644 --- a/examples/widgets/check_buttons.py +++ b/examples/widgets/check_buttons.py @@ -20,19 +20,23 @@ s2 = np.sin(6*np.pi*t) fig, ax = plt.subplots() -l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz') -l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz') -l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz') +l0, = ax.plot(t, s0, visible=False, lw=2, color='black', label='1 Hz') +l1, = ax.plot(t, s1, lw=2, color='red', label='2 Hz') +l2, = ax.plot(t, s2, lw=2, color='green', label='3 Hz') fig.subplots_adjust(left=0.2) lines_by_label = {l.get_label(): l for l in [l0, l1, l2]} +line_colors = [l.get_color() for l in lines_by_label.values()] # Make checkbuttons with all plotted lines with correct visibility rax = fig.add_axes([0.05, 0.4, 0.1, 0.15]) check = CheckButtons( ax=rax, labels=lines_by_label.keys(), - actives=[l.get_visible() for l in lines_by_label.values()] + actives=[l.get_visible() for l in lines_by_label.values()], + label_props={'color': line_colors}, + frame_props={'edgecolor': line_colors}, + check_props={'facecolor': line_colors}, ) diff --git a/examples/widgets/radio_buttons.py b/examples/widgets/radio_buttons.py index c1600b369be7..8180ab8b9d42 100644 --- a/examples/widgets/radio_buttons.py +++ b/examples/widgets/radio_buttons.py @@ -25,23 +25,31 @@ axcolor = 'lightgoldenrodyellow' rax = fig.add_axes([0.05, 0.7, 0.15, 0.15], facecolor=axcolor) -radio = RadioButtons(rax, ('2 Hz', '4 Hz', '8 Hz')) +radio = RadioButtons(rax, ('1 Hz', '2 Hz', '4 Hz'), + label_props={'color': 'cmy', 'fontsize': [12, 14, 16]}, + radio_props={'s': [16, 32, 64]}) def hzfunc(label): - hzdict = {'2 Hz': s0, '4 Hz': s1, '8 Hz': s2} + hzdict = {'1 Hz': s0, '2 Hz': s1, '4 Hz': s2} ydata = hzdict[label] l.set_ydata(ydata) - plt.draw() + fig.canvas.draw() radio.on_clicked(hzfunc) rax = fig.add_axes([0.05, 0.4, 0.15, 0.15], facecolor=axcolor) -radio2 = RadioButtons(rax, ('red', 'blue', 'green')) +radio2 = RadioButtons( + rax, ('red', 'blue', 'green'), + label_props={'color': ['red', 'blue', 'green']}, + radio_props={ + 'facecolor': ['red', 'blue', 'green'], + 'edgecolor': ['darkred', 'darkblue', 'darkgreen'], + }) def colorfunc(label): l.set_color(label) - plt.draw() + fig.canvas.draw() radio2.on_clicked(colorfunc) rax = fig.add_axes([0.05, 0.1, 0.15, 0.15], facecolor=axcolor) @@ -50,7 +58,7 @@ def colorfunc(label): def stylefunc(label): l.set_linestyle(label) - plt.draw() + fig.canvas.draw() radio3.on_clicked(stylefunc) plt.show() diff --git a/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png b/lib/matplotlib/tests/baseline_images/test_widgets/check_radio_buttons.png index e96085d9bffd7a38297b14b2011a7061beb2a287..d6e6004a1732e7f6c449ce3ce7cc87b70743acc7 100644 GIT binary patch literal 24315 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 z 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 RQh=I=7#W%vR9tX;@NW~Cpx*!h literal 14567 zcmeHuc{r7A`|d-ElxUEm3`GMa4JyP^nlvDDGL_7QOc`RWMjE7)Oc^R=3YBCYO6848 z8AFE1oS7_ImVMs7@3+6-w~zfDd-(S6zx^J)N3yQxd9LTa@9Vs->pZV@RZC+ZAFnVk zMNxd}`*&+o6eokCIGTB8;WzSMzyHKvTrRuRb$RgLIi8dL`1fq5{YPCWivKwIpCg_Y zXNzCzy6!P_)p4|PJ$u~Qk~)3d)#;R@>nYn4;_jBtF1C&i;+teQ$;vrfY?O6!baCCX zZJX>#OLsXL>rGoMx1O-rd{SWI1z6eUin@7}5Falg00 z+jY_C>#0G_1>zEtIXts@t<-K^=gOk-tdTnLvUstl^eY>&1G)mDTDE~Zr32RRuHZ^$ zs`{v&-r3CU|BiRr?j$|leSMcM9hBJJw@P5q8_R8~hCy-#G>>n>A1v>jdXc8oU!$NP zm9x2Nyw5NsW3y74#FgVqI4O!@d1=BMe^c>hT;llWI{^m%u~?15?+oX1@KBWb#{d7p zzq2HVX3#Y$xkpI`snRr>+1IaMd1uca9CM|ps*_8u@Xnc2m8d6k`HFLHPce^b%(C(E zap&#J*io~nn0rG*LyLrjI{Ixx=W+1dU8$&ORGz+VUc{&Nj#`RJENyfC@WaWYvFylV zeA+r;YZmcoFU6@!wOZ_GYmMFh?`&pkP3naluhGPz;bFn$%Rd>UGtVDiBE4>%ZjyfT zBHzu-}d2|hqp#SbG&U9BD2k+JX!Q_Lm=pzgUVvlpKp*O!ne-;mvuIA!B=ryx%7F0)xkyz8=%>8*;c5!TPP zwsG$Rl=)iP&4LRIeiOaE7;ti3U^XSf6?ipe+F zAw~}Rz?_ycI*%$vY4!WlrdCiKlxp4zHYC!INX@St0BA%{t> z$B{g*(V=&`9MphFOPTSyGh9yPvFh1>`f^6oq9+fjtE<~J=4J)mxidOB)>Py5Yq^rU z&E&*rRMWQ8b+MCw#%k<7PO^7#kaVFO7>Tr2Er`XJ=CDq@~N@ z)W}%Dl*!ViOQqMZAGOPijg=H!wCJxcSG%TspOVj?t1r!7cuu82Q)PVjomGbutCq}| zHTO#s`}elV{wa29pw&!@K^LI6FOuW%Xn8xEZ@J>3PvP&@tX-=dry^tnI1l=N;24RI8<(@Y)Df1IG@PYL$s;!vZ`E9 zmsGQb6lJBx3NuhWH*28q{EY0L6t;8L8(r<8pxE<4;Rh%5J3c3uPqE6D`3$}@I$ir> z=aC~vOanZB{&ib~Q)cB#H8sC^RmmBRiOSx;ZALo3ZkkJe6D+yjEZ-PgSzVc+Qy+}AwGV8pvpwNq%E#7%5aFZ`WX7)eSnBP_N?UN$I7~Nd!Og_ z6eTCsQudv51yvj%vLSA>_T$I(Qt#N!VcXRwT}SP%y-4%6Z~qwP)fQ|}vV7&XZ3d3{ z<4+D8IPlV~NB{Kq%p*Vu*|d-4j}I!-Mdi#H#Yyc7Go zw_Zj5m={so2R6Vym>ib;+gux_H|`i7VGHL}U3c}9+TqxU+-O>*43 z;%Jiz*{)p=YtyWI9tEnD>u4%VWz?_ls*;?T=4TFKbT;Z+E&Syd_kkOLykMUVWR+6#+|bw^W7p)@RgQ-;f_)W9ogl zqbxc^(uY0X(cPGrQ(j(P8K)^GwQ5!OuiCUIw}%fOE)o$*`t|GACX3#NoQyZUFHhIw zJVjPqUATHjAc@(1HgeXpNO6cTN}|E^yW>r`hk#_2g}c> zM@TBCe)*Dv)3iE3jMmt*vH0flBm|?~?_ae+v7w<$mhbS;Dh?D2kv!d)dvv&?Y(v^- z|M@FN-itVTTyUs;VOjU;^s&qJnLR1?!v_u@u4*d{(K9kieED+Ao;`cI9{7nSI_32j zOqnVuD3p|y;gyCzZ7cck;p^jrA-TRjfG44r4Fg=eDfiAVJH3CO?7F=#NO1nf!`l4n z^VDfy4{uvvo_VIsrl&f<)Qn@Di4vcZ!}sl6k;-o+3Z~ z8b|t{`b|B#BjpFL1qK@6tOzb#_y|C^R_bxfqnpQ%A2)h&@~V4(V=@j+i9?3{yK90I z%@_HlBYUs_!J(m@v+F)bD#>izIC!PLnxdK=-JN>wD!os2oz;XHVbcK)Y`f*#=wNgt`pJ{;JR` z$2-3~P?KJ}_6R;dr`S{%|AC#s`V^$XUkSO+M@aMAFwC?)YLL(&z`L( zt5ch7RE$5J|~L9_MNPmDg@#^0+@5=O(z82L7^Sf2|=o5LnbMY zVzl3y4b09=V%WEIeto3q(tblS;ds#%>cQtzV zSG}+-b;SnP*pV#I`R%zy6!rb3?Hz=o{SH%i_gB6{ccTC5bx-p7^IP#u%O&A0HZcX0 zy^h^AsbUMc`Y?a^ZF`zy^L52&PmLztqw2nbi-U)6-Ah}e{Bv0yXEW-P4a|J> z@QGfkY2KFOZ@99IQ+PY75hi>~H|^#VHdfj$f-RoEa&PBfw^vqq1t16(4GOV1TNVwR zpTS*OK&Rs@bv+I-El1iQxy0vBZ-!C2T`&;&QM!GN9ahGaE;ev`<-W>X&*4phmM2fz z;ACZwRv$~ZE)xw9RW`bEoctyz7U5`*L=dj%mYD6{o0#R&A&u1HGchuFfH?N7Yohnwf*G zf=ibs0I_7YZmk~nFgCtFG1?P%_wJ(6zJ_C)tv^@Fq~>b(Ey>Et^1^}8)7E~Nd@RGR z^?iU*o|jvgGLPiQlLIcL4J z`Xv1L@#EmD1!CRR$q`W=Ewk4jeG&WS%;#uYYJkW#yM}DHiJ{UB@+bciXgJ&CV|lL@ z2Nl5gB;V&xA|B<{pEu?@(s@K(`^=f=$P{sZ`U{%U&OO>2;0iZ&Ge0%toR!(Jm?!=;`;9)-!~ra2x?PntQoayE zEkr-n6e-@TbeM$$J3?N=B-`~XFn$!f(v=Vw6;$7Q&$T!09A3rld*+$2Gcro+y$4&~ ziQFs5*U*dZ`18BoG5G%dr_ocaj&NmfcQ;dMRO`D~M+3WNe6dQi{Svd2gtbPbqF&#o zoU{Ga5BFTZ8x-_klWb&+`*AJ~XlUpc_)d<56tMP4NJyCG$)W?e&P-cX!5t&{>Ar$a zh^&crhC{>Ms3^R&dlxQ2@Z5=_vppN!YD`FF_C8Roe>Bvh_@*9?y7Io76DLl%;xsI$ z`5Fhk^c=SO6iqAmsJL?PjV~gOxj$xSTbAClMY0ogXr0K_*3oerAF>2wppHC@n#!y5 zfnSM9$4FNN@$Ar{8>=+#$D=4*vwHPDWTo`%0+iCUy{w}Ks>WR957f9VEiG*UE#WBl zVA|;^yLGF6*^c2GPY#7?CnueZ*kb$eOzZouPurRhDy>+}hx{r`)zkH_st#{!kc{~B z1XXE&OsOFA_&MZ0WdN;;Ym3(o#};G~&{}pT2bYvMq}5iP2O#$wrAgFV1>V z-#eX+a*Y%=Z`R@F7riU#)>Z!aqYHl2&2jI|^#xYwUSg)b5-5_JOF#V`ZIwetX_sq> zRhnn|XT*B&?b{FL2+6L2$I4zKT84(9W$muNzHQ8EaBu@2ywOSh-%3afbF0hDR-asF zjx$^Xek)e204PlO z2Uqun`rhB)hxTbdHml*L&igZq_|duC!j8-4H1v(1+3umkC9uPv##+L`v)}8-Wg&pN zGR@P|lO45yVsZ`o4LR{HxwS}CGzE}O*PKC}yT(-8vu-|PU!Z7J*k)@`05JkmUqjxHDfqu`ky5|;tHs)!p!^w}fO(}jg7bH2A?i3^;fIsWgu864jVz)&2?oAr(bq>LQ-xVfye z<=u6aa4AaMa6WxtZy{GR3OrKnzI*%jKlL`qy9X!PV+E=D%iOvv)%lf2ZoP7BGHoA3 z=|do(`|BgWS@V{~^V3)ZSKNfw8^!ugP9!)s<%a{Q`N}i_y`>==##nvsIiuZ4sfqZc zyDBMU)uB+Ww)J~`Jt{hH-4dAWptIwU2Sx#xo|D2HRJ9uG^)Ze!yiBC2?xuo*H*emc z@Y-P8TzH;zg!z-dk0l$WA8={^_!U@}j-1O|U@=yFbD6@!_D>NZp6mWAn;e?d;3xPx-y=7DYY1$W(Jf#d%YVW_tVf?YexQ9F%a%fyM9MC72cX9#B_5 zf_72frTtLeP>D?2&D#+XN!yyv=}1XQ)n&USZFTq_kFGD|s1-spipIZ0P8G)nh4C0y z^Gn-`k?$^Lo*K(nqp=aTMBG+l1Jivcy>PX9kRmz_9I0-gbsQRI8RN4!Q&Bn}NZ-;C zO*j2@A&9qFfzHk)j-cV|D&0^} zc}qggwaI5J_^4IIlf%MWPlZnW?oC1^+mkoeSn>7IJ}LEg@7|#ZjqWR$@+}(*5SA}j z9{aBIW9wPe!iX_VmU@jw|Ej;`u;BbY>u4W+r0%+B$1gf$wJjzI5-pGR;~p%ZA5-mc zs0!ut*uEx(R3-V$mN~t3=?BphRv&ZBYxDF)lAGw#q$kRnWZR&PnCN^II8v#j>6tGI zE+y&ZOI_q&+071U#M>Aj=P>V#I$85Ro)tg@9iiT0W9IKkr z*N`KFRDh!A$cYmVKSwF|99=Cfec<`?=b#m2H&-|2dCPp)KmejMRfybv=FRXoIOX>9 zulebx>#LKp4%GCSELpJN&3sjg!9D0#C+9;QC$U!Tpz^{HbqRY1U=;BoVls&95&i`m!=5ZHL=Gh12N1VQzbGEP1f4 z(W7XgYw5l96(mi3e?RK^_{o#*p|(<-VzgA@X!=IJsPPBqeEw17H(FD`)|)wVCRl+c z-#^KKrN;vU1DoQa5te&c4_=|OS90x)dHQr6T5NlqG6De|Bb`Y91XgTNi2Yj)m7Z7S z#&zos9lW>hE0(wFrL9hFuY0oL^Ic-+26kR{$vZdx0!cje+c!gbn=h(pWa| z7yYRV?SykqLpNHe%LWai3eLMx6j}Dw7%VN4bM5>h5q$S~UY$w^ z4Gp!+o8?$_a_ZR3KeHH1=hGYime)i=#jg4LYfZ5ydrq+~UAiP4lF&EFoK9=+dRkgq zz-AgGyHV8Z>&%NAX1-l(j+1|bc`xoYL< z8&2(=%*k&`odU;SDTYse)==x#t-C9!u#6?v7!4-YGNXQ)8k~y{+iR#(Rf)lb)?B_k z8Awgnco#IcNbC6V2Z+zM{sb`R0(7|<1+&a;Y~t~5EQOh+t$&G4-`eW+9ynAvqlMpU z7v4y#*q;{42WM56uV9<+CC<8CVqQE-oUP=55olYg+2XC($D3do3;q0QT%4o@6_a2K ztRbD}IR;ZrTmo+tFV(_-{(Y+1OzzOjC}L3%u=<>{SldOY^Os=K!XqxeR$1A^J*Q!~ z)oGxeFUIbz7^j~9@g-LZR&y|qdG4c@&-43RyDerttMI8m&BZ0l7$s6}paDatkP!>m zy6oro$6J`dOa5m`kC(}zRqmne|-tQ#_+xsyD(a33Ho%!`6#T}rW1 zdbmwt5KNX5lVvd3M@%j=XW+Js%L4R5d1A4^pVr)j->80B$!5GDKWO0x5!{6lvxmje zMjpNMDszT3S>_A#2Dx>~GIOEEiWkO>d4%7JH7|$j0*m}L@yW24gRa zql!F~bMYW|C6PmT_P-U=a+x*~psV&y8|`9oq)!{o8suiqB1Sy8r4sRN1h>o}-X_ea zGy2JkRjgn$vcDEKEu=mc{xb;<7rPhiqc{lLjV*~1^SfAZlA;rP!4pfa{ODJw*!qq0 zRsD)2;wsic1}F~WAmxG(n8IJ~O8u>Z&5Pv3*Y4P13@Q!^g1IMLrEQ-%my*MN#nXsC z;Iw0Wi5msD?F4a}b%i;DbrB1~oC)vw;eGsBcu&-z7*jYsMb^#aB#~?QsaY+lC#YH2&?PPIOlXsKEiv$zbTKJzW z!3XPZGG{#X!^Lw}vMGBg0fi5-o7s5eGjW)_n#GQy%x5`d9COI1AI>Rl8)`kp$i{(d z{^uwRk+`sMkXtoq*W4mGmh$YvrV=I2PN(gt&MnhwS=%=kp2Z2jI*ryHKDnG=p(GK4 zqzi7Upt|P{ayR>tJw&daF<^lg=i1o9uj&aecmR})Yj9ZLl0S_K#*2&pB|xVV0XeDy z5^Tx?yUGy9+YLY$l^2H>|EeAuf~07N{~-utAr=h{GR7BE)D3j7}3H zRT$-Cc~lwVY>G*6nFDufCb%5BY>>OK2A;?fmrMW=g|V=9yNoqkNNy>GRXl|Ar8R9d zA4WdJND@Y)(?+;u0_IyH^8V(^md!vAC= z1>#|Q{=!NIBX(rO6=}FEu;{f}UA!pXL3}nLFUKS)x=cS$OwkWWP9(>+egqj!D?Ilv zqLEWzkXu~f4DS-k4xS0b<7MN4hpOU$rBW4aN+=%BRo_UaQ*8WxE?8k;!?u(zSt!5nYf;XRL9W6WjM2pbo{+8 z5T6j>Jcy+-oX^2i_ySk!<2KJEVG@j$TIUB7H)4X@F(@u`32cTj0_qu83qQpUptuA8 zY}A_T_@NR%WFDD8cVMBr0UK@L>iTlmu3anz!GoxyK)QkS58kCV?#RIfPpFcP8tNlB z>!Q~Y&kNex+R*rY#yLkXI)ia-T%LOL=#iAk908zPS%P5@qN3=>0Lz8C0NkO1hwUj- zTCaUtEsrX#Wkl)1Z2VHM^`aSd0l`c);8M=b8jlBD)&-o2$6&Lye7M9J48*4D zC#(SH0$9rvqwO%Ngt#gmgTT@9zQ?NyrV$*ZJ=h?l=O->h|18}R0y2V#L$$vnHR80j zwOxmPey~gR9{Q<{J~%NZCT5=1EUsC2?zy5(9GT8-2MBF7(EK`F>1>Lr@3`f=;$r)6 z&yEMhwzQmBZ<4JG-76tYVG-C^G&|0Uk*2Ahs@9SewPZP&1sLv%i|$6vuLr5M5<(cd zy~NM$+@VY1b3V~!llD6}IHccnCEcouO8%RUn&e}IUbZ3h3Pe(2c{@pUMfYE!t2CpG z>$|EG(0SWIh0GrQtW=ffeU`4d+6L4G{*?90hLi@Ty(Zz%eC)}b!tdXWi5vn6$~1K$ zm`DhX220M{+S+zQ4=JyxDBTft>sHL=IYRe5KSD$N_3e4x3(M;Z7A)A}GvVs?>)Y4i zb31pQf1(+kl3Ktv+8S|~aJ*1Kcd1SJ$c?6zsZ=QS=RU$y(rv!(LRndLM^oi!-**)@ z(<1q3+TMydO#_tbjvo2eWl`O=X>l1jobrUxTC4Mf=`Fm5n=FBKHnncenlF$#(6udq zyZTuyF#EySqze}=IL{bB6G7U~MxQa0)K@gL34CHI#za9JMYq~bNJKQAkI?TB&YV8Q zhNw~V^TVAs_Z;~%ExVU1d+9nBOumw|WwXY;Cc5I}K{hM@`IV;V)_oToYDq)&)1zr4 zb@r*Y5VP9ei*=XH*^nF45#{}vW;%A4Hl-RPs=o3_yC;n6Zb1uq8f2A67?I{Ds<{h>zI8r=YP2=e8lA`Ows} zEtoe0#T@AZ0%>eRk*%i&q+O3>K+_f!747S{#V`nsidTBVr)rkvYxlb(SjKdHQ>q!Q zEZXA42~x=3S*@AqJ=RBh?r>0Upj%4i<}SVR0DUIpCEccT!vPN2U8^7xcmD7bOK}we zIa7`ee`#5|_$F#8z*CIF%t^h%j0X1za1*Xjvs3Q{`undL_C_5HtxifkPBU8n#;+PnE`@YS0)tHGPQBF=CwIu3E&IzM6ExDZmdhQ4t$~! znTQZopMGCx5$ZK6`?h+G*c`sU{YY(XZB*~6#@z8M&#lU$l8Z&X4-u&xQV6; zWYFtAY3kcbg2T5tMxi4mYEKeWU!!N{=dHtP%;+YNt93zy3qqfak6%+J_=fg-q9?Tn zTy{Cgh@_`apE`%s=A2Dkuy7#+w|E(Y6dOp>2M!&o#NpJ+izM9@H#I8rHk18Yp5`=N z@&kEZ)Se28MLRoLOlR#&cbsu2EUc?auL#jc7#`>tkGGz#TO%nMTQ`gr%8y^HI#|X4 z6C5~6K1G|*DFg3o)@OWwxOlyhuD8hOgWSnL`)}Jukv_D9Jj7D(cd5)+0{$74!tLZoClIm|`Lk767=F^zz4bEtI;FO)kBPeW;{!6BnM z)hv~0z?rY@O}Q}im`$+`Zlm}Q>4YDko(9!nOoR> zZeL19X^L@{HbPN&hsU$_&yh)x$d3*V4iYLT*1qZ6bC5TY3NBCfy8PaI>c?nAr@nPEbrNm2hB3j~*3dpQ?MQ zevzABAL9>%R6bB_j4=ZYjXVS|L3C6Cw7>frl7N~-YaAUZv1>OdmGe_`x7|yP}&sUKQwk&949g*%6Fp6E}#PO*!J_Mu18O(iqFKFEn9St zp`CZSLD+53L}ost!TL`PAFMz2Y7HcO0>?PG%B4U9n~9OG2^=|;tXpiph=W9t-ZIj( zezMj&x(gKW5eJ79qn9=h9;o?se^?z|Nk~_ccEAKgZKVA(yyto4VF-!{DDQ7B@2MIR z{2|*VXwg(DiefT)`q12@@TSLtz!t1l&u+b`^wMk8j!@3v?{%IWI*sW zWsTzzF1(J*eA+)QCBiCXqA|Na&^UsNetbzv%}+!mbOQ2q)zz=Y+D9+^1xA&~P9N^9 zwnz3Na@5GLv@);iXc|3#7Qg6IhlXrhD1yR(X>f}AeXp!Og+JbC@!kd#B&NkpuP<_A z)aBi#BfOz}2&h%o%-a=RTO^li#8nI%T3J1j`0y?B%%hFYKliHz9nYu_fQGUZ!!@xI zA8hJgCOmg;E7??PZDn;YCjjiEaL^4pt5q!c^W$gFzT7$q6e=4221Mple-DhOcm(!r zFI3^PN~Ll)@cK)T#J5e1^*b8pc~$j{N^3W=i{*bsgp={E!J$O@(&VXU@AvONX2Ejv zPAQ1vD!f4UU~R$d-P!I&XB%@pF<(+1y3t~bYp0gFHq=)lPy@`~C!I-&4$pILyJk>2 z)>Bh9+=01yI}xF|G5iXC>Vr)wHTLkd{Jl6!}eS|9*di9*)ru&EQtJxGEw!Wy|<2isRe&Ep|jk zC891EP1E)C$&n$q2M-?L5y6s?Dn3SFp$QiSPL61*K+VT-aY@*Zs4Qe8{p6WGEvuR?TMdcfzS>_7eg0m}KTp@Wdi%peDS zxiE81;*RoF>O>T4KF_`7Y@hLD-`N74i?b}vz%zz8`sZJJ3EcoxsE(15QC3+_g9E%? zDjXE?c2A!A@Xz8G?hp1vIhg{ojs`q?Mgw%4-kJA!=G~? ztViHX&u686U48v~zLPy>_Sh%H-)*8Kl$x=%6A}`nWo1*GpF-E^hQ?a6GH}O3z{`Pb z_of0(+NAD>4c2O}7#nxJjWr4KE#4 zNGzZ?2@;wjVCb<$%CQU$wJUSK;%GseTMOvegQ~U)>8JA9@ggrTOZm+hX~MD4n3||5 zcnLWJ`g0c&!qB!>o^GEJ?(M|$FB>+F?`Jnu1x9Pi$#2lZvP&Lfj^(7(yx;oY@5m~4_zpZMhH`v?J?7#|=1 zMr2>Kg3I3e496NQPhK4x6n~r3_7P~1-Dn8B23~Vxa%4qUT?ZFRtW?a@*rK5oR&sxe zVIyVI>O_rVP%f1$IP|e_QsF>JAxSj{U78g^4HC6kgJWtC;7~@AvQ+~EKxUKO2BvwU zF^X8GycU4M^4?LE z&6^?O|N5R)aqA>9<5aw>ETHm=qy^g7sA7$1mDBq-x%YjyU2^4k?&RTvBV#1koX%=4B5 z@2j317D;`N=Bv8?`YKHCbd0;8=6(3+ksJ5|xBf;GEU6wuI?^#Oy&?hR812@>Ttzny zJ0|lQVlQ0aRAvnn1v^+k11Ft%}F-*WsRa?|e z&lvwZsgWo9|1dS~PJKl;A=5&A-?QwJFP8ihkC8wcKEpd*vYc6cS64@ULEcLR-$3f? z>46aJm@TjviLrVEef%J5*u>1t%#!C;;A6-%W#En>|9PT2)^yeROsPzc=s@K2R*oaZ zUs;qg8TPWyL6R8w9wY^doe)BAuPzLZjO?xbjAGt#i9NEJ*RSWrTg(foUXT}WPLrXk zYG9Hs2DMOP+x&QQnUG4pgV)VGlpS@~|6QpWZcR$P2+fLG++JKJcJB|zCqPMq%WE|| zJG-zsvMHsg>`2_J1$EG6vUz@HBMp&3Z8^|vH_%3|+_I(8Y5TJGn3a_aa_n6{q8J|QJm2YWn4S=4x6soQq+V&e5+btf>Ep6K*>iKj2N=eDL{#UPV1aowf zRYRY|F#c3N&q-3ex0!*!BEz17pQnThn3v}Z3)@|~a>Yy5KMYjU5fn6CJ)^V4ZcS!D zU=$%`u*>9|2=j(*#seJU-v-Ybx9ytyC|oRYSR$lUDyKnqTDVxHP6l}hs9?H8`S&*q z!IWdVy0J8rJ48;Oho0=?$3sZ$X(gwD(E6B^H9*#f;HuL;e7ORj z+m`P}P6ktuF5I6@LVR|+kA>4ue#T6$;Xj!hLS-ozfb(;UePuHR8~tzGs05cJlRJM) zNpZn4!8z4FNUqPWnfr*13H~h&r{2GRpV#r1aTKauh$`S@sm#y+(VGq|M+O^Edy>by zgyD!*5=oOfFAfcfDgk-A6L^kEBM{D@f~k0@NnA@|uO-MSz_S8Jw-Cg;0O7_NP>jTR z$o1Xg)Ol(ju1_pYZQ^pCwD-rvEG_Q%(VDRwv4VNR>0pT|yEmyEhK7b~)Sb68=al|K z$kc`GK*g_xLJ2C73~r+2y?p5s6JNym$2U*tnlr1WZ&H-Xu>`=7nYo>=@p@?ckdOyF zQ9XTTKF_oSz}O(bn3e#je9-Jf0-%@+ICzKzKn=)q3J?i^(h}lPot6MpO~Azx34n5g z`a&cCs*)Iiil?;1U_@S1)rS~?QKCj+L?i&J63Psb0H|huF7aszpwJA~gxzP*<};=x z0E!I?pGW{yC%>FlALpv}VHw&8-oiiZ{O$ z{T=7{oaS?Vvg=s>x9BU!<8eLcXKkA`(H?&`-lRC&c=-7AFqNwL9?TKza1akK4;Q(FNL>H_t7iZD+jlJbxAdzU-gji)z|vCcdo*^Z>@vUb-vCt9!+8Jz diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 4c4f2fdc240f..36445d1469b7 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -992,21 +992,38 @@ def test_TextBox(ax, toolbar): @image_comparison(['check_radio_buttons.png'], style='mpl20', remove_text=True) def test_check_radio_buttons_image(): ax = get_ax() - # Remove this line when this test image is regenerated. - plt.rcParams['text.kerning_factor'] = 6 + fig = ax.figure + fig.subplots_adjust(left=0.3) - plt.subplots_adjust(left=0.3) - rax1 = plt.axes([0.05, 0.7, 0.15, 0.15]) - rax2 = plt.axes([0.05, 0.2, 0.15, 0.15]) - rb = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) + rax1 = fig.add_axes([0.05, 0.7, 0.2, 0.15]) + rb1 = widgets.RadioButtons(rax1, ('Radio 1', 'Radio 2', 'Radio 3')) with pytest.warns(DeprecationWarning, match='The circles attribute was deprecated'): - rb.circles # Trigger the old-style elliptic radiobuttons. - cb = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), - (False, True, True)) + rb1.circles # Trigger the old-style elliptic radiobuttons. + + rax2 = fig.add_axes([0.05, 0.5, 0.2, 0.15]) + cb1 = widgets.CheckButtons(rax2, ('Check 1', 'Check 2', 'Check 3'), + (False, True, True)) with pytest.warns(DeprecationWarning, match='The rectangles attribute was deprecated'): - cb.rectangles # Trigger old-style Rectangle check boxes + cb1.rectangles # Trigger old-style Rectangle check boxes + + rax3 = fig.add_axes([0.05, 0.3, 0.2, 0.15]) + rb3 = widgets.RadioButtons( + rax3, ('Radio 1', 'Radio 2', 'Radio 3'), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + radio_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}) + + rax4 = fig.add_axes([0.05, 0.1, 0.2, 0.15]) + cb4 = widgets.CheckButtons( + rax4, ('Check 1', 'Check 2', 'Check 3'), (False, True, True), + label_props={'fontsize': [8, 12, 16], + 'color': ['red', 'green', 'blue']}, + frame_props={'edgecolor': ['red', 'green', 'blue'], + 'facecolor': ['mistyrose', 'palegreen', 'lightblue']}, + check_props={'color': ['red', 'green', 'blue']}) @check_figures_equal(extensions=["png"]) @@ -1019,6 +1036,21 @@ def test_radio_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") +@check_figures_equal(extensions=['png']) +def test_radio_buttons_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + radio_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + label_props=label_props, radio_props=radio_props) + + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee']) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) @@ -1031,6 +1063,29 @@ def test_check_buttons(fig_test, fig_ref): ax.text(.25, 1/3, "coffee", transform=ax.transAxes, va="center") +@check_figures_equal(extensions=['png']) +def test_check_button_props(fig_test, fig_ref): + label_props = {'color': ['red'], 'fontsize': [24]} + frame_props = {'facecolor': 'green', 'edgecolor': 'blue', 'linewidth': 2} + check_props = {'facecolor': 'red', 'linewidth': 2} + + widgets.CheckButtons(fig_ref.subplots(), ['tea', 'coffee'], [True, True], + label_props=label_props, frame_props=frame_props, + check_props=check_props) + + cb = widgets.CheckButtons(fig_test.subplots(), ['tea', 'coffee'], + [True, True]) + cb.set_label_props(label_props) + # Setting the label size automatically increases default marker size, so we + # need to do that here as well. + cb.set_frame_props({**frame_props, 's': (24 / 2)**2}) + # FIXME: Axes.scatter promotes facecolor to edgecolor on unfilled markers, + # but Collection.update doesn't do that (it forgot the marker already). + # This means we cannot pass facecolor to both setters directly. + check_props['edgecolor'] = check_props.pop('facecolor') + cb.set_check_props({**check_props, 's': (24 / 2)**2}) + + @check_figures_equal(extensions=["png"]) def test_check_buttons_rectangles(fig_test, fig_ref): # Test should be removed once .rectangles is removed diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 1dc1dd5f77ae..bdcee1d21ca7 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -11,13 +11,15 @@ from contextlib import ExitStack import copy +import itertools from numbers import Integral, Number +from cycler import cycler import numpy as np import matplotlib as mpl -from . import (_api, _docstring, backend_tools, cbook, colors, ticker, - transforms) +from . import (_api, _docstring, backend_tools, cbook, collections, colors, + text as mtext, ticker, transforms) from .lines import Line2D from .patches import Circle, Rectangle, Ellipse, Polygon from .transforms import TransformedPatchPath, Affine2D @@ -966,6 +968,11 @@ def on_changed(self, func): return self._observers.connect('changed', lambda val: func(val)) +def _expand_text_props(props): + props = cbook.normalize_kwargs(props, mtext.Text) + return cycler(**props)() if props else itertools.repeat({}) + + class CheckButtons(AxesWidget): r""" A GUI neutral set of check buttons. @@ -989,7 +996,8 @@ class CheckButtons(AxesWidget): each box, but have ``set_visible(False)`` when its box is not checked. """ - def __init__(self, ax, labels, actives=None, *, useblit=True): + def __init__(self, ax, labels, actives=None, *, useblit=True, + label_props=None, frame_props=None, check_props=None): """ Add check buttons to `matplotlib.axes.Axes` instance *ax*. @@ -1005,9 +1013,22 @@ def __init__(self, ax, labels, actives=None, *, useblit=True): useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. + label_props : dict, optional + Dictionary of `.Text` properties to be used for the labels. + frame_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button frame. Defaults (label font size / 2)**2 size, black + edgecolor, no facecolor, and 1.0 linewidth. + check_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + check button check. Defaults to (label font size / 2)**2 size, + black color, and 1.0 linewidth. """ super().__init__(ax) + _api.check_isinstance((dict, None), label_props=label_props, + frame_props=frame_props, check_props=check_props) + ax.set_xticks([]) ax.set_yticks([]) ax.set_navigate(False) @@ -1019,22 +1040,39 @@ def __init__(self, ax, labels, actives=None, *, useblit=True): self._background = None ys = np.linspace(1, 0, len(labels)+2)[1:-1] - text_size = mpl.rcParams["font.size"] / 2 + label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment="left", verticalalignment="center") - for y, label in zip(ys, labels)] - - self._squares = ax.scatter( - [0.15] * len(ys), ys, marker='s', s=text_size**2, - c="none", linewidth=1, transform=ax.transAxes, edgecolor="k" - ) - self._crosses = ax.scatter( - [0.15] * len(ys), ys, marker='x', linewidth=1, s=text_size**2, - c=["k" if active else "none" for active in actives], - transform=ax.transAxes, animated=self._useblit, - ) + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + frame_props = { + 's': text_size**2, + 'linewidth': 1, + **cbook.normalize_kwargs(frame_props, collections.PathCollection), + 'marker': 's', + 'transform': ax.transAxes, + } + frame_props.setdefault('facecolor', frame_props.get('color', 'none')) + frame_props.setdefault('edgecolor', frame_props.pop('color', 'black')) + self._frames = ax.scatter([0.15] * len(ys), ys, **frame_props) + check_props = { + 'linewidth': 1, + 's': text_size**2, + **cbook.normalize_kwargs(check_props, collections.PathCollection), + 'marker': 'x', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + check_props.setdefault('facecolor', check_props.pop('color', 'black')) + self._checks = ax.scatter([0.15] * len(ys), ys, **check_props) + # The user may have passed custom colours in check_props, so we need to + # create the checks (above), and modify the visibility after getting + # whatever the user set. + self._init_status(actives) self.connect_event('button_press_event', self._clicked) if self._useblit: @@ -1047,7 +1085,7 @@ def _clear(self, event): if self.ignore(event): return self._background = self.canvas.copy_from_bbox(self.ax.bbox) - self.ax.draw_artist(self._crosses) + self.ax.draw_artist(self._checks) if hasattr(self, '_lines'): for l1, l2 in self._lines: self.ax.draw_artist(l1) @@ -1066,18 +1104,65 @@ def _clicked(self, event): and y0 <= pclicked[1] <= y0 + p.get_height())): distances[i] = np.linalg.norm(pclicked - p.get_center()) else: - _, square_inds = self._squares.contains(event) - coords = self._squares.get_offset_transform().transform( - self._squares.get_offsets() + _, frame_inds = self._frames.contains(event) + coords = self._frames.get_offset_transform().transform( + self._frames.get_offsets() ) for i, t in enumerate(self.labels): - if (i in square_inds["ind"] + if (i in frame_inds["ind"] or t.get_window_extent().contains(event.x, event.y)): distances[i] = np.linalg.norm(pclicked - coords[i]) if len(distances) > 0: closest = min(distances, key=distances.get) self.set_active(closest) + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) + + def set_frame_props(self, props): + """ + Set properties of the check button frames. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button frames. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._frames.update(props) + + def set_check_props(self, props): + """ + Set properties of the check button checks. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the check + button check. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + actives = self.get_status() + self._checks.update(props) + # If new colours are supplied, then we must re-apply the status. + self._init_status(actives) + def set_active(self, index): """ Toggle (activate or deactivate) a check button by index. @@ -1097,15 +1182,15 @@ def set_active(self, index): if index not in range(len(self.labels)): raise ValueError(f'Invalid CheckButton index: {index}') - cross_facecolors = self._crosses.get_facecolor() - cross_facecolors[index] = colors.to_rgba( - "black" - if colors.same_color( - cross_facecolors[index], colors.to_rgba("none") - ) - else "none" + invisible = colors.to_rgba('none') + + facecolors = self._checks.get_facecolor() + facecolors[index] = ( + self._active_check_colors[index] + if colors.same_color(facecolors[index], invisible) + else invisible ) - self._crosses.set_facecolor(cross_facecolors) + self._checks.set_facecolor(facecolors) if hasattr(self, "_lines"): l1, l2 = self._lines[index] @@ -1116,7 +1201,7 @@ def set_active(self, index): if self._useblit: if self._background is not None: self.canvas.restore_region(self._background) - self.ax.draw_artist(self._crosses) + self.ax.draw_artist(self._checks) if hasattr(self, "_lines"): for l1, l2 in self._lines: self.ax.draw_artist(l1) @@ -1128,12 +1213,28 @@ def set_active(self, index): if self.eventson: self._observers.process('clicked', self.labels[index].get_text()) + def _init_status(self, actives): + """ + Initialize properties to match active status. + + The user may have passed custom colours in *check_props* to the + constructor, or to `.set_check_props`, so we need to modify the + visibility after getting whatever the user set. + """ + self._active_check_colors = self._checks.get_facecolor() + if len(self._active_check_colors) == 1: + self._active_check_colors = np.repeat(self._active_check_colors, + len(actives), axis=0) + self._checks.set_facecolor( + [ec if active else "none" + for ec, active in zip(self._active_check_colors, actives)]) + def get_status(self): """ Return a list of the status (True/False) of all of the check buttons. """ return [not colors.same_color(color, colors.to_rgba("none")) - for color in self._crosses.get_facecolors()] + for color in self._checks.get_facecolors()] def on_clicked(self, func): """ @@ -1147,7 +1248,8 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def rectangles(self): if not hasattr(self, "_rectangles"): @@ -1162,7 +1264,7 @@ def rectangles(self): ) for i, y in enumerate(ys) ] - self._squares.set_visible(False) + self._frames.set_visible(False) for rectangle in rectangles: self.ax.add_patch(rectangle) if not hasattr(self, "_lines"): @@ -1170,12 +1272,13 @@ def rectangles(self): _ = self.lines return self._rectangles - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def lines(self): if not hasattr(self, "_lines"): ys = np.linspace(1, 0, len(self.labels)+2)[1:-1] - self._crosses.set_visible(False) + self._checks.set_visible(False) dy = 1. / (len(self.labels) + 1) w, h = dy / 2, dy / 2 self._lines = [] @@ -1485,7 +1588,7 @@ class RadioButtons(AxesWidget): """ def __init__(self, ax, labels, active=0, activecolor='blue', *, - useblit=True): + useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. @@ -1499,9 +1602,25 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, The index of the initially selected button. activecolor : color The color of the selected button. + + .. note:: + If a facecolor is supplied in *radio_props*, it will override + *activecolor*. This may be used to provide an active color per + button. useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. + label_props : dict or list of dict, optional + Dictionary of `.Text` properties to be used for the labels. + radio_props : dict, optional + Dictionary of scatter `.Collection` properties to be used for the + radio buttons. Defaults to (label font size / 2)**2 size, black + edgecolor, and *activecolor* facecolor (when active). + + .. note:: + If a facecolor is supplied in *radio_props*, it will override + *activecolor*. This may be used to provide an active color per + button. """ super().__init__(ax) self.activecolor = activecolor @@ -1512,19 +1631,42 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, ax.set_navigate(False) ys = np.linspace(1, 0, len(labels) + 2)[1:-1] - text_size = mpl.rcParams["font.size"] / 2 self._useblit = useblit and self.canvas.supports_blit self._background = None + _api.check_isinstance((dict, None), label_props=label_props, + radio_props=radio_props) + + label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, - horizontalalignment="left", verticalalignment="center") - for y, label in zip(ys, labels)] - self._buttons = ax.scatter( - [.15] * len(ys), ys, transform=ax.transAxes, s=text_size**2, - c=[activecolor if i == active else "none" for i in range(len(ys))], - edgecolor="black", animated=self._useblit) + horizontalalignment="left", verticalalignment="center", + **props) + for y, label, props in zip(ys, labels, label_props)] + text_size = np.array([text.get_fontsize() for text in self.labels]) / 2 + + radio_props = { + 's': text_size**2, + **cbook.normalize_kwargs(radio_props, collections.PathCollection), + 'marker': 'o', + 'transform': ax.transAxes, + 'animated': self._useblit, + } + radio_props.setdefault('edgecolor', radio_props.get('color', 'black')) + radio_props.setdefault('facecolor', + radio_props.pop('color', activecolor)) + self._buttons = ax.scatter([.15] * len(ys), ys, **radio_props) + # The user may have passed custom colours in radio_props, so we need to + # create the radios, and modify the visibility after getting whatever + # the user set. + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, len(labels), + axis=0) + self._buttons.set_facecolor( + [activecolor if i == active else "none" + for i, activecolor in enumerate(self._active_colors)]) self.connect_event('button_press_event', self._clicked) if self._useblit: @@ -1564,6 +1706,42 @@ def _clicked(self, event): closest = min(distances, key=distances.get) self.set_active(closest) + def set_label_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Text` properties to be used for the labels. + """ + _api.check_isinstance(dict, props=props) + props = _expand_text_props(props) + for text, prop in zip(self.labels, props): + text.update(prop) + + def set_radio_props(self, props): + """ + Set properties of the `.Text` labels. + + Parameters + ---------- + props : dict + Dictionary of `.Collection` properties to be used for the radio + buttons. + """ + _api.check_isinstance(dict, props=props) + if 's' in props: # Keep API consistent with constructor. + props['sizes'] = np.broadcast_to(props.pop('s'), len(self.labels)) + self._buttons.update(props) + self._active_colors = self._buttons.get_facecolor() + if len(self._active_colors) == 1: + self._active_colors = np.repeat(self._active_colors, + len(self.labels), axis=0) + self._buttons.set_facecolor( + [activecolor if text.get_text() == self.value_selected else "none" + for text, activecolor in zip(self.labels, self._active_colors)]) + def set_active(self, index): """ Select button with number *index*. @@ -1575,7 +1753,7 @@ def set_active(self, index): self.value_selected = self.labels[index].get_text() button_facecolors = self._buttons.get_facecolor() button_facecolors[:] = colors.to_rgba("none") - button_facecolors[index] = colors.to_rgba(self.activecolor) + button_facecolors[index] = colors.to_rgba(self._active_colors[index]) self._buttons.set_facecolor(button_facecolors) if hasattr(self, "_circles"): # Remove once circles is removed. for i, p in enumerate(self._circles): @@ -1609,7 +1787,8 @@ def disconnect(self, cid): """Remove the observer with connection id *cid*.""" self._observers.disconnect(cid) - @_api.deprecated("3.7") + @_api.deprecated("3.7", + addendum="Any custom property styling may be lost.") @property def circles(self): if not hasattr(self, "_circles"): From 1a77aa6b5a60f8ef020263fd48ead7252886078c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 12 Jan 2023 23:43:39 -0500 Subject: [PATCH 2/4] Warn if activecolor and a facecolor are provided --- lib/matplotlib/tests/test_widgets.py | 9 +++++++++ lib/matplotlib/widgets.py | 30 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 36445d1469b7..871faa93ff02 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1051,6 +1051,15 @@ def test_radio_buttons_props(fig_test, fig_ref): cb.set_radio_props({**radio_props, 's': (24 / 2)**2}) +def test_radio_button_active_conflict(ax): + with pytest.warns(UserWarning, + match=r'Both the \*activecolor\* parameter'): + rb = widgets.RadioButtons(ax, ['tea', 'coffee'], activecolor='red', + radio_props={'facecolor': 'green'}) + # *radio_props*' facecolor wins over *activecolor* + assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index bdcee1d21ca7..63ae6b605de5 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1587,7 +1587,7 @@ class RadioButtons(AxesWidget): The label text of the currently selected button. """ - def __init__(self, ax, labels, active=0, activecolor='blue', *, + def __init__(self, ax, labels, active=0, activecolor=None, *, useblit=True, label_props=None, radio_props=None): """ Add radio buttons to an `~.axes.Axes`. @@ -1601,12 +1601,8 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, active : int The index of the initially selected button. activecolor : color - The color of the selected button. - - .. note:: - If a facecolor is supplied in *radio_props*, it will override - *activecolor*. This may be used to provide an active color per - button. + The color of the selected button. The default is ``'blue'`` if not + specified here or in *radio_props*. useblit : bool, default: True Use blitting for faster drawing if supported by the backend. See the tutorial :doc:`/tutorials/advanced/blitting` for details. @@ -1623,6 +1619,21 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, button. """ super().__init__(ax) + + _api.check_isinstance((dict, None), label_props=label_props, + radio_props=radio_props) + + radio_props = cbook.normalize_kwargs(radio_props, + collections.PathCollection) + if activecolor is not None: + if 'facecolor' in radio_props: + _api.warn_external( + 'Both the *activecolor* parameter and the *facecolor* ' + 'key in the *radio_props* parameter has been specified. ' + '*activecolor* will be ignored.') + else: + activecolor = 'blue' # Default. + self.activecolor = activecolor self.value_selected = labels[active] @@ -1635,9 +1646,6 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, self._useblit = useblit and self.canvas.supports_blit self._background = None - _api.check_isinstance((dict, None), label_props=label_props, - radio_props=radio_props) - label_props = _expand_text_props(label_props) self.labels = [ ax.text(0.25, y, label, transform=ax.transAxes, @@ -1648,7 +1656,7 @@ def __init__(self, ax, labels, active=0, activecolor='blue', *, radio_props = { 's': text_size**2, - **cbook.normalize_kwargs(radio_props, collections.PathCollection), + **radio_props, 'marker': 'o', 'transform': ax.transAxes, 'animated': self._useblit, From 5c9e1fd84c54fc319a6c8c9c7f9176407c1cc4b7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Jan 2023 02:18:39 -0500 Subject: [PATCH 3/4] Handle modification of RadioButtons.activecolor --- lib/matplotlib/tests/test_widgets.py | 11 +++++++++++ lib/matplotlib/widgets.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 871faa93ff02..5eb32e69901a 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -1060,6 +1060,17 @@ def test_radio_button_active_conflict(ax): assert mcolors.same_color(rb._buttons.get_facecolor(), ['green', 'none']) +@check_figures_equal(extensions=['png']) +def test_radio_buttons_activecolor_change(fig_test, fig_ref): + widgets.RadioButtons(fig_ref.subplots(), ['tea', 'coffee'], + activecolor='green') + + # Test property setter. + cb = widgets.RadioButtons(fig_test.subplots(), ['tea', 'coffee'], + activecolor='red') + cb.activecolor = 'green' + + @check_figures_equal(extensions=["png"]) def test_check_buttons(fig_test, fig_ref): widgets.CheckButtons(fig_test.subplots(), ["tea", "coffee"], [True, True]) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 63ae6b605de5..5f86cfffb0b4 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1634,7 +1634,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, else: activecolor = 'blue' # Default. - self.activecolor = activecolor + self._activecolor = activecolor self.value_selected = labels[active] ax.set_xticks([]) @@ -1750,6 +1750,21 @@ def set_radio_props(self, props): [activecolor if text.get_text() == self.value_selected else "none" for text, activecolor in zip(self.labels, self._active_colors)]) + @property + def activecolor(self): + return self._activecolor + + @activecolor.setter + def activecolor(self, activecolor): + colors._check_color_like(activecolor=activecolor) + self._activecolor = activecolor + self.set_radio_props({'facecolor': activecolor}) + # Make sure the deprecated version is updated. + # Remove once circles is removed. + labels = [label.get_text() for label in self.labels] + with cbook._setattr_cm(self, eventson=False): + self.set_active(labels.index(self.value_selected)) + def set_active(self, index): """ Select button with number *index*. From 538a7d5071c697ded1dc655f677af3f44f0f3870 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 19 Jan 2023 14:48:14 -0500 Subject: [PATCH 4/4] Add what's new docs for button styling --- .../next_whats_new/widget_button_styling.rst | 31 +++++++++++++++++++ lib/matplotlib/widgets.py | 20 ++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 doc/users/next_whats_new/widget_button_styling.rst diff --git a/doc/users/next_whats_new/widget_button_styling.rst b/doc/users/next_whats_new/widget_button_styling.rst new file mode 100644 index 000000000000..4307e6977fc5 --- /dev/null +++ b/doc/users/next_whats_new/widget_button_styling.rst @@ -0,0 +1,31 @@ +Custom styling of button widgets +-------------------------------- + +Additional custom styling of button widgets may be achieved via the +*label_props* and *radio_props* arguments to `.RadioButtons`; and the +*label_props*, *frame_props*, and *check_props* arguments to `.CheckButtons`. + +.. plot:: + + from matplotlib.widgets import CheckButtons, RadioButtons + + fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(5, 2), width_ratios=[1, 2]) + default_rb = RadioButtons(ax[0, 0], ['Apples', 'Oranges']) + styled_rb = RadioButtons(ax[0, 1], ['Apples', 'Oranges'], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + radio_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}) + + default_cb = CheckButtons(ax[1, 0], ['Apples', 'Oranges'], + actives=[True, True]) + styled_cb = CheckButtons(ax[1, 1], ['Apples', 'Oranges'], + actives=[True, True], + label_props={'color': ['red', 'orange'], + 'fontsize': [16, 20]}, + frame_props={'edgecolor': ['red', 'orange'], + 'facecolor': ['mistyrose', 'peachpuff']}, + check_props={'color': ['darkred', 'darkorange']}) + + ax[0, 0].set_title('Default') + ax[0, 1].set_title('Stylized') diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 5f86cfffb0b4..2365220770cc 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1015,14 +1015,20 @@ def __init__(self, ax, labels, actives=None, *, useblit=True, See the tutorial :doc:`/tutorials/advanced/blitting` for details. label_props : dict, optional Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 frame_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the check button frame. Defaults (label font size / 2)**2 size, black edgecolor, no facecolor, and 1.0 linewidth. + + .. versionadded:: 3.7 check_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the check button check. Defaults to (label font size / 2)**2 size, black color, and 1.0 linewidth. + + .. versionadded:: 3.7 """ super().__init__(ax) @@ -1120,6 +1126,8 @@ def set_label_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1134,6 +1142,8 @@ def set_frame_props(self, props): """ Set properties of the check button frames. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1149,6 +1159,8 @@ def set_check_props(self, props): """ Set properties of the check button checks. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1608,6 +1620,8 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, See the tutorial :doc:`/tutorials/advanced/blitting` for details. label_props : dict or list of dict, optional Dictionary of `.Text` properties to be used for the labels. + + .. versionadded:: 3.7 radio_props : dict, optional Dictionary of scatter `.Collection` properties to be used for the radio buttons. Defaults to (label font size / 2)**2 size, black @@ -1617,6 +1631,8 @@ def __init__(self, ax, labels, active=0, activecolor=None, *, If a facecolor is supplied in *radio_props*, it will override *activecolor*. This may be used to provide an active color per button. + + .. versionadded:: 3.7 """ super().__init__(ax) @@ -1718,6 +1734,8 @@ def set_label_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict @@ -1732,6 +1750,8 @@ def set_radio_props(self, props): """ Set properties of the `.Text` labels. + .. versionadded:: 3.7 + Parameters ---------- props : dict