From d32604c5dc18f14b3ebdcdec7779b2461a334087 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Fri, 1 Sep 2023 18:02:55 +0200 Subject: [PATCH 01/11] coloring shapes by categorical variable --- src/spatialdata_plot/pl/render.py | 9 ++++++--- src/spatialdata_plot/pl/utils.py | 7 +++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 6e264532..0f9c8feb 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -116,9 +116,12 @@ def _render_shapes( cax = ax.add_collection(_cax) # Using dict.fromkeys here since set returns in arbitrary order - palette = ( - ListedColormap(dict.fromkeys(color_vector)) if render_params.palette is None else render_params.palette - ) + # remove the color of NaN values, else it might be assigned to a category + # order of color in the palette should agree to order of occurence + if color_source_vector is None: + palette = ListedColormap(dict.fromkeys(color_vector)) + else: + palette = ListedColormap(dict.fromkeys(color_vector[~color_source_vector.isnull()])) # print(len(set(color_vector)) == 1) # print(set(color_source_vector[0]) == to_hex(render_params.cmap_params.na_color)) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index d60e53a1..2b35033c 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -841,8 +841,9 @@ def _set_color_source_vec( if groups is not None: color_source_vector = color_source_vector.remove_categories(categories.difference(groups)) + categories = groups - color_map = dict(zip(categories, _get_colors_for_categorical_obs(categories))) + color_map = dict(zip(categories, _get_colors_for_categorical_obs(categories, palette))) # color_map = _get_palette( # adata=adata, cluster_key=value_to_plot, categories=categories, palette=palette, alpha=alpha # ) @@ -1004,7 +1005,9 @@ def _decorate_axs( # Adding legends if is_categorical_dtype(color_source_vector): - clusters = color_source_vector.categories + # order of clusters should agree to palette order + clusters = color_source_vector.unique() + clusters = clusters[~clusters.isnull()] palette = _get_palette( adata=adata, cluster_key=value_to_plot, categories=clusters, palette=palette, alpha=alpha ) From 24c1392ca890c1163cb1ecbf4ff749d712678d93 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Fri, 1 Sep 2023 18:40:47 +0200 Subject: [PATCH 02/11] update for case of array instead of categorical --- src/spatialdata_plot/pl/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 0f9c8feb..41d69fa3 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -121,7 +121,7 @@ def _render_shapes( if color_source_vector is None: palette = ListedColormap(dict.fromkeys(color_vector)) else: - palette = ListedColormap(dict.fromkeys(color_vector[~color_source_vector.isnull()])) + palette = ListedColormap(dict.fromkeys(color_vector[~pd.Categorical(color_source_vector).isnull()])) # print(len(set(color_vector)) == 1) # print(set(color_source_vector[0]) == to_hex(render_params.cmap_params.na_color)) From d171e59b3f0596a64a3a47c0663682a8d215ad50 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Mon, 4 Sep 2023 10:47:05 +0200 Subject: [PATCH 03/11] unittests added --- CHANGELOG.md | 1 + ...Shapes_can_plot_with_group_and_palette.png | Bin 0 -> 8980 bytes .../Shapes_can_plot_with_group_arg.png | Bin 0 -> 8960 bytes tests/pl/test_render_shapes.py | 21 ++++++++++++++++++ 4 files changed, 22 insertions(+) create mode 100644 tests/_images/Shapes_can_plot_with_group_and_palette.png create mode 100644 tests/_images/Shapes_can_plot_with_group_arg.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b138c91..17a30256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning][]. - Legend order is now deterministic (#143) - Images no longer normalised by default (#150) +- Shapes can be colored by categorical variable with the `group` and `palette` arguments (#153) ## [0.0.4] - 2023-08-11 diff --git a/tests/_images/Shapes_can_plot_with_group_and_palette.png b/tests/_images/Shapes_can_plot_with_group_and_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..dacb1d6c5c1adbfe09ffbd550f22e05ea3121eff GIT binary patch literal 8980 zcmX|Hby!qSxCSJaE@`AYB~)VR?vNH%5EhW`SVFp6mX?ri=>|z*DFNx0UV5eT?(g2` zxqqB9XXg3lOq`kTyzl!>w6>-aJ`Obw3JMCoin6>eGR^<*0$?KVv6D8u$OP40S4kG7 zW`gzrnL)Ra(U3twsY}Ftu)sj(v0apn-BC~o2L8KHq1*&C$U;#M1tX95&ek5@7H(h^ z4GRw!2WJn5&z~5)z;5oJot=dE-th_YFxYx{xVVe)^E>{(0iUy*4L_&Z&KDFECMy+r z89g7+@i%OLy`9--LPlJb1w$EaX)g-tv>2v*<=(&RAu)~moOiOQ-Yj7ILo8z4?@t&n ziHgu-7__DHxk7$&8(rx$g#8E)ApAfw{3N-~?(WJq!qOlhBrr)aVRN!Fwz9Hv-?5it z!=GGD6v`+awgAG92r@^v1#l<^wGG2y(`ngpad^uHpqtDQD_}oBJJ?51NT})T@77jR z-MD06T2>a9S{jclQR(9qq0|Gz-o&?=Wz6f>XnHK{?4$76N-(m-#POIb8{2Y(<>2& zLe?LF|Ml{4epHRWi^amiLePCzz3s53+uRf= z^pJxzk$w4+fynGDn?~clm?xYCi;yl=!tY8smYnw@u+Oc@Znj7j$~9eOEM(kho6MKf zXg90M2kIf;e7M3$Q=ENDS{9p5 zvJMF~3D^8|?P8#v3x(5jI4*_95^>bazDij@)3em`LqlLl8P*nddnP#@IbTa^mjd}9 zZwg)BbyDj;SKS7G4QyQe(d&OhI4!!xsv~P%e&F@)faVVP$4*30sphv7vktG3P(sNv zjQzm^Qzp?j#_bOsu@0;C`qSLSYUM?`(=F~FeeZ)?Sh;{)?D8}#A&sHr^oWl*rcXmM zK@KUn=dfsE-Ex{O6~;-+kMin}o)jYWa}cvrYRe5x|4{?(!~V&o5fDGVVr$||n<=v0|s zQe(V7tBuV~#rFJf<5_v$uN{syG9H?pYTZx^#W%Q0s}F}cIl<5Ek+b#Z=AF}uk=<+s zYs-pTY)xu$UBJO~@pPA;_oKU7kY3-kBlf9s)D^4oqOsdHf;jVHizTz(DaJv(OG9v! zlV7dqeUIceBr2>V9LDTPpG@(ghvSj{gg;>EI{7T6d!NXhpRM`)#HZ5?#=olEp_~paG>vLSQ+^t3p9IJ@Kw4IP(7*x0%S9T1_M&C2g`PyjGei(eTXP8;Ban&pc6&@cF6^k^Ef81}XZrh@7rQ&C zObJGIru+IhbK5seKa7Hk%9pvvav9`rUTRjG54Jq$F4px^c2w!k@t5;w{i>#V5p8=g zSCP%)pUTj`jGw+AWy?SC%k|u71U9^G!dY33cP>%l5HEPICc9I`9qMm-)g9j5-MUnjDyMX)fg ztY?nZ847Ag{=9BTy3IVtl2HF$}a&(JA1xj-*{s=tk@wY2Fe$M&#c zRNM|cVX5wlBPZ}gn5qTXZ=$3Z{JubDU9CNO0j`Of{GMFj z4TBwc`)>M|>^M-H4mqQBdOW67sTr}Gm3lyDN;TEOqi}SEDoV!SQflPS7Ik{u?=|KR z$OytCzQW3C3(3&5=Jcs0w>rb?Wx7wbv6VUoOPj=#hT$_?O#9lb{U<00`RL)jXb@8WpUhB z3JxgG;$RTid~e{WcE}bK1GO_t;sS+o)9(5nq;G)en1*fTXhp`ntZx)dG(U5GoY3jx zWESk6^jVa!ZSg2ul~Gkn?VhSnp^#S!Ms=OKSqCke!6LZ(?-><4%`Cn*Wdqo&^I^OPnD-;c_bm#8Jh zr6W10C|W0-sez&)yXuco=+v2(cs1E4vr8ZH zDcBHqf9M^2a_8YoW!&B1$}Ph&?1EIdHAD*|BlBDN;SC#4M*iU~wV^Z5mxBw6TlZxt zwJlh@XOrrtL|XYozDw{KputC6O9IEjJHLJ}mCT@ci_b@&>xVc;!*jNebQz{#YsfrW z5dU(|G-73Gj;ciVSU#<_YG>|$xe#pwpvBCbf`0=Bku3Qy-IQnfe4i zR6}Q1bvB{Y<#r!iPAxh>a`-lT0=s4digF(Kl5N6Nf;rFWS#PcN6yJEx zK2u&%9x^3PMYtb-y>Z>Fr)ZYf5R$-eLHSb(Iy-;rw((F-D*Q6S5Dib~xuyg(+okt( z^>h6qV|D+vY~DMc_lXfVfDYlFTX&dn=aC}#>Wg@nqdFxYUM+e?@sj{inW?GPrs1rF6qOk1u|Glnzv#)&Z<>tgzg#l5 z7Oe~jAL)`;mfb+MHER*JE&1--U7BOmOsPdgR9uqfn$^H(sjp?;za3%Uh{iMm^{CcP zHNo9tgw7km8~*zz^p!^$f}ujRCzL{_o+m5DC*Ai>64K!^aCK>6Pq?0h#XG4M^r*A9 zj!P`*{2%9Lyl*hk_w5+bwSH-oD7m|Xdl@8gvmW-3ROfjyxv6qCUFOf+;-0~rXeHGF zCvAI;>^$#F;^^u>OJQJv5<$FpCiU_M5`d%$tQwaN!Mlq`MdR1eggzoqWq7CRLiq=r z$9R$A-WxwrB?#Rd9IE_pHX|X;+Plh6^F2?jU69KZwRFB!Kuc6gCTYf>A@6_8%+iJf z>Ozz+;)wEUvlsj^bb{GLBgCdZEGN+uY=w+GXXHmQW~Mq2lM68ued3JUI6kuJMeMY0 z&}JY)9?BWYJ{8pE5ft}wN&4+)c7dqYem`qV^Hfm@+YVs}tztGeAQ(_7tLo4tEga+UhKH7C6<{c5DK{)FX> z`xi8Az6V!BG3qM@Nx2q4k67bH4c?P!1*05A82lLb);+Rx2kJrF@B339WsG&s{jYAG zgGKY)KqZ0V`g3_kwc=P<+%jxGR~R#BlqQs-Nl@3uZl41sUSC|sHhml(dZb}x;gL@O zm9YD|kXWHvWfV(*p;Mb*o%D0BYB)hekax684E=kA>0|KBl#CP(8m-?tOd{BVs7n-f z94?LT->x@1x`exr>}|Jfec>+GpS{#uR8`cCaFFbcBkENl!u~C(p7c+mAdd45+Ehca zZC~opv329u)E}uif;8Um)L5=es|c451Jsa~6?_bng+FAtBiS1)$h3$SI;6U|u?XqM z%1c|c)Bi0%E!~dXT$)v|l2NWX3d5Go7Ky;KsZ^%m`|@MNqE?=YqEUpULa;`V--K&G zXd(j%L$n(W7|+w8s;X@E5`$n)o2m(tn!6w|Nkqvylsd{;e@!d*0cl9g0R8R3mrs=t z9U8LV25XUBeOeRaC6o=FG|`!q|dBS_17=90tNznCMNH z+XhrkZmCvQT+p3)ZUWQ_HR>;npu~0Tw;}RDf6fIQYsziCB4g+I;A9w@-$eCYz`-PJ zXuwpT;bvwZz;`1XV4gS; zsNQX@enZ0aR%X|e*_le9%S^e3+gs7r61)*+X~kZFs~3fljk5=-$19`tCjEi|>jJY$ z^r;X-S`efA+(dHKZ=UOEYgTD;N)cq8`r;Rh9!DjYB&DD#LSVq1$A4tF z%A`s4t@B!yBND+v%-eU4iPj?UY5ajaGaGm{_bHBQ+a2TUDqaengg|UD_*+MjI>s_F zHW5Hl(}qctU9lYZ54XGSfcp#OHrK781E>Hm>a#VDC=gw_(cUqZ%8N#ziBH&p<;YWo#ih$W5y)#U!1V>nrZjVU)+PK z!pNsK=qaOW-)juguP@es`&;w7A01gZfg_h(6gFWMz%$(vT6Mf!R~XW_WKuCQGRo9a zjk&0kSJ%8RO}O$B_d5E`*rGye9Rv&3oxM6<4iMp6FehwV@;hAn`Yyej^Rvb0?$71w zFR5+^ly8;pzbts6PqmuMBMFhj|Ed-#kKQd!yTO{9&HiB#GLRQPd9byo zablB*v*kx{a{gmun1p&Xm5fKWlJ9me?@L-VDf`mZZlaDJk?g)1b&p>6(v!Yscmw}w z3eJ|w-q-DIeeXSsBBDEJeIitVKhYgk=-2Od&qEeIKFxV{ifxQ|lu#nB*hpS_wuAD6 zH430xA@H-8QFE2#nC#k#bZ__@!wl+h&|%eHij=yGD#*A{5&vMdqlNFiNI&59u(`JH z*tLM0@MhUo&R(~CG(=^EWSPd$VuveC-u;LZT$#Ev`CTD)JO15TQi(=3Y_Y-SGDmLW zH5#7aS)d1X6f7>~w>^A__yDLfkHe%JZ8gb+tWH%L2dZs^2%xfu{zB}9Q-G2f)|hOVTd@CSQJ2V(>1mJXO>goLqgoKsKJxVDSeUJIG=8KXu0!q zFoa^;TJYZTSHc0ocw^X)i8!P-7s~+<5CrOi?#9*XMXeju0|F~jqj{K%`BT$U=BY4C zCH0J2^*C3nBpQnRi?&wQZ+p#4_)fi&eeHKg3LEHV|7nR^PUMIpseWO{p++vy*z0u7 z@m>Xle6vW{E|TE-wx0C6?Z?ZJNU?!T-r4d_DHXB`XFeG=6YfUBUPvw8Me8M0nA#cz zJa){pvI;9qj~1)-EutGY&ihLIZTvj5y_G1Hh7>#O0zU@0RB$>3Fo9Q`v>t(Bv1h1yUllsWX6MY#JRl#W~zl*fXL3QmQ>~*T1 z=f_jtAhAAjTxIK@SC%e<1X+HwCh6kD)ltD46Ab*gy^~C4U)LPmC6vK z_J_hPsUqLYT{zLLVeN0sxT)e=jqJB~P|4z33lrJE!HvZ-oM^4MbcMQD7Eu9X$;4;V zmPzXf8&!i=l0rnBz?)CovkA4T)ru7i?@?YFJ^3B=Oy4F=IiWd54Zmy1Ez;_)!*JWQKAvbT1ksHSO&Ci%86#W=r*%BB`*lorMybv<*hdv zu74UW`uhq)oVdbapGPO@W@S(&NRRsOtnIH3x1AV*%8MlH1=iez>#BTgJQFd+%7P1)(hsy2)vY%%-uuuhuqfnK7-pmDA zhS83&fNHh!B$q~wV-4QV%em6%)R$QtOka_b(uBMU8_o$4x*khSK8;Up_5NFA`YJ8` zlAoa1dS ze(x^}tOL@W9uL~Qz6k-M2~$2q6X!GLQ=(F%#r*g1@;xdWr_iiYue1Q!#yl(zB{cXG zm$bDnPR$NZ4ELa3(0yu~xOnQOwYEBj@{X8$4kem~02u?b#~g={uX#Ra30jdB^A?%b zp?Abj9d!&_4XV)`?+9sKJdCKDDw+C!jZfxas7k4if&)J{zQh!uWSTB# z&7+??jxGwLqt~JF1b{lOHbN1FWU6( zRS{IQiRojB?m-uEU22YJi2w*d(q|gVpo_n;{Hl|=5@6)t6_Nx&YWLg4>7e=m)eA{X z{%@l0yF-W?g%|fkcn(;T@Z>>!o{|$7Acp_nliF z#0T>o=H<%XbtIU{tTD2(LSibhZ9(nuojYRI@)g4od3FH35|*^fUI_S&o3V@HMw(K{(VLKaf%Cjk6n5?Mng0kvnS>B zA!yro?#8^2Z!PI{>~Es)3RAQcD=JjDCp#s1^;&LkW2t1`@KF-c%37k@HwQcVos*f> zEP~wsb=8Nk>AhXSLy&FM(%6u4>PSUVJ*WtfG8B8-gyX}}4l10_*ay{v8n;C%1)0b| z7JhGTVn1<6?^Sxo4sZ#5(|i2S*sKmGOZ{u}@%KMfMi-nBXddJt<)i-U#u$$i8}#ac zZd6i_p6^v_8J3^^U&KC%6*Y&P`6zE)-4=bwg|TBU-c}8jp!YL3v0Wg}FPpWShnoximn(;}XWiUejW60`|g4HGSw z{yFZ1=%?`*Ga+MO+)P^`#CfwccfMd9(d>vIq&9#_CGfiw8RLPV!kLv4_LJQ#hvJgc zKL#56DcS?DL4?F6_QM!bfU_Jy)$ZE*ej;=c0FiUE+m7mDon<5tG0coOYM6xm7h^*r z2FM0+=qxX4y)I-ljrX#>H|q6`Owe{vsy|J`|JF=GRmK~axAl=jbd$1cDkFQeb6DFm zr*`*YE#ZgXuUFptz$9XiUc$9R*~s#1TsGPhd1MG`JMqgy;l;s>(p~`4Nq{r$6! zz(+{iK z8d+@L&A#v@cU>2m#@s%QX4#&1zv>v?Y_RPW;ZNC3g22V8t2I^mu8avTzQf+4|G}_; z{?1^mjNYf5bc%FPzpDsc^6-ps)PB>aL7Xlz$(i~anp%JU23FHfpQ%Gx09_glV%2+n z1R17jBI-gtcde@a&;nOUb;F^*gwE>jw%-Jqpy(#=jHVx=rN46l)z#JAt@w!WOv16Z zH{=--1|`mf@&D5Oz0G>-Xt%ueU${yskq(*{c&8%fv7hhUSQ^<^d4?{eFo0}@*AxEd zoJ*c@e=wFp4H?ob_B@<>duqPwaGfw(7x-|lWVUm$V43Kvzwy@y;PU+RcxPmAbAdjf zR*lgn`tSBE-v=@-66v-B^SzYzi7`ZDVG)S`5KsN)V(>EUKYS!=?bey*?ZN}pELMd& z_yHpi4$fX8fkET(IW3O&nDmEA^0ftV5q7v^fa_zI1}|`s85L1v%!r;S&G*SyfWSs7 z(OkypSKX6O_h|Sw^wFUMVsX=XS!(p_;=(BD-e>Ommgcr&^_+aJu4>D8y@l&sv{~E; zv>-+|zr1Qs&*Q`L^P9Lge1r!Z>UVuIJ7Rx}*zqiW(?&bf(bktM;pbtG>zSBZKVttM z$Ls%7AQxHmzErbaVTepb)8D^yVg}%Pz2crouy=%H5v^mM`#2By_gBX=?w`JXU}a;2 zzm7~A#wdSwSj(Bf3lDuQ!q0>;p7S74gn?9Fo}c`_o$4N(5@f%1oZgG@IS<6A7XDq3 zVVd;$GKPdziNj%;ufbta(DAGni{7@tMTVTmhyke>L8TrKwD^1hZrrNoV{#gjDn8%! zS{#?(nzffee$5iW=ORn+N*=E0>-<1PzPH8R#c6HFlcx=&bRU;(I z@JngC(cHCLOY+t#84*GU{`Qb;D(v>AlVZ(zx=2OPZCja_8W_d>`uz5Hf!t;4PrVnN zcks&pP^7kf1-{?x>m`rY7?7X}cu%kwM{884gp zB)<{H;oX2I`MeQI2zQA{s{Yrv;I-%DpEfN*IKMwt2>v?|E$Fl&^5|mJRv><6#)Ju4 z6l2$#;5Itrz!XhFiHyYC3And8_WDk3t< z+nF`e6oP%Gr)ynJN)mw&tyeH0WfTHx2yzvD$ErS&^+qj^hAtP!Tab*7Qhs7x!vDr) zxfC~|Gp?<&)?UVFCzHX&Ev@qBIGRjx2JMGo;Ur9G*TyqM@H z8HF=QJBUGwxsj1J%MC#I2S(~<8tfk*8y&f!r~_au*d1~&$mCg_RXu2J75TR9&f_LL zjp9V$OX6#a^vRxddpO%cIWN`{*C+3-$Fn|9PHGHQ>JqtP$j9#QF9a_j!Fv%>;xJkG z(Cje9xHOA~@6z!a!gC%2D5v|rF;sV(9ZN!oom6YYnBvICvuvR3`bOz*1loGjhS3Qf zR@JG|ulnvuZmEWeDY6>^Yl~O7A~p&A=ceK7&b@V2?&sS^Pyhe` literal 0 HcmV?d00001 diff --git a/tests/_images/Shapes_can_plot_with_group_arg.png b/tests/_images/Shapes_can_plot_with_group_arg.png new file mode 100644 index 0000000000000000000000000000000000000000..744ce7e752e8fd4c0b76b3ba6c412d6a53acc80b GIT binary patch literal 8960 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 ai2NTJV*MgG8(L!k0000 Date: Mon, 4 Sep 2023 17:30:43 +0200 Subject: [PATCH 04/11] filter points and shapes using groups --- src/spatialdata_plot/pl/render.py | 40 ++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 41d69fa3..8ae8b29a 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -4,6 +4,7 @@ from copy import copy from typing import Union +import dask import geopandas as gpd import matplotlib import numpy as np @@ -18,6 +19,7 @@ from spatialdata.models import ( Image2DModel, Labels2DModel, + PointsModel, ) from spatialdata_plot._logging import logger @@ -57,6 +59,12 @@ def _render_shapes( ) -> None: elements = render_params.elements + if render_params.groups is not None: + if isinstance(render_params.groups, str): + render_params.groups = [render_params.groups] + if not all(isinstance(g, str) for g in render_params.groups): + raise TypeError("All groups must be strings.") + sdata_filt = sdata.filter_by_coordinate_system( coordinate_system=coordinate_system, filter_table=sdata.table is not None, @@ -68,7 +76,6 @@ def _render_shapes( elements = list(sdata_filt.shapes.keys()) for e in elements: - # shapes = [sdata.shapes[e] for e in elements] shapes = sdata.shapes[e] n_shapes = sum([len(s) for s in shapes]) @@ -99,7 +106,15 @@ def _render_shapes( if len(color_vector) == 0: color_vector = [render_params.cmap_params.na_color] + # filter by `groups` + if render_params.groups is not None and color_source_vector is not None: + mask = color_source_vector.isin(render_params.groups) + shapes = shapes[mask] + shapes = shapes.reset_index() + color_source_vector = color_source_vector[mask] + color_vector = color_vector[mask] shapes = gpd.GeoDataFrame(shapes, geometry="geometry") + _cax = _get_collection_shape( shapes=shapes, s=render_params.scale, @@ -158,6 +173,12 @@ def _render_points( scalebar_params: ScalebarParams, legend_params: LegendParams, ) -> None: + if render_params.groups is not None: + if isinstance(render_params.groups, str): + render_params.groups = [render_params.groups] + if not all(isinstance(g, str) for g in render_params.groups): + raise TypeError("All groups must be strings.") + elements = render_params.elements sdata_filt = sdata.filter_by_coordinate_system( @@ -177,6 +198,14 @@ def _render_points( color = [render_params.color] if isinstance(render_params.color, str) else render_params.color coords.extend(color) + points = points[coords].compute() + # points[color[0]].cat.set_categories(render_params.groups, inplace=True) + if render_params.groups is not None: + points = points[points[color].isin(render_params.groups).values] + points[color[0]] = points[color[0]].cat.set_categories(render_params.groups) + points = dask.dataframe.from_pandas(points, npartitions=1) + sdata_filt.points[e] = PointsModel.parse(points) + point_df = points[coords].compute() # we construct an anndata to hack the plotting functions @@ -193,7 +222,6 @@ def _render_points( key=render_params.color, palette=render_params.palette, ) - # print(p) color_source_vector, color_vector, _ = _set_color_source_vec( sdata=sdata_filt, element=points, @@ -414,6 +442,12 @@ def _render_labels( ) -> None: elements = render_params.elements + if render_params.groups is not None: + if isinstance(render_params.groups, str): + render_params.groups = [render_params.groups] + if not all(isinstance(g, str) for g in render_params.groups): + raise TypeError("All groups must be strings.") + sdata_filt = sdata.filter_by_coordinate_system( coordinate_system=coordinate_system, filter_table=sdata.table is not None, @@ -440,7 +474,7 @@ def _render_labels( table = sdata.table[sdata.table.obs[region_key].isin([label_key])] - # get isntance id based on subsetted table + # get instance id based on subsetted table instance_id = table.obs[instance_key].values # get color vector (categorical or continuous) From 9c65706b0ce55007395844b66267da29dcd73ff0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 15:35:58 +0000 Subject: [PATCH 05/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/pl/test_render_shapes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 807140da..d7beed3c 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -121,6 +121,7 @@ def test_plot_can_plot_with_group_and_palette(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes( "blobs_polygons", color="cluster", groups=["c2", "c1"], palette=ListedColormap(["green", "yellow"]) ).pl.show() + def test_plot_colorbar_respects_input_limits(self, sdata_blobs: SpatialData): sdata_blobs.shapes["blobs_polygons"]["cluster"] = [1, 2, 3, 5, 20] sdata_blobs.pl.render_shapes("blobs_polygons", color="cluster", groups=["c1"]).pl.show() From 03779daa6c828cfb90cc88d3f5d800f3641a3f50 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Mon, 4 Sep 2023 18:01:34 +0200 Subject: [PATCH 06/11] PointsModel coordinate argument --- src/spatialdata_plot/pl/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index a35a9cf5..48d47fe2 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -208,7 +208,7 @@ def _render_points( points = points[points[color].isin(render_params.groups).values] points[color[0]] = points[color[0]].cat.set_categories(render_params.groups) points = dask.dataframe.from_pandas(points, npartitions=1) - sdata_filt.points[e] = PointsModel.parse(points) + sdata_filt.points[e] = PointsModel.parse(points, coordinates={"x": "x", "y": "y"}) point_df = points[coords].compute() From 19dd3e61f68036ab3a3e9921394bd01e7ef80413 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 5 Sep 2023 11:15:09 +0200 Subject: [PATCH 07/11] unittests for points and filtering by groups --- tests/_images/Points_can_color_by_palette.png | Bin 0 -> 12074 bytes .../_images/Points_can_filter_with_groups.png | Bin 0 -> 8086 bytes .../Shapes_can_plot_with_group_arg.png | Bin 8960 -> 7641 bytes tests/pl/test_render_points.py | 9 +++++++++ 4 files changed, 9 insertions(+) create mode 100644 tests/_images/Points_can_color_by_palette.png create mode 100644 tests/_images/Points_can_filter_with_groups.png diff --git a/tests/_images/Points_can_color_by_palette.png b/tests/_images/Points_can_color_by_palette.png new file mode 100644 index 0000000000000000000000000000000000000000..6dfaab73f3b4b0339098d3468d585efe5b72f2b2 GIT binary patch literal 12074 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 Urc5{OKL7v#07*qoM6N<$f`aY_{r~^~ literal 0 HcmV?d00001 diff --git a/tests/_images/Points_can_filter_with_groups.png b/tests/_images/Points_can_filter_with_groups.png new file mode 100644 index 0000000000000000000000000000000000000000..3d52aebb8aa7e69f959a38a40a8f6a678ad9a713 GIT binary patch literal 8086 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 k5))tyM~MkAhJyhA51Y?`gbAl)S>T{85W-}k=v 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 A?EnA( diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index bfa58658..1ea28e98 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -1,6 +1,7 @@ import matplotlib import scanpy as sc import spatialdata_plot # noqa: F401 +from matplotlib.colors import ListedColormap from spatialdata import SpatialData from tests.conftest import PlotTester, PlotTesterMeta @@ -21,3 +22,11 @@ class TestPoints(PlotTester, metaclass=PlotTesterMeta): def test_plot_can_render_points(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_points(elements="blobs_points").pl.show() + + def test_plot_can_filter_with_groups(self, sdata_blobs: SpatialData): + sdata_blobs.pl.render_points(color="genes", groups="b", palette=ListedColormap(["orange"])).pl.show() + + def test_plot_can_color_by_palette(self, sdata_blobs: SpatialData): + sdata_blobs.pl.render_points( + color="genes", groups=["a", "b"], palette=ListedColormap(["lightgreen", "darkblue"]) + ).pl.show() From a1cf6aea4d5d8aa51293d536f7ba6516b839cc94 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 5 Sep 2023 18:35:10 +0200 Subject: [PATCH 08/11] changing palette and cmap usage --- src/spatialdata_plot/pl/basic.py | 10 ++--- src/spatialdata_plot/pl/render.py | 10 ++++- src/spatialdata_plot/pl/render_params.py | 1 + src/spatialdata_plot/pl/utils.py | 41 ++++++++++++------ tests/_images/Points_coloring_with_cmap.png | Bin 0 -> 10913 bytes ...e.png => Points_coloring_with_palette.png} | Bin ....png => Shapes_can_filter_with_groups.png} | Bin ...e.png => Shapes_coloring_with_palette.png} | Bin tests/pl/test_render_points.py | 12 ++--- tests/pl/test_render_shapes.py | 9 ++-- 10 files changed, 53 insertions(+), 30 deletions(-) create mode 100644 tests/_images/Points_coloring_with_cmap.png rename tests/_images/{Points_can_color_by_palette.png => Points_coloring_with_palette.png} (100%) rename tests/_images/{Shapes_can_plot_with_group_arg.png => Shapes_can_filter_with_groups.png} (100%) rename tests/_images/{Shapes_can_plot_with_group_and_palette.png => Shapes_coloring_with_palette.png} (100%) diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index e228f2ac..2585dd31 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -14,7 +14,7 @@ from dask.dataframe.core import DataFrame as DaskDataFrame from geopandas import GeoDataFrame from matplotlib.axes import Axes -from matplotlib.colors import Colormap, ListedColormap, Normalize +from matplotlib.colors import Colormap, Normalize from matplotlib.figure import Figure from multiscale_spatial_image.multiscale_spatial_image import MultiscaleSpatialImage from pandas.api.types import is_categorical_dtype @@ -150,7 +150,7 @@ def render_shapes( outline_width: float = 1.5, outline_color: str | list[float] = "#000000ff", layer: str | None = None, - palette: ListedColormap | str | None = None, + palette: str | list[str] | None = None, cmap: Colormap | str | None = None, norm: bool | Normalize = False, na_color: str | tuple[float, ...] | None = "lightgrey", @@ -235,7 +235,7 @@ def render_points( color: str | None = None, groups: str | Sequence[str] | None = None, size: float = 1.0, - palette: ListedColormap | str | None = None, + palette: str | list[str] | None = None, cmap: Colormap | str | None = None, norm: None | Normalize = None, na_color: str | tuple[float, ...] | None = (0.0, 0.0, 0.0, 0.0), @@ -303,7 +303,7 @@ def render_images( cmap: list[Colormap] | list[str] | Colormap | str | None = None, norm: None | Normalize = None, na_color: str | tuple[float, ...] | None = (0.0, 0.0, 0.0, 0.0), - palette: ListedColormap | str | None = None, + palette: str | list[str] | None = None, alpha: float = 1.0, quantiles_for_norm: tuple[float | None, float | None] = (None, None), **kwargs: Any, @@ -381,7 +381,7 @@ def render_labels( contour_px: int = 3, outline: bool = False, layer: str | None = None, - palette: ListedColormap | str | None = None, + palette: str | list[str] | None = None, cmap: Colormap | str | None = None, norm: None | Normalize = None, na_color: str | tuple[float, ...] | None = (0.0, 0.0, 0.0, 0.0), diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 48d47fe2..0170f613 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -95,6 +95,7 @@ def _render_shapes( palette=render_params.palette, na_color=render_params.cmap_params.na_color, alpha=render_params.fill_alpha, + cmap_params=render_params.cmap_params, ) values_are_categorical = color_source_vector is not None @@ -236,6 +237,7 @@ def _render_points( palette=render_params.palette, na_color=render_params.cmap_params.na_color, alpha=render_params.alpha, + cmap_params=render_params.cmap_params, ) # color_source_vector is None when the values aren't categorical @@ -258,6 +260,11 @@ def _render_points( if not ( len(set(color_vector)) == 1 and list(set(color_vector))[0] == to_hex(render_params.cmap_params.na_color) ): + if color_source_vector is None: + palette = ListedColormap(dict.fromkeys(color_vector)) + else: + palette = ListedColormap(dict.fromkeys(color_vector[~pd.Categorical(color_source_vector).isnull()])) + _ = _decorate_axs( ax=ax, cax=cax, @@ -265,7 +272,7 @@ def _render_points( adata=adata, value_to_plot=render_params.color, color_source_vector=color_source_vector, - palette=render_params.palette, + palette=palette, alpha=render_params.alpha, na_color=render_params.cmap_params.na_color, legend_fontsize=legend_params.legend_fontsize, @@ -493,6 +500,7 @@ def _render_labels( palette=render_params.palette, na_color=render_params.cmap_params.na_color, alpha=render_params.fill_alpha, + cmap_params=render_params.cmap_params, ) if (render_params.fill_alpha != render_params.outline_alpha) and render_params.contour_px is not None: diff --git a/src/spatialdata_plot/pl/render_params.py b/src/spatialdata_plot/pl/render_params.py index 9294dc2d..cca7bd58 100644 --- a/src/spatialdata_plot/pl/render_params.py +++ b/src/spatialdata_plot/pl/render_params.py @@ -19,6 +19,7 @@ class CmapParams: cmap: Colormap norm: Normalize na_color: str | tuple[float, ...] = (0.0, 0.0, 0.0, 0.0) + is_default: bool = True @dataclass diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index c0fc3c44..42defdb7 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -571,6 +571,7 @@ def _prepare_cmap_norm( vcenter: float | None = None, **kwargs: Any, ) -> CmapParams: + is_default = cmap is None cmap = copy(matplotlib.colormaps[rcParams["image.cmap"] if cmap is None else cmap]) cmap.set_bad("lightgray" if na_color is None else na_color) @@ -583,7 +584,7 @@ def _prepare_cmap_norm( else: norm = TwoSlopeNorm(vmin=vmin, vmax=vmax, vcenter=vcenter) - return CmapParams(cmap, norm, na_color) + return CmapParams(cmap, norm, na_color, is_default) def _set_outline( @@ -745,8 +746,9 @@ def _normalize( def _get_colors_for_categorical_obs( categories: Sequence[str | int], - palette: ListedColormap | str | None = None, + palette: ListedColormap | str | list[str] | None = None, alpha: float = 1.0, + cmap_params: CmapParams | None = None, ) -> list[str]: """ Return a list of colors for a categorical observation. @@ -768,7 +770,9 @@ def _get_colors_for_categorical_obs( # check if default matplotlib palette has enough colors if palette is None: - if len(rcParams["axes.prop_cycle"].by_key()["color"]) >= len_cat: + if cmap_params is not None and not cmap_params.is_default: + palette = cmap_params.cmap + elif len(rcParams["axes.prop_cycle"].by_key()["color"]) >= len_cat: cc = rcParams["axes.prop_cycle"]() palette = [next(cc)["color"] for _ in range(len_cat)] else: @@ -784,12 +788,13 @@ def _get_colors_for_categorical_obs( "input has more than 103 categories. Uniform " "'grey' color will be used for all categories." ) - # otherwise, single chanels turn out grey + # otherwise, single channels turn out grey color_idx = np.linspace(0, 1, len_cat) if len_cat > 1 else [0.7] if isinstance(palette, str): - cmap = plt.get_cmap(palette) - palette = [to_hex(x) for x in cmap(color_idx, alpha=alpha)] + # cmap = plt.get_cmap(palette) + # palette = [to_hex(x) for x in cmap(color_idx, alpha=alpha)] + palette = [to_hex(palette)] elif isinstance(palette, list): palette = [to_hex(x) for x in palette] elif isinstance(palette, ListedColormap): @@ -797,7 +802,7 @@ def _get_colors_for_categorical_obs( elif isinstance(palette, LinearSegmentedColormap): palette = [to_hex(palette(x, alpha=alpha)) for x in color_idx] # type: ignore[attr-defined] else: - raise TypeError(f"Palette is {type(palette)} but should be string or `ListedColormap`.") + raise TypeError(f"Palette is {type(palette)} but should be string or list.") return palette[:len_cat] # type: ignore[return-value] @@ -809,9 +814,10 @@ def _set_color_source_vec( element_name: list[str] | str | None = None, layer: str | None = None, groups: Sequence[str] | str | None = None, - palette: ListedColormap | str | None = None, + palette: str | list[str] | None = None, na_color: str | tuple[float, ...] | None = None, alpha: float = 1.0, + cmap_params: CmapParams | None = None, ) -> tuple[ArrayLike | pd.Series | None, ArrayLike, bool]: if value_to_plot is None: color = np.full(len(element), to_hex(na_color)) # type: ignore[arg-type] @@ -836,6 +842,11 @@ def _set_color_source_vec( # numerical case, return early if not is_categorical_dtype(color_source_vector): + if palette is not None: + logging.warning( + "Ignoring categorical palette which is given for a continuous variable. " + "Consider using `cmap` to pass a ColorMap." + ) return None, color_source_vector, False color_source_vector = pd.Categorical(color_source_vector) # convert, e.g., `pd.Series` @@ -845,7 +856,7 @@ def _set_color_source_vec( color_source_vector = color_source_vector.remove_categories(categories.difference(groups)) categories = groups - color_map = dict(zip(categories, _get_colors_for_categorical_obs(categories, palette))) + color_map = dict(zip(categories, _get_colors_for_categorical_obs(categories, palette, cmap_params=cmap_params))) # color_map = _get_palette( # adata=adata, cluster_key=value_to_plot, categories=categories, palette=palette, alpha=alpha # ) @@ -919,7 +930,7 @@ def _get_palette( categories: Sequence[Any], adata: AnnData | None = None, cluster_key: None | str = None, - palette: ListedColormap | str | None = None, + palette: ListedColormap | str | list[str] | None = None, alpha: float = 1.0, ) -> Mapping[str, str] | None: if adata is not None and palette is None: @@ -950,11 +961,13 @@ def _get_palette( return {cat: to_hex(to_rgba(col)[:3]) for cat, col in zip(categories, palette[:len_cat])} if isinstance(palette, str): - cmap = plt.get_cmap(palette) + cmap = ListedColormap([palette]) + elif isinstance(palette, list): + cmap = ListedColormap(palette) elif isinstance(palette, ListedColormap): cmap = palette else: - raise TypeError(f"Palette is {type(palette)} but should be string or `ListedColormap`.") + raise TypeError(f"Palette is {type(palette)} but should be string or list.") palette = [to_hex(np.round(x, 5)) for x in cmap(np.linspace(0, 1, len_cat), alpha=alpha)] return dict(zip(categories, palette)) @@ -971,6 +984,8 @@ def _maybe_set_colors( raise KeyError("Unable to copy the palette when there was other explicitly specified.") target.uns[color_key] = source.uns[color_key] except KeyError: + if isinstance(palette, str): + palette = ListedColormap([palette]) if isinstance(palette, ListedColormap): # `scanpy` requires it palette = cycler(color=palette.colors) add_colors_for_categorical_sample_annotation(target, key=key, force_update_colors=True, palette=palette) @@ -983,7 +998,7 @@ def _decorate_axs( adata: AnnData, value_to_plot: str | None, color_source_vector: pd.Series[CategoricalDtype], - palette: ListedColormap | str | None = None, + palette: ListedColormap | str | list[str] | None = None, alpha: float = 1.0, na_color: str | tuple[float, ...] = (0.0, 0.0, 0.0, 0.0), legend_fontsize: int | float | _FontSize | None = None, diff --git a/tests/_images/Points_coloring_with_cmap.png b/tests/_images/Points_coloring_with_cmap.png new file mode 100644 index 0000000000000000000000000000000000000000..4f620cca1c61bb882e78ac7781b3abc8c8171028 GIT binary patch literal 10913 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 Date: Tue, 5 Sep 2023 18:46:51 +0200 Subject: [PATCH 09/11] some docstrings, changelog --- CHANGELOG.md | 2 +- src/spatialdata_plot/pl/basic.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a608018d..44a5a53f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning][]. - Multipolygons are now handled correctly (#93) - Legend order is now deterministic (#143) - Images no longer normalised by default (#150) -- Shapes can be colored by categorical variable with the `group` and `palette` arguments (#153) +- Filtering of shapes and points using the `groups` argument is now possible, coloring by palette and cmap arguments works for shapes and points (#153) - Colorbar no longer autoscales to [0, 1] (#155) ## [0.0.4] - 2023-08-11 diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index 2585dd31..21e5249b 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -183,8 +183,11 @@ def render_shapes( Key in :attr:`anndata.AnnData.layers` or `None` for :attr:`anndata.AnnData.X`. palette Palette for discrete annotations, see :class:`matplotlib.colors.Colormap`. + Must contain valid color names. cmap Colormap for continuous annotations, see :class:`matplotlib.colors.Colormap`. + If no palette is given and `color` refers to a categorical, the colors are + sampled from this colormap. norm Colormap normalization for continuous annotations, see :class:`matplotlib.colors.Normalize`. na_color @@ -259,8 +262,11 @@ def render_points( Value to scale points. palette Palette for discrete annotations, see :class:`matplotlib.colors.Colormap`. + Must contain valid color names. cmap Colormap for continuous annotations, see :class:`matplotlib.colors.Colormap`. + If no palette is given and `color` refers to a categorical, the colors are + sampled from this colormap. norm Colormap normalization for continuous annotations, see :class:`matplotlib.colors.Normalize`. na_color From b3a78051fb43650e959de4f86b3e27b789605d3c Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 5 Sep 2023 19:01:15 +0200 Subject: [PATCH 10/11] docstrings --- src/spatialdata_plot/pl/basic.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index 21e5249b..f58d7e04 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -182,8 +182,9 @@ def render_shapes( layer Key in :attr:`anndata.AnnData.layers` or `None` for :attr:`anndata.AnnData.X`. palette - Palette for discrete annotations, see :class:`matplotlib.colors.Colormap`. - Must contain valid color names. + Palette for discrete annotations. List of valid color names that should be used + for the categories (all or as specified by `groups`). For a single category, + a valid color name can be given as string. cmap Colormap for continuous annotations, see :class:`matplotlib.colors.Colormap`. If no palette is given and `color` refers to a categorical, the colors are @@ -261,8 +262,9 @@ def render_points( size Value to scale points. palette - Palette for discrete annotations, see :class:`matplotlib.colors.Colormap`. - Must contain valid color names. + Palette for discrete annotations. List of valid color names that should be used + for the categories (all or as specified by `groups`). For a single category, + a valid color name can be given as string. cmap Colormap for continuous annotations, see :class:`matplotlib.colors.Colormap`. If no palette is given and `color` refers to a categorical, the colors are From 40288fe62b8d6e560fabd14d52ce491309a4009c Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Wed, 6 Sep 2023 20:40:05 +0200 Subject: [PATCH 11/11] remove comments and brackets --- src/spatialdata_plot/pl/utils.py | 2 -- tests/pl/test_render_points.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 42defdb7..1f6aff5a 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -792,8 +792,6 @@ def _get_colors_for_categorical_obs( color_idx = np.linspace(0, 1, len_cat) if len_cat > 1 else [0.7] if isinstance(palette, str): - # cmap = plt.get_cmap(palette) - # palette = [to_hex(x) for x in cmap(color_idx, alpha=alpha)] palette = [to_hex(palette)] elif isinstance(palette, list): palette = [to_hex(x) for x in palette] diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index afe5af78..43379517 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -23,7 +23,7 @@ def test_plot_can_render_points(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_points(elements="blobs_points").pl.show() def test_plot_can_filter_with_groups(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_points(color="genes", groups="b", palette=["orange"]).pl.show() + sdata_blobs.pl.render_points(color="genes", groups="b", palette="orange").pl.show() def test_plot_coloring_with_palette(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_points(color="genes", groups=["a", "b"], palette=["lightgreen", "darkblue"]).pl.show()