From ad2c1d48a01c9114e83db02376105f675c735ac7 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 14 Jul 2023 15:09:40 +0200 Subject: [PATCH] Make singular colorbars consistent with single-value mappables. If vmin == vmax, Norm maps values to 0 (not to 0.5). Make colorbars match that behavior by having them only expand singular norms the the high side. --- doc/api/next_api_changes/behavior/26307-AL.rst | 4 ++++ lib/matplotlib/colorbar.py | 9 +++++++-- .../test_colorbar/colorbar_single_scatter.png | Bin 1843 -> 0 bytes lib/matplotlib/tests/test_colorbar.py | 17 ++++++++--------- 4 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26307-AL.rst delete mode 100644 lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png diff --git a/doc/api/next_api_changes/behavior/26307-AL.rst b/doc/api/next_api_changes/behavior/26307-AL.rst new file mode 100644 index 000000000000..a83366f012b3 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26307-AL.rst @@ -0,0 +1,4 @@ +Colorbars of single-value norms now map that value to the lowest color, not the midpoint color +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This behavior is consistent with e.g. how `~.Axes.imshow` handle inputs with +``vmin == vmax``. diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index af61e4671ff4..5539f818d909 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1088,8 +1088,13 @@ def _process_values(self): # If we still aren't scaled after autoscaling, use 0, 1 as default self.norm.vmin = 0 self.norm.vmax = 1 - self.norm.vmin, self.norm.vmax = mtransforms.nonsingular( - self.norm.vmin, self.norm.vmax, expander=0.1) + + # Only expand vmax if needed, to match Normalize's behavior of mapping + # everything to 0 if the norm is singular. + vmin, vmax = sorted([self.norm.vmin, self.norm.vmax]) + self.norm.vmin = vmin + _, self.norm.vmax = mtransforms.nonsingular(vmin, vmax, expander=0.1) + if (not isinstance(self.norm, colors.BoundaryNorm) and (self.boundaries is None)): b = self.norm.inverse(b) diff --git a/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png b/lib/matplotlib/tests/baseline_images/test_colorbar/colorbar_single_scatter.png deleted file mode 100644 index 18d9cf02add023bf7fe9f0c028401efaa3ef933c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1843 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoKX9-C$wJ+|*+43|#5JNMI6tkVJh3R1!8fs_ zASb^hCo@T*EVZaOGe6H*&sfh$uOPp;#L&XPTrVZH%s@Lm)!5M7%-GV**jPs)qokyu z*h*hN7p_$=zbIXw>GOkNpz)jq9+AZi3~cHk%;+0(<2aDf=IP=XQZeW4UBm8}=c30S zDsR?nQQR335!dXiQ*_fQHt2@N433zUC(AFmdn`P}(I_x&nuyjX&MQZJv&s+k9!!{{ z^fIKw{Kh8kZeFu+<}_~294_zPM;VV()9#1=viIM*V7K-18^3288-LI8mjMD1hXvEC z_!%yAGcavr5$I5DaL{37N#axx@MdU?VB$C;;N22L-;@)jl z{2z4IZrxS!G3o7_H&;x2W78t0EnT(h_T$+HdHn7FWo=z`H7j&`{{4TE_kaHuR)6(S z@%WzlpG9|f6h3~zY?t1~mk@gO+&RCyyGmEzer#X4ZSM1%JZTbXZFle9o&UF%fAyfiWv;@3Uw?M3 z`d4tmP+s=90`o>jw4gY>{D4F4j?o@%7&f z-?6fBD5BdYW!G?eQ%LsO?-B+r*JgcA>}}&$IIxVTUh#{Guj%>X_l5}^=UTsRg4k>N z$2%dx_uiUS_rJ;|>6j4{{!il)JU*s{&b5CsV?WRbk2j$E04XxL7dB>nulB#t&MVN7 z%FL2=ukO#$>#@fz%9uDb?{NQ#-^0QNihf{3>YWZ+yY*i30fDIObM2-;KQPQT`qQp( z+2pFt`Mt**6fMNBoo2(5LU`*Xc{a#Zuiq2(zvA)Nd&xXUv8lpHVt2TC6yIGhs||er zMJ`!`fWNIBEv_tf?|*gctoGNZe|MLrRWDx`w<|6>x~{b1AMbufsM}q3mwooKwW(iz z@Aqf@*AIVMm%e>A^`7$4thKpMYHnn&zLmXt>;F3!E2W+X#NXQ={eG@>XlQ8ueCyEp zf4}BE@3iR3f8G0Q(Y?PX>!Yv6g@*oJH7$A0iv16nEqsH|e!f%Plaaoy{MG97@2}i` z?sqkOetmfQw)5ZQG_Nf?*BXCjxqSWBoF~uwvwloJ@TL6grLR>#FK_;OQ(pec>G|{4 zuAaW`yBz1Wq#wK!{%zZ~?bTuae^;t`BCHYV3M1?76p(WI^P{}6{#V=sb-j>lwf6{w z5+vgSQwPV#zvatc|F+&=`OHxKmHu&)vl5`RjGoi4ePE24wlpdm6uVol&Ed~BsbOF= zOZ~yUaCzpc%6ZlOjLrpmYo-HJ4zQe9^Dv%?Ir~#-Xziuyj*T1&0bkdj@t?LAn0#(B zG_JZXSHJ6z^L@j<*$~wjc7aNj7!F>|fAgyw5C7Y)i^t!Hq*A1Buw+Mg=zY1Op6W(M zBMx{vP5pPZsv;`;?9UBuhuH-Zg4_NJq|912>-mQAw1@QwH>0{j>YtHBg73e*;amTI zzZZ<0O!u&Jgj_Y>%l_gJFzEvGW$4$i_l&zRvK6pk5O_R4^lRaJ#kM47yu~K3?15`h zx~DJIwq2VgdB)%h&))Oh;zutqu2~heHtcTw!vqmvFfvkv diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 74742d8c2369..a1929bfb6daf 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -265,18 +265,17 @@ def test_gridspec_make_colorbar(): plt.subplots_adjust(top=0.95, right=0.95, bottom=0.2, hspace=0.25) -@image_comparison(['colorbar_single_scatter.png'], remove_text=True, - savefig_kwarg={'dpi': 40}) def test_colorbar_single_scatter(): - # Issue #2642: if a path collection has only one entry, - # the norm scaling within the colorbar must ensure a - # finite range, otherwise a zero denominator will occur in _locate. + # Issue #2642: If a path collection has only one entry, the norm scaling within the + # colorbar must ensure a finite range, otherwise a zero denominator will occur in + # _locate. Also, adding the colorbar must not change the value's normalization. plt.figure() - x = y = [0] - z = [50] - cmap = mpl.colormaps['jet'].resampled(16) - cs = plt.scatter(x, y, z, c=z, cmap=cmap) + v = 50 + cs = plt.scatter([0], [0], c=[50]) + old_normed = cs.norm(v) plt.colorbar(cs) + new_normed = cs.norm(v) + assert old_normed == new_normed @pytest.mark.parametrize('use_gridspec', [True, False])