From f699d50bcef31106dfaa1dbd03b8921f2b238d23 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Wed, 23 Aug 2023 23:53:55 +0200 Subject: [PATCH 01/19] Support standard formatters in axisartist. Ideally, axisartist-specific formatters (and locators) should be deprecated and removed in the future. --- .../next_whats_new/stdfmt-axisartist.rst | 3 +++ lib/mpl_toolkits/axisartist/floating_axes.py | 8 +++--- lib/mpl_toolkits/axisartist/grid_finder.py | 23 ++++++++++++++---- .../axisartist/grid_helper_curvelinear.py | 8 +++--- .../polar_box.png | Bin 62526 -> 64346 bytes .../tests/test_grid_helper_curvelinear.py | 20 +++++++-------- 6 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 doc/users/next_whats_new/stdfmt-axisartist.rst diff --git a/doc/users/next_whats_new/stdfmt-axisartist.rst b/doc/users/next_whats_new/stdfmt-axisartist.rst new file mode 100644 index 000000000000..9cb014413042 --- /dev/null +++ b/doc/users/next_whats_new/stdfmt-axisartist.rst @@ -0,0 +1,3 @@ +``axisartist`` can now be used together with standard ``Formatters`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... instead of being limited to axisartist-specific ones. diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index 97dafe98c694..306ae2c5139a 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -222,10 +222,10 @@ def _update_grid(self, x1, y1, x2, y2): grid_info["lon_info"] = lon_levs, lon_n, lon_factor grid_info["lat_info"] = lat_levs, lat_n, lat_factor - grid_info["lon_labels"] = grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs) - grid_info["lat_labels"] = grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs) + grid_info["lon_labels"] = grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs) + grid_info["lat_labels"] = grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs) lon_values = lon_levs[:lon_n] / lon_factor lat_values = lat_levs[:lat_n] / lat_factor diff --git a/lib/mpl_toolkits/axisartist/grid_finder.py b/lib/mpl_toolkits/axisartist/grid_finder.py index f969b011c4cd..18a48b8e8ccf 100644 --- a/lib/mpl_toolkits/axisartist/grid_finder.py +++ b/lib/mpl_toolkits/axisartist/grid_finder.py @@ -1,6 +1,6 @@ import numpy as np -from matplotlib import ticker as mticker +from matplotlib import ticker as mticker, _api from matplotlib.transforms import Bbox, Transform @@ -150,6 +150,19 @@ def __init__(self, self.tick_formatter2 = tick_formatter2 self.set_transform(transform) + def _format_ticks(self, idx, direction, factor, levels): + """ + Helper to support both standard formatters (inheriting from + `.mticker.Formatter`) and axisartist-specific ones; should be called instead of + directly calling ``self.tick_formatter1`` and ``self.tick_formatter2``. This + method should be considered as a temporary workaround which will be removed in + the future at the same time as axisartist-specific formatters. + """ + fmt = _api.check_getitem( + {1: self.tick_formatter1, 2: self.tick_formatter2}, idx=idx) + return (fmt.format_ticks(levels) if isinstance(fmt, mticker.Formatter) + else fmt(direction, factor, levels)) + def get_grid_info(self, x1, y1, x2, y2): """ lon_values, lat_values : list of grid values. if integer is given, @@ -192,14 +205,14 @@ def get_grid_info(self, x1, y1, x2, y2): tck_labels = grid_info["lon"]["tick_labels"] = {} for direction in ["left", "bottom", "right", "top"]: levs = grid_info["lon"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter1( - direction, lon_factor, levs) + tck_labels[direction] = self._format_ticks( + 1, direction, lon_factor, levs) tck_labels = grid_info["lat"]["tick_labels"] = {} for direction in ["left", "bottom", "right", "top"]: levs = grid_info["lat"]["tick_levels"][direction] - tck_labels[direction] = self.tick_formatter2( - direction, lat_factor, levs) + tck_labels[direction] = self._format_ticks( + 2, direction, lat_factor, levs) return grid_info diff --git a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py index ae17452b6c58..f76fd84b55fe 100644 --- a/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/axisartist/grid_helper_curvelinear.py @@ -137,10 +137,10 @@ def update_lim(self, axes): "extremes": (lon_min, lon_max, lat_min, lat_max), "lon_info": (lon_levs, lon_n, np.asarray(lon_factor)), "lat_info": (lat_levs, lat_n, np.asarray(lat_factor)), - "lon_labels": grid_finder.tick_formatter1( - "bottom", lon_factor, lon_levs), - "lat_labels": grid_finder.tick_formatter2( - "bottom", lat_factor, lat_levs), + "lon_labels": grid_finder._format_ticks( + 1, "bottom", lon_factor, lon_levs), + "lat_labels": grid_finder._format_ticks( + 2, "bottom", lat_factor, lat_levs), "line_xy": (xx, yy), } diff --git a/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png b/lib/mpl_toolkits/axisartist/tests/baseline_images/test_grid_helper_curvelinear/polar_box.png index 04e8ceecd2cb57557a08bb1c6d7845316dd35328..8909355e9af8ac0d66827e13bd7674c8f0b82fed 100644 GIT binary patch literal 64346 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%0Du}-Th;9 zl;cC;eF&lQ8r0X>9Ndg5GS>CbntuOc@_i~n9v}8>450u1JO7|0e?#Mt$;r81Baz?Z zAcPQTi(ZP^YyZ5z|S{g!6E~20Zr*C$oZpolV!wcV>^hI5VyGPT~)?^M^ML|hOdXU%D(gBq^`l&W= z->-_Lp`r#S8PUakIGw#Pe|v_1Yl$?%d+%!8hOsUT3vB$!*#C_TL|9wfW-+L}u z(OCQ=Sw>WeQ)7xGvqGZu%zmvzGA5q!+}sPP-{|F$Pmk3+^!U@om@(6oPgwRt=~+Bt zpYw=YW$68{e7*cm0_nlfrF)}w!XGA*dzL)d(^0BW4oIindQo9Vj~-Pt5A|pa;K&@+ z(J38gW!%iI>Gm$l-85p~jSHc3-Z~){w|TqmGjIFo)V|^K3i7}N?T9c#Zi$<{4FVJ; zP!qll!A*ItUIpv`KGi*V6?M}0SM<8Lnov-Gow?1t#;(}mF5HQ)vp ze%{qJ?tb!>-NK)xb_tb#YF2xPndJfY8 z4B%!sHObSuH6;$eMmS>{;2+w?i&qLsHZd;8O;3-@~ui|qgb6cEr7)Q6qJz@_Gd-dvb_sUKyU#)0g&>uE_=h;AR6#nCCCsN<1E4`1a~eAsbR>7lQ2q{NYw+dBtyCP|(Wp;)M< zuDy9PVC2s?y6o-Jb$#+^O8$#WP0zeAL`{)KiuhF5IvWMQV=_jNE+Y5t-Gve~$Y@L2 zs>vEkT{ix5yY#8hObTc$7A|XC*S3RNM7o=ncP^ATL@FYF5?{1ACaUB2ZeQHK!*KUV z!~6m1R1)&tvX%}pJ~KmSRsmoj*B|LQ4HukIuj16e%u6@xhCV7RrHNwm=3C%YaC5q> zvC##70ZL7tQxXwE(*hQB96pMev@shtT#2%Ef>!yf{Y5GO1$x5Px9(8`HeFxk=2@Bd zq=KQyqnMX(1)4qbNdCpiUq`?DljnJ4(X3xbZWc7FSqSDdyvv?x2M%1B41WN(7r10| zVu>>_w>&$Nz9gs;RLod*gM=F0KT{w^TEJrr=@40$uF{l@U)juB@7_XplDa}shbO#9OcF5F4Wx|QR%>k*&FiFg= zpR+()Ms`q;qK@v{zdwHO8-AA?OM3~50rLkg=K7;-w-3Sp?#)#dI6-vXZ?{xxRG#nA z&u3(w_Lx4C+_rfEIn})HHz!#ff|QOK|ng*ROb9j*3Zo+IO&mS72FB-5ZTXhXu)*r$Q_~Y{1SGTgV1lXjrl;#-9iU?GUbS>8cVgMt-WHp?U$B~8xY0A2*2broBPny(Kl|kjx~X6o_U}kH)B59OTi25 zI#uBpB+upu`&z@uD0JWNs#F`TMU8AzZx3+mHQ4h;D3#Oc8Jb~U-938oeE`@}tOH7% z_4ze{yBuSYhL|QLY*qwog@Gkxq0^enA?!E`VP_E0=g)gk=dbwqViJxhJ0LVt^3NF^ zYzLkst+$?Hy#;Qmy0Hf}_4QRa)1@w$T&CyO&}>Ptsbb>IeK{|6bd-PjM~!ODL2IIM6I@w>m+s#pjW%oMY+D&fg_iUg{mv|h&Od+t>`BHViRGQn zkKvUN%CsGO>{gBD zX{Ubv8R+?{;|uRsD-6QAM7)`N@$N9U9=TiY4gvc)Xd0E5wX^a!Gr5U z5eb!+hPgGc5AIvlS-(a7lqkgj)10&KzS_Gxg)8=`@*aKso_6%c^bj@!_jJj+rv8Yx zWrqPIv%tlphi19Pug}_5tGemyKaAGVKaky!@`2vm>vD$(3tyC=9AnJ4ZUORy*dzJ- zopMeHZh+?7Z=Ag4)o`-1=Ylg`N8GnA)|pmN?^;B<=Vfj40^96bi-zT`sDvN8=!A@z z)>m2o-LWrAauD0j$oyZG$cAWng)nUHZDNFc>EMpFo$W8XT+Q&)ztI3hWhm&(Y)Ha*m$hKFz!EfvI`z&L99eKa8k!|dfS^Go!H%qJe zrWW2bc2>^BF-Jl|5~6Kfj&pp-Y;|r`_tegO8nx$GYGuFtm4HKNGl8OUV9fT~SM6ye z(HuoOmVC~-V+^5yLd9I1qsy?p))DMlF zGpfE;lTBUx*Yv3J9OGT(y}-?F-?R=r&K+#2KXho5eG^~otqbu@kb$0LqM^gO!9c7H z*;cC|COBV^XwM$uih=Ux!VkS23Sx?uN%Ay_AeaOsaAL)Za~bVaLmr>lp8u-UW$Eq_ zZT&N6y_#RJ%W#^%qwC6*Z!NDjRm^A@(JQm9Rx@DkvPh>PY8(x|ed@p^MJ(~3hgAd^ z77<2X`&I?#JmNzKRsV=Ma%2JtsY?nU2HmUq%&94G3ta&iXqyflUcL7x#J-btY@plo z+Yc_rapU#$PznCFI&4vub>l;^scPd2Tmr5y*p$6^VOgv76j8>Jdy|Wz`QJ$oDR=@C zc_h+epw@>$qx$e7xGFnbK76b%m@cO+?c|`f>+phNm1BKNmcEHeW0w|MT0t$k?74NZ zowi2vmgf$pT)&X7wxIG=-pc{y!-8k{UYXa|?*`$|@3T5BKWlJ3p2X~~8-FEKDE@3N zUnsTLPRFP=P)8Q~Lb?Jyle&i|r#=TDQ=%T7-%v(@1;3l#_nsrxU=H9wxA<$8`=9U^ z3z)9KqLX$vI@ELR^_vZKJ&vi|Pg|Cno*r*B_t)}`KRayzr!|PEN<=p2RaxO4cxhpr zMwL-SE16D&dfS&oH+5hX8Iw5R>K{sD8Bh(9UP1b5|6PIY#-te1MGjE$cQaoCGnh}E z!zyF$LOb+O5`Vz@$I#n^a|VAcNRE~3D$p~^QEE2uS9qBU5>Tl)B<~rvC%kew6K?Ka zjB~2I+5KcuLA=V(KX_%1-dp;9*Yb4@rVRhEZR9#$T~n(rutL6av$nDqp@NfHC+>)--vT_m-AD&~BRsMPSjh%AVCKgCmX{X-y3P zBr3WKt``n&AOVF^Zvl2E0>+O-wWff-)+9vUO*J?!lp_Gb=u5eV@a*vj8fD zGR3?9%uC@=`Z+B-Oi20}Eh3Qr+I1m2Gv>l;{GR=sJ? z2D`@WPSp@Sc$J7RCpnfkNs1d#KKgz@GXo zy&A-SoP&EWV}bSpS{8(p!SaY64XA4sG3Uv?BI@nE*^4DbFlfYK)KzpO3TFN$4jJvDAaGtMyPntRAN;XZkk-0$%$t98fm7CL0338`mZv-l6!sXOm zUq8@w`mD^wI`X7Y*H9G#x(05GW$gpE8X$96g7)fYaSa8N382mg@p6IJTZ~v$6_Xw> ze3=XUYYdn6MsDdSP4gGG=yfw{)cv1hXqA*}!IH85k zMt!sY>1=p^`*hBQg!iOD97t{{truPV8f(x8?BWkTQY3EUfrJumGKRf z#81>y{rfv*R;Z6$rz*o@U9Wt%_YL#{+dTE>?A1GmUnn%JG1=SoU77#h_ze?sJEcjD z4VIZjJbzHLAN)I*lEds_L$`P=U!vMHEYH4dYNG}H{7dHO#YGw@<%nOl>*K-=6U>0?tmbZ+JAmN#@$@BA&M zQO8OHmOw%SUL9pUaI`SA8cz)btiq9e$p8hKBh*zg{HW(C=DU+~C*xdxX))Rg%sclxrkWDwMynHgW`^>3iJ0Ug8DY(1IlpcE|C-Yy zN0xtIm$!8u4g$1Rjrm_Aj*yms-iJ&K86b}`G5q=RWA~OKTd2~{(9E5aGg--Vmx)KV zdoC_+0Yg|17heX>MbcT1jXqU*JHU{fvP^;2hh@0pe?GifPzRZey#Y{a5r-GtfJN8@ z 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 JnQ`_3{|mGCE0zEN literal 62526 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 Date: Fri, 15 Sep 2023 19:09:23 +0200 Subject: [PATCH 02/19] Fix axh{line,span} on polar axes. For axhline, set the underlying path's _interpolation_steps to GRIDLINE_INTERPOLATION_STEPS (180), which amounts to using the same logic to draw it as for drawing gridlines. This ensures that a polar axhline goes (due to interpolation) around the whole circle, rather than being a trivial line connecting a point to itself. Also update axvline for consistency. (Note that _interpolation_steps has is ignored for rectilinear transforms and thus the change doesn't slow down the common, rectilinear case.) Increase the number of interpolation steps of ax{v,h}span likewise to GRIDLINE_INTERPOLATION_STEPS (again for consistency), and more importantly switch them to using Rectangle rather than Polygon, so that a polar axhspan is drawn as an annulus. This is necessary due to a separate "bug", whereby the CLOSEPOLY step on a Polygon would generate (effectively) a segment that's unfortunately ignored by _interpolation_steps (as it doesn't appear explicitly), whereas Rectangle explicitly includes the closing (4th) segment before emitting a CLOSEPOLY. --- .../next_api_changes/behavior/26788-AL.rst | 6 +++++ doc/users/next_whats_new/polar-line-spans.rst | 5 ++++ lib/matplotlib/axes/_axes.py | 25 ++++++++++++++---- .../test_axes/axhvlinespan_interpolation.png | Bin 0 -> 28240 bytes lib/matplotlib/tests/test_axes.py | 12 +++++++++ lib/matplotlib/tests/test_path.py | 6 ++--- lib/matplotlib/transforms.py | 13 +++++++++ lib/matplotlib/widgets.py | 12 +++------ 8 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26788-AL.rst create mode 100644 doc/users/next_whats_new/polar-line-spans.rst create mode 100644 lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png diff --git a/doc/api/next_api_changes/behavior/26788-AL.rst b/doc/api/next_api_changes/behavior/26788-AL.rst new file mode 100644 index 000000000000..14573e870843 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26788-AL.rst @@ -0,0 +1,6 @@ +``axvspan`` and ``axhspan`` now return ``Rectangle``\s, not ``Polygons`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This change allows using `~.Axes.axhspan` to draw an annulus on polar axes. + +This change also affects other elements built via `~.Axes.axvspan` and +`~.Axes.axhspan`, such as ``Slider.poly``. diff --git a/doc/users/next_whats_new/polar-line-spans.rst b/doc/users/next_whats_new/polar-line-spans.rst new file mode 100644 index 000000000000..47bb382dbdbf --- /dev/null +++ b/doc/users/next_whats_new/polar-line-spans.rst @@ -0,0 +1,5 @@ +``axhline`` and ``axhspan`` on polar axes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... now draw circles and circular arcs (`~.Axes.axhline`) or annuli and wedges +(`~.Axes.axhspan`). diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9997e660f40c..0fcabac8c7c0 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -783,6 +783,7 @@ def axhline(self, y=0, xmin=0, xmax=1, **kwargs): trans = self.get_yaxis_transform(which='grid') l = mlines.Line2D([xmin, xmax], [y, y], transform=trans, **kwargs) self.add_line(l) + l.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS if scaley: self._request_autoscale_view("y") return l @@ -851,6 +852,7 @@ def axvline(self, x=0, ymin=0, ymax=1, **kwargs): trans = self.get_xaxis_transform(which='grid') l = mlines.Line2D([x, x], [ymin, ymax], transform=trans, **kwargs) self.add_line(l) + l.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS if scalex: self._request_autoscale_view("x") return l @@ -978,10 +980,17 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs): self._check_no_units([xmin, xmax], ['xmin', 'xmax']) (ymin, ymax), = self._process_unit_info([("y", [ymin, ymax])], kwargs) - verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin) - p = mpatches.Polygon(verts, **kwargs) + p = mpatches.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, **kwargs) p.set_transform(self.get_yaxis_transform(which="grid")) + # For Rectangles and non-separable transforms, add_patch can be buggy + # and update the x limits even though it shouldn't do so for an + # yaxis_transformed patch, so undo that update. + ix = self.dataLim.intervalx + mx = self.dataLim.minposx self.add_patch(p) + self.dataLim.intervalx = ix + self.dataLim.minposx = mx + p.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS self._request_autoscale_view("y") return p @@ -1034,11 +1043,17 @@ def axvspan(self, xmin, xmax, ymin=0, ymax=1, **kwargs): self._check_no_units([ymin, ymax], ['ymin', 'ymax']) (xmin, xmax), = self._process_unit_info([("x", [xmin, xmax])], kwargs) - verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] - p = mpatches.Polygon(verts, **kwargs) + p = mpatches.Rectangle((xmin, ymin), xmax - xmin, ymax - ymin, **kwargs) p.set_transform(self.get_xaxis_transform(which="grid")) - p.get_path()._interpolation_steps = 100 + # For Rectangles and non-separable transforms, add_patch can be buggy + # and update the y limits even though it shouldn't do so for an + # xaxis_transformed patch, so undo that update. + iy = self.dataLim.intervaly.copy() + my = self.dataLim.minposy self.add_patch(p) + self.dataLim.intervaly = iy + self.dataLim.minposy = my + p.get_path()._interpolation_steps = mpl.axis.GRIDLINE_INTERPOLATION_STEPS self._request_autoscale_view("x") return p diff --git a/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png b/lib/matplotlib/tests/baseline_images/test_axes/axhvlinespan_interpolation.png new file mode 100644 index 0000000000000000000000000000000000000000..3937cdf5b34c9a77d8003dd04967ee70ef4ab130 GIT binary patch literal 28240 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 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index ab9dab03e543..564bf6a86b52 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -8849,3 +8849,15 @@ def test_xylim_changed_shared(): axs[1].callbacks.connect("ylim_changed", events.append) axs[0].set(xlim=[1, 3], ylim=[2, 4]) assert events == [axs[1], axs[1]] + + +@image_comparison(["axhvlinespan_interpolation.png"], style="default") +def test_axhvlinespan_interpolation(): + ax = plt.figure().add_subplot(projection="polar") + ax.set_axis_off() + ax.axvline(.1, c="C0") + ax.axvspan(.2, .3, fc="C1") + ax.axvspan(.4, .5, .1, .2, fc="C2") + ax.axhline(1, c="C0", alpha=.5) + ax.axhspan(.8, .9, fc="C1", alpha=.5) + ax.axhspan(.6, .7, .8, .9, fc="C2", alpha=.5) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 0a1d6c6b5e52..8c0c32dc133b 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -142,11 +142,11 @@ def test_nonlinear_containment(): ax.set(xscale="log", ylim=(0, 1)) polygon = ax.axvspan(1, 10) assert polygon.get_path().contains_point( - ax.transData.transform((5, .5)), ax.transData) + ax.transData.transform((5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((.5, .5)), ax.transData) + ax.transData.transform((.5, .5)), polygon.get_transform()) assert not polygon.get_path().contains_point( - ax.transData.transform((50, .5)), ax.transData) + ax.transData.transform((50, .5)), polygon.get_transform()) @image_comparison(['arrow_contains_point.png'], diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index d04b59afa9d7..5a7fd125a29b 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -671,6 +671,7 @@ def intersection(bbox1, bbox2): y1 = np.minimum(bbox1.ymax, bbox2.ymax) return Bbox([[x0, y0], [x1, y1]]) if x0 <= x1 and y0 <= y1 else None + _default_minpos = np.array([np.inf, np.inf]) @@ -1011,6 +1012,10 @@ def minpos(self): """ return self._minpos + @minpos.setter + def minpos(self, val): + self._minpos[:] = val + @property def minposx(self): """ @@ -1022,6 +1027,10 @@ def minposx(self): """ return self._minpos[0] + @minposx.setter + def minposx(self, val): + self._minpos[0] = val + @property def minposy(self): """ @@ -1033,6 +1042,10 @@ def minposy(self): """ return self._minpos[1] + @minposy.setter + def minposy(self, val): + self._minpos[1] = val + def get_points(self): """ Get the points of the bounding box as an array of the form diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 0a31a9dd2529..771cfd714b91 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -433,8 +433,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None, Notes ----- Additional kwargs are passed on to ``self.poly`` which is the - `~matplotlib.patches.Polygon` that draws the slider knob. See the - `.Polygon` documentation for valid property names (``facecolor``, + `~matplotlib.patches.Rectangle` that draws the slider knob. See the + `.Rectangle` documentation for valid property names (``facecolor``, ``edgecolor``, ``alpha``, etc.). """ super().__init__(ax, orientation, closedmin, closedmax, @@ -577,16 +577,12 @@ def set_val(self, val): ---------- val : float """ - xy = self.poly.xy if self.orientation == 'vertical': - xy[1] = .25, val - xy[2] = .75, val + self.poly.set_height(val - self.poly.get_y()) self._handle.set_ydata([val]) else: - xy[2] = val, .75 - xy[3] = val, .25 + self.poly.set_width(val - self.poly.get_x()) self._handle.set_xdata([val]) - self.poly.xy = xy self.valtext.set_text(self._format(val)) if self.drawon: self.ax.figure.canvas.draw_idle() From a3bebed46725dc7203f459c62bc9eeb60a2a7338 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 7 Jul 2023 08:54:11 +0100 Subject: [PATCH 03/19] Use pybind11 in image module --- lib/matplotlib/tests/test_image.py | 15 +- setupext.py | 9 +- src/_image_wrapper.cpp | 329 +++++++++++------------------ src/py_converters_11.cpp | 20 ++ src/py_converters_11.h | 13 ++ 5 files changed, 179 insertions(+), 207 deletions(-) create mode 100644 src/py_converters_11.cpp create mode 100644 src/py_converters_11.h diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py index c9c959e96115..4fb4e65137d4 100644 --- a/lib/matplotlib/tests/test_image.py +++ b/lib/matplotlib/tests/test_image.py @@ -1468,17 +1468,26 @@ def test_str_norms(fig_test, fig_ref): def test__resample_valid_output(): resample = functools.partial(mpl._image.resample, transform=Affine2D()) - with pytest.raises(ValueError, match="must be a NumPy array"): + with pytest.raises(TypeError, match="incompatible function arguments"): resample(np.zeros((9, 9)), None) with pytest.raises(ValueError, match="different dimensionalities"): resample(np.zeros((9, 9)), np.zeros((9, 9, 4))) - with pytest.raises(ValueError, match="must be RGBA"): + with pytest.raises(ValueError, match="different dimensionalities"): + resample(np.zeros((9, 9, 4)), np.zeros((9, 9))) + with pytest.raises(ValueError, match="3D input array must be RGBA"): + resample(np.zeros((9, 9, 3)), np.zeros((9, 9, 4))) + with pytest.raises(ValueError, match="3D output array must be RGBA"): resample(np.zeros((9, 9, 4)), np.zeros((9, 9, 3))) - with pytest.raises(ValueError, match="Mismatched types"): + with pytest.raises(ValueError, match="mismatched types"): resample(np.zeros((9, 9), np.uint8), np.zeros((9, 9))) with pytest.raises(ValueError, match="must be C-contiguous"): resample(np.zeros((9, 9)), np.zeros((9, 9)).T) + out = np.zeros((9, 9)) + out.flags.writeable = False + with pytest.raises(ValueError, match="Output array must be writeable"): + resample(np.zeros((9, 9)), out) + def test_axesimage_get_shape(): # generate dummy image to test get_shape method diff --git a/setupext.py b/setupext.py index 9f78d88c87e8..049f46c65c7d 100644 --- a/setupext.py +++ b/setupext.py @@ -424,12 +424,13 @@ def get_extensions(self): add_libagg_flags(ext) yield ext # image - ext = Extension( + ext = Pybind11Extension( "matplotlib._image", [ "src/_image_wrapper.cpp", - "src/py_converters.cpp", - ]) - add_numpy_flags(ext) + "src/py_converters_11.cpp", + ], + cxx_std=11) + # Only need source code files agg_image_filters.cpp and agg_trans_affine.cpp add_libagg_flags_and_sources(ext) yield ext # path diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index ca6ae8b2226f..a63004ebb624 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -1,7 +1,8 @@ -#include "mplutils.h" +#include +#include + #include "_image_resample.h" -#include "numpy_cpp.h" -#include "py_converters.h" +#include "py_converters_11.h" /********************************************************************** @@ -49,8 +50,8 @@ const char* image_resample__doc__ = " The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; -static PyArrayObject * -_get_transform_mesh(PyObject *py_affine, npy_intp *dims) +static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, + const ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -58,240 +59,168 @@ _get_transform_mesh(PyObject *py_affine, npy_intp *dims) every pixel in the output image to the input image. This is used as a lookup table during the actual resampling. */ - PyObject *py_inverse = NULL; - npy_intp out_dims[3]; - - out_dims[0] = dims[0] * dims[1]; - out_dims[1] = 2; + // If attribute doesn't exist, raises Python AttributeError + auto inverse = transform.attr("inverted")(); - py_inverse = PyObject_CallMethod(py_affine, "inverted", NULL); - if (py_inverse == NULL) { - return NULL; - } + pybind11::array_t input_mesh({dims[0]*dims[1], 2L}); + auto p = input_mesh.mutable_data(); - numpy::array_view input_mesh(out_dims); - double *p = (double *)input_mesh.data(); - - for (npy_intp y = 0; y < dims[0]; ++y) { - for (npy_intp x = 0; x < dims[1]; ++x) { + for (auto y = 0; y < dims[0]; ++y) { + for (auto x = 0; x < dims[1]; ++x) { *p++ = (double)x; *p++ = (double)y; } } - PyObject *output_mesh = PyObject_CallMethod( - py_inverse, "transform", "O", input_mesh.pyobj_steal()); - - Py_DECREF(py_inverse); - - if (output_mesh == NULL) { - return NULL; - } - - PyArrayObject *output_mesh_array = - (PyArrayObject *)PyArray_ContiguousFromAny( - output_mesh, NPY_DOUBLE, 2, 2); + auto output_mesh = inverse.attr("transform")(input_mesh); - Py_DECREF(output_mesh); + auto output_mesh_array = + pybind11::array_t(output_mesh); - if (output_mesh_array == NULL) { - return NULL; - } + if (output_mesh_array.ndim() != 2) + throw std::runtime_error( + "Inverse transformed mesh array should be 2D not " + + std::to_string(output_mesh_array.ndim()) + "D"); return output_mesh_array; } -static PyObject * -image_resample(PyObject *self, PyObject* args, PyObject *kwargs) +// Using generic pybind::array for input and output arrays rather than the more usual +// pybind::array_t as function supports multiple array dtypes. +static void image_resample(pybind11::array input_array, + pybind11::array& output_array, + const pybind11::object& transform, + interpolation_e interpolation, + bool resample_, // Avoid name clash with resample() function + float alpha, + bool norm, + float radius) { - PyObject *py_input = NULL; - PyObject *py_output = NULL; - PyObject *py_transform = NULL; - resample_params_t params; + // Validate input_array + auto dtype = input_array.dtype(); // Validated when determine resampler below + auto ndim = input_array.ndim(); - PyArrayObject *input = NULL; - PyArrayObject *output = NULL; - PyArrayObject *transform_mesh = NULL; - int ndim; - int type; - - params.interpolation = NEAREST; - params.transform_mesh = NULL; - params.resample = false; - params.norm = false; - params.radius = 1.0; - params.alpha = 1.0; - - const char *kwlist[] = { - "input_array", "output_array", "transform", "interpolation", - "resample", "alpha", "norm", "radius", NULL }; - - if (!PyArg_ParseTupleAndKeywords( - args, kwargs, "OOO|iO&dO&d:resample", (char **)kwlist, - &py_input, &py_output, &py_transform, - ¶ms.interpolation, &convert_bool, ¶ms.resample, - ¶ms.alpha, &convert_bool, ¶ms.norm, ¶ms.radius)) { - return NULL; - } + if (ndim < 2 || ndim > 3) + throw std::invalid_argument("Input array must be a 2D or 3D array"); - if (params.interpolation < 0 || params.interpolation >= _n_interpolation) { - PyErr_Format(PyExc_ValueError, "Invalid interpolation value %d", - params.interpolation); - goto error; - } + if (ndim == 3 && input_array.shape(2) != 4) + throw std::invalid_argument( + "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + + std::to_string(ndim)); - input = (PyArrayObject *)PyArray_FromAny( - py_input, NULL, 2, 3, NPY_ARRAY_C_CONTIGUOUS, NULL); - if (!input) { - goto error; - } - ndim = PyArray_NDIM(input); - type = PyArray_TYPE(input); + // Ensure input array is contiguous, regardless of dtype + input_array = pybind11::array::ensure(input_array, pybind11::array::c_style); - if (!PyArray_Check(py_output)) { - PyErr_SetString(PyExc_ValueError, "Output array must be a NumPy array"); - goto error; - } - output = (PyArrayObject *)py_output; - if (PyArray_NDIM(output) != ndim) { - PyErr_Format( - PyExc_ValueError, - "Input (%dD) and output (%dD) have different dimensionalities.", - ndim, PyArray_NDIM(output)); - goto error; - } - // PyArray_FromAny above checks that input is 2D or 3D. - if (ndim == 3 && (PyArray_DIM(input, 2) != 4 || PyArray_DIM(output, 2) != 4)) { - PyErr_Format( - PyExc_ValueError, - "If 3D, input and output arrays must be RGBA with shape (M, N, 4); " - "got trailing dimensions of %" NPY_INTP_FMT " and %" NPY_INTP_FMT - " respectively", PyArray_DIM(input, 2), PyArray_DIM(output, 2)); - goto error; - } - if (PyArray_TYPE(output) != type) { - PyErr_SetString(PyExc_ValueError, "Mismatched types"); - goto error; - } - if (!PyArray_IS_C_CONTIGUOUS(output)) { - PyErr_SetString(PyExc_ValueError, "Output array must be C-contiguous"); - goto error; - } + // Validate output array + auto out_ndim = output_array.ndim(); + + if (out_ndim != ndim) + throw std::invalid_argument( + "Input (" + std::to_string(ndim) + "D) and output (" + std::to_string(out_ndim) + + "D) arrays have different dimensionalities"); + + if (out_ndim == 3 && output_array.shape(2) != 4) + throw std::invalid_argument( + "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + + std::to_string(out_ndim)); - if (py_transform == NULL || py_transform == Py_None) { + if (!output_array.dtype().is(dtype)) + throw std::invalid_argument("Input and output arrays have mismatched types"); + + if ((output_array.flags() & pybind11::array::c_style) == 0) + throw std::invalid_argument("Output array must be C-contiguous"); + + if (!output_array.writeable()) + throw std::invalid_argument("Output array must be writeable"); + + resample_params_t params; + params.interpolation = interpolation; + params.transform_mesh = nullptr; + params.resample = resample_; + params.norm = norm; + params.radius = radius; + params.alpha = alpha; + + // Only used if transform is not affine. + // Need to keep it in scope for the duration of this function. + pybind11::array_t transform_mesh; + + // Validate transform + if (transform.is_none()) { params.is_affine = true; } else { - PyObject *py_is_affine; - int py_is_affine2; - py_is_affine = PyObject_GetAttrString(py_transform, "is_affine"); - if (!py_is_affine) { - goto error; - } + // Raises Python AttributeError if no such attribute or TypeError if cast fails + bool is_affine = pybind11::cast(transform.attr("is_affine")); - py_is_affine2 = PyObject_IsTrue(py_is_affine); - Py_DECREF(py_is_affine); - - if (py_is_affine2 == -1) { - goto error; - } else if (py_is_affine2) { - if (!convert_trans_affine(py_transform, ¶ms.affine)) { - goto error; - } + if (is_affine) { + convert_trans_affine(transform, params.affine); params.is_affine = true; } else { - transform_mesh = _get_transform_mesh( - py_transform, PyArray_DIMS(output)); - if (!transform_mesh) { - goto error; - } - params.transform_mesh = (double *)PyArray_DATA(transform_mesh); + transform_mesh = _get_transform_mesh(transform, output_array.shape()); + params.transform_mesh = transform_mesh.data(); params.is_affine = false; } } if (auto resampler = (ndim == 2) ? ( - (type == NPY_UINT8) ? resample : - (type == NPY_INT8) ? resample : - (type == NPY_UINT16) ? resample : - (type == NPY_INT16) ? resample : - (type == NPY_FLOAT32) ? resample : - (type == NPY_FLOAT64) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : nullptr) : ( // ndim == 3 - (type == NPY_UINT8) ? resample : - (type == NPY_INT8) ? resample : - (type == NPY_UINT16) ? resample : - (type == NPY_INT16) ? resample : - (type == NPY_FLOAT32) ? resample : - (type == NPY_FLOAT64) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : + (dtype.is(pybind11::dtype::of())) ? resample : nullptr)) { Py_BEGIN_ALLOW_THREADS resampler( - PyArray_DATA(input), PyArray_DIM(input, 1), PyArray_DIM(input, 0), - PyArray_DATA(output), PyArray_DIM(output, 1), PyArray_DIM(output, 0), + input_array.data(), input_array.shape(1), input_array.shape(0), + output_array.mutable_data(), output_array.shape(1), output_array.shape(0), params); Py_END_ALLOW_THREADS - } else { - PyErr_SetString( - PyExc_ValueError, - "arrays must be of dtype byte, short, float32 or float64"); - goto error; - } - - Py_DECREF(input); - Py_XDECREF(transform_mesh); - Py_RETURN_NONE; - - error: - Py_XDECREF(input); - Py_XDECREF(transform_mesh); - return NULL; + } else + throw std::invalid_argument("arrays must be of dtype byte, short, float32 or float64"); } -static PyMethodDef module_functions[] = { - {"resample", (PyCFunction)image_resample, METH_VARARGS|METH_KEYWORDS, image_resample__doc__}, - {NULL} -}; - -static struct PyModuleDef moduledef = { - PyModuleDef_HEAD_INIT, "_image", NULL, 0, module_functions, -}; - -PyMODINIT_FUNC PyInit__image(void) -{ - PyObject *m; - - import_array(); - - m = PyModule_Create(&moduledef); - - if (m == NULL) { - return NULL; - } - - if (PyModule_AddIntConstant(m, "NEAREST", NEAREST) || - PyModule_AddIntConstant(m, "BILINEAR", BILINEAR) || - PyModule_AddIntConstant(m, "BICUBIC", BICUBIC) || - PyModule_AddIntConstant(m, "SPLINE16", SPLINE16) || - PyModule_AddIntConstant(m, "SPLINE36", SPLINE36) || - PyModule_AddIntConstant(m, "HANNING", HANNING) || - PyModule_AddIntConstant(m, "HAMMING", HAMMING) || - PyModule_AddIntConstant(m, "HERMITE", HERMITE) || - PyModule_AddIntConstant(m, "KAISER", KAISER) || - PyModule_AddIntConstant(m, "QUADRIC", QUADRIC) || - PyModule_AddIntConstant(m, "CATROM", CATROM) || - PyModule_AddIntConstant(m, "GAUSSIAN", GAUSSIAN) || - PyModule_AddIntConstant(m, "BESSEL", BESSEL) || - PyModule_AddIntConstant(m, "MITCHELL", MITCHELL) || - PyModule_AddIntConstant(m, "SINC", SINC) || - PyModule_AddIntConstant(m, "LANCZOS", LANCZOS) || - PyModule_AddIntConstant(m, "BLACKMAN", BLACKMAN) || - PyModule_AddIntConstant(m, "_n_interpolation", _n_interpolation)) { - Py_DECREF(m); - return NULL; - } - return m; +PYBIND11_MODULE(_image, m) { + pybind11::enum_(m, "interpolation_e") + .value("NEAREST", NEAREST) + .value("BILINEAR", BILINEAR) + .value("BICUBIC", BICUBIC) + .value("SPLINE16", SPLINE16) + .value("SPLINE36", SPLINE36) + .value("HANNING", HANNING) + .value("HAMMING", HAMMING) + .value("HERMITE", HERMITE) + .value("KAISER", KAISER) + .value("QUADRIC", QUADRIC) + .value("CATROM", CATROM) + .value("GAUSSIAN", GAUSSIAN) + .value("BESSEL", BESSEL) + .value("MITCHELL", MITCHELL) + .value("SINC", SINC) + .value("LANCZOS", LANCZOS) + .value("BLACKMAN", BLACKMAN) + .value("_n_interpolation", _n_interpolation) + .export_values(); + + m.def("resample", &image_resample, + pybind11::arg("input_array"), + pybind11::arg("output_array"), + pybind11::arg("transform"), + pybind11::arg("interpolation") = interpolation_e::NEAREST, + pybind11::arg("resample") = false, + pybind11::arg("alpha") = 1, + pybind11::arg("norm") = false, + pybind11::arg("radius") = 1, + image_resample__doc__); } diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp new file mode 100644 index 000000000000..fbcc8f809f44 --- /dev/null +++ b/src/py_converters_11.cpp @@ -0,0 +1,20 @@ +#include "py_converters_11.h" + +void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) +{ + // If None assume identity transform so leave affine unchanged + if (transform.is(pybind11::none())) + return; + + auto array = pybind11::array_t::ensure(transform); + if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) + throw std::invalid_argument("Invalid affine transformation matrix"); + + auto buffer = array.data(); + affine.sx = buffer[0]; + affine.shx = buffer[1]; + affine.tx = buffer[2]; + affine.shy = buffer[3]; + affine.sy = buffer[4]; + affine.ty = buffer[5]; +} diff --git a/src/py_converters_11.h b/src/py_converters_11.h new file mode 100644 index 000000000000..1017b2a3e5c1 --- /dev/null +++ b/src/py_converters_11.h @@ -0,0 +1,13 @@ +#ifndef MPL_PY_CONVERTERS_11_H +#define MPL_PY_CONVERTERS_11_H + +// pybind11 equivalent of py_converters.h + +#include +#include + +#include "agg_trans_affine.h" + +void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine); + +#endif From c14ad96e31898726f5841d9804363d9d4d4ba409 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 7 Jul 2023 11:32:18 +0100 Subject: [PATCH 04/19] Be explicit with array dimension types --- src/_image_wrapper.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index a63004ebb624..5167e196c9e5 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -51,7 +51,7 @@ const char* image_resample__doc__ = static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, - const ssize_t *dims) + const pybind11::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -62,7 +62,8 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // If attribute doesn't exist, raises Python AttributeError auto inverse = transform.attr("inverted")(); - pybind11::array_t input_mesh({dims[0]*dims[1], 2L}); + pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[2], 2}; + pybind11::array_t input_mesh(mesh_dims); auto p = input_mesh.mutable_data(); for (auto y = 0; y < dims[0]; ++y) { From 1a3c723dd8685cf6e8bd3f9f8bd9b15a77b94dd8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 17 Jul 2023 09:48:19 +0100 Subject: [PATCH 05/19] Review comments --- src/_image_resample.h | 2 -- src/_image_wrapper.cpp | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/_image_resample.h b/src/_image_resample.h index 10763fb01d37..60f5e66d4539 100644 --- a/src/_image_resample.h +++ b/src/_image_resample.h @@ -496,7 +496,6 @@ typedef enum { SINC, LANCZOS, BLACKMAN, - _n_interpolation } interpolation_e; @@ -629,7 +628,6 @@ static void get_filter(const resample_params_t ¶ms, { switch (params.interpolation) { case NEAREST: - case _n_interpolation: // Never should get here. Here to silence compiler warnings. break; diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 5167e196c9e5..13178e734d36 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -62,7 +62,7 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // If attribute doesn't exist, raises Python AttributeError auto inverse = transform.attr("inverted")(); - pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[2], 2}; + pybind11::ssize_t mesh_dims[2] = {dims[0]*dims[1], 2}; pybind11::array_t input_mesh(mesh_dims); auto p = input_mesh.mutable_data(); @@ -102,7 +102,7 @@ static void image_resample(pybind11::array input_array, auto dtype = input_array.dtype(); // Validated when determine resampler below auto ndim = input_array.ndim(); - if (ndim < 2 || ndim > 3) + if (ndim != 2 && ndim != 3) throw std::invalid_argument("Input array must be a 2D or 3D array"); if (ndim == 3 && input_array.shape(2) != 4) @@ -211,7 +211,6 @@ PYBIND11_MODULE(_image, m) { .value("SINC", SINC) .value("LANCZOS", LANCZOS) .value("BLACKMAN", BLACKMAN) - .value("_n_interpolation", _n_interpolation) .export_values(); m.def("resample", &image_resample, From bfaf1a7d406f115b71ed25676825cbc4d1eb9c22 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:08:37 +0100 Subject: [PATCH 06/19] Added curly braces around one-liners --- src/_image_wrapper.cpp | 27 ++++++++++++++++++--------- src/py_converters_11.cpp | 6 ++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 13178e734d36..93904d52bcc6 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -78,10 +78,11 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra auto output_mesh_array = pybind11::array_t(output_mesh); - if (output_mesh_array.ndim() != 2) + if (output_mesh_array.ndim() != 2) { throw std::runtime_error( "Inverse transformed mesh array should be 2D not " + std::to_string(output_mesh_array.ndim()) + "D"); + } return output_mesh_array; } @@ -102,13 +103,15 @@ static void image_resample(pybind11::array input_array, auto dtype = input_array.dtype(); // Validated when determine resampler below auto ndim = input_array.ndim(); - if (ndim != 2 && ndim != 3) + if (ndim != 2 && ndim != 3) { throw std::invalid_argument("Input array must be a 2D or 3D array"); + } - if (ndim == 3 && input_array.shape(2) != 4) + if (ndim == 3 && input_array.shape(2) != 4) { throw std::invalid_argument( "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + std::to_string(ndim)); + } // Ensure input array is contiguous, regardless of dtype input_array = pybind11::array::ensure(input_array, pybind11::array::c_style); @@ -116,24 +119,29 @@ static void image_resample(pybind11::array input_array, // Validate output array auto out_ndim = output_array.ndim(); - if (out_ndim != ndim) + if (out_ndim != ndim) { throw std::invalid_argument( "Input (" + std::to_string(ndim) + "D) and output (" + std::to_string(out_ndim) + "D) arrays have different dimensionalities"); + } - if (out_ndim == 3 && output_array.shape(2) != 4) + if (out_ndim == 3 && output_array.shape(2) != 4) { throw std::invalid_argument( "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + std::to_string(out_ndim)); + } - if (!output_array.dtype().is(dtype)) + if (!output_array.dtype().is(dtype)) { throw std::invalid_argument("Input and output arrays have mismatched types"); + } - if ((output_array.flags() & pybind11::array::c_style) == 0) + if ((output_array.flags() & pybind11::array::c_style) == 0) { throw std::invalid_argument("Output array must be C-contiguous"); + } - if (!output_array.writeable()) + if (!output_array.writeable()) { throw std::invalid_argument("Output array must be writeable"); + } resample_params_t params; params.interpolation = interpolation; @@ -187,8 +195,9 @@ static void image_resample(pybind11::array input_array, output_array.mutable_data(), output_array.shape(1), output_array.shape(0), params); Py_END_ALLOW_THREADS - } else + } else { throw std::invalid_argument("arrays must be of dtype byte, short, float32 or float64"); + } } diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp index fbcc8f809f44..47a5ec5b5a2c 100644 --- a/src/py_converters_11.cpp +++ b/src/py_converters_11.cpp @@ -3,12 +3,14 @@ void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) { // If None assume identity transform so leave affine unchanged - if (transform.is(pybind11::none())) + if (transform.is(pybind11::none())) { return; + } auto array = pybind11::array_t::ensure(transform); - if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) + if (!array || array.ndim() != 2 || array.shape(0) != 3 || array.shape(1) != 3) { throw std::invalid_argument("Invalid affine transformation matrix"); + } auto buffer = array.data(); affine.sx = buffer[0]; From c417369d21d2d5c671abb420513401492b2e20a2 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:20:18 +0100 Subject: [PATCH 07/19] Export _InterpolationType rather than interpolation_e --- src/_image_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index 93904d52bcc6..a41b803f7146 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -202,7 +202,7 @@ static void image_resample(pybind11::array input_array, PYBIND11_MODULE(_image, m) { - pybind11::enum_(m, "interpolation_e") + pybind11::enum_(m, "_InterpolationType") .value("NEAREST", NEAREST) .value("BILINEAR", BILINEAR) .value("BICUBIC", BICUBIC) From 5d90e482eace4312ad696a9cdf3aa85d3cba4fc8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sun, 24 Sep 2023 14:28:57 +0100 Subject: [PATCH 08/19] Miscellaneous review comments --- src/_image_wrapper.cpp | 28 +++++++++++++--------------- src/py_converters_11.cpp | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp index a41b803f7146..179f8d1301f7 100644 --- a/src/_image_wrapper.cpp +++ b/src/_image_wrapper.cpp @@ -10,9 +10,6 @@ * */ const char* image_resample__doc__ = -"resample(input_array, output_array, transform, interpolation=NEAREST, resample=False, alpha=1.0, norm=False, radius=1.0)\n" -"--\n\n" - "Resample input_array, blending it in-place into output_array, using an\n" "affine transformation.\n\n" @@ -50,8 +47,8 @@ const char* image_resample__doc__ = " The radius of the kernel, if method is SINC, LANCZOS or BLACKMAN.\n"; -static pybind11::array_t _get_transform_mesh(const pybind11::object& transform, - const pybind11::ssize_t *dims) +static pybind11::array_t +_get_transform_mesh(const pybind11::object& transform, const pybind11::ssize_t *dims) { /* TODO: Could we get away with float, rather than double, arrays here? */ @@ -90,14 +87,15 @@ static pybind11::array_t _get_transform_mesh(const pybind11::object& tra // Using generic pybind::array for input and output arrays rather than the more usual // pybind::array_t as function supports multiple array dtypes. -static void image_resample(pybind11::array input_array, - pybind11::array& output_array, - const pybind11::object& transform, - interpolation_e interpolation, - bool resample_, // Avoid name clash with resample() function - float alpha, - bool norm, - float radius) +static void +image_resample(pybind11::array input_array, + pybind11::array& output_array, + const pybind11::object& transform, + interpolation_e interpolation, + bool resample_, // Avoid name clash with resample() function + float alpha, + bool norm, + float radius) { // Validate input_array auto dtype = input_array.dtype(); // Validated when determine resampler below @@ -110,7 +108,7 @@ static void image_resample(pybind11::array input_array, if (ndim == 3 && input_array.shape(2) != 4) { throw std::invalid_argument( "3D input array must be RGBA with shape (M, N, 4), has trailing dimension of " + - std::to_string(ndim)); + std::to_string(input_array.shape(2))); } // Ensure input array is contiguous, regardless of dtype @@ -128,7 +126,7 @@ static void image_resample(pybind11::array input_array, if (out_ndim == 3 && output_array.shape(2) != 4) { throw std::invalid_argument( "3D output array must be RGBA with shape (M, N, 4), has trailing dimension of " + - std::to_string(out_ndim)); + std::to_string(output_array.shape(2))); } if (!output_array.dtype().is(dtype)) { diff --git a/src/py_converters_11.cpp b/src/py_converters_11.cpp index 47a5ec5b5a2c..982cc9ac6c46 100644 --- a/src/py_converters_11.cpp +++ b/src/py_converters_11.cpp @@ -3,7 +3,7 @@ void convert_trans_affine(const pybind11::object& transform, agg::trans_affine& affine) { // If None assume identity transform so leave affine unchanged - if (transform.is(pybind11::none())) { + if (transform.is_none()) { return; } From a9523f5fbc9552d088f7759f918d0c1ab766d236 Mon Sep 17 00:00:00 2001 From: rsp2210 Date: Mon, 25 Sep 2023 22:46:28 -0700 Subject: [PATCH 09/19] Resolved squash conflict and applied changes --- doc/api/next_api_changes/behavior/26902-RP.rst | 5 +++++ lib/matplotlib/lines.py | 18 ++---------------- lib/matplotlib/tests/test_lines.py | 7 ++----- 3 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/26902-RP.rst diff --git a/doc/api/next_api_changes/behavior/26902-RP.rst b/doc/api/next_api_changes/behavior/26902-RP.rst new file mode 100644 index 000000000000..3106de94fbd5 --- /dev/null +++ b/doc/api/next_api_changes/behavior/26902-RP.rst @@ -0,0 +1,5 @@ +``Line2D`` +~~~~~~~~~~ + +When creating a Line2D or using `.Line2D.set_xdata` and `.Line2D.set_ydata`, +passing x/y data as non sequence is now an error. diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 92d55a3fe6ae..31b931a52c82 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -1276,14 +1276,7 @@ def set_xdata(self, x): x : 1D array """ if not np.iterable(x): - # When deprecation cycle is completed - # raise RuntimeError('x must be a sequence') - _api.warn_deprecated( - since="3.7", - message="Setting data with a non sequence type " - "is deprecated since %(since)s and will be " - "remove %(removal)s") - x = [x, ] + raise RuntimeError('x must be a sequence') self._xorig = copy.copy(x) self._invalidx = True self.stale = True @@ -1297,14 +1290,7 @@ def set_ydata(self, y): y : 1D array """ if not np.iterable(y): - # When deprecation cycle is completed - # raise RuntimeError('y must be a sequence') - _api.warn_deprecated( - since="3.7", - message="Setting data with a non sequence type " - "is deprecated since %(since)s and will be " - "remove %(removal)s") - y = [y, ] + raise RuntimeError('y must be a sequence') self._yorig = copy.copy(y) self._invalidy = True self.stale = True diff --git a/lib/matplotlib/tests/test_lines.py b/lib/matplotlib/tests/test_lines.py index 4f23e6969b0b..68e378a20f88 100644 --- a/lib/matplotlib/tests/test_lines.py +++ b/lib/matplotlib/tests/test_lines.py @@ -92,12 +92,9 @@ def test_invalid_line_data(): mlines.Line2D([], 1) line = mlines.Line2D([], []) - # when deprecation cycle is completed - # with pytest.raises(RuntimeError, match='x must be'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): + with pytest.raises(RuntimeError, match='x must be'): line.set_xdata(0) - # with pytest.raises(RuntimeError, match='y must be'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): + with pytest.raises(RuntimeError, match='y must be'): line.set_ydata(0) From a16721f0771be2816114172574af2c2a36b8fbe2 Mon Sep 17 00:00:00 2001 From: ananya314 <57040724+ananya314@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:36:02 -0400 Subject: [PATCH 10/19] Fix 26872-AD.rst --- doc/api/next_api_changes/removals/26872-AD.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/api/next_api_changes/removals/26872-AD.rst b/doc/api/next_api_changes/removals/26872-AD.rst index 2f678ffc3cfd..411359813e51 100644 --- a/doc/api/next_api_changes/removals/26872-AD.rst +++ b/doc/api/next_api_changes/removals/26872-AD.rst @@ -1,6 +1,5 @@ -``repeat`` -~~~~~~~~~~ -... of `.TimedAnimation` is removed without replacements. -``save_count`` -~~~~~~~~~~~~~~ -... of `.FuncAnimation` is removed without replacements. +``Animation`` attributes +~~~~~~~~~~~~~~~~~~~~~~~~ + +The attributes ``repeat`` of `.TimedAnimation` and subclasses and +``save_count`` of `.FuncAnimation` are considered private and removed. From c60622a3fd8bb010d6fe6f1e71651e78e3775e4c Mon Sep 17 00:00:00 2001 From: wemi3 <62965919+wemi3@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:36:47 -0700 Subject: [PATCH 11/19] 26865 Removed deprecations from quiver.py (#26918) * 26865 Removed deprecations from quiver.py * Added rst file * updated quiver.pyi * removed extra lines * removed code stubs, edited rst file * Update lib/matplotlib/quiver.py Co-authored-by: Elliott Sales de Andrade * Update doc/api/next_api_changes/removals/26918-EW.rst Co-authored-by: Oscar Gustafsson --------- Co-authored-by: Elliott Sales de Andrade Co-authored-by: Oscar Gustafsson --- doc/api/next_api_changes/removals/26918-EW.rst | 3 +++ lib/matplotlib/quiver.py | 4 ---- lib/matplotlib/quiver.pyi | 4 ---- 3 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26918-EW.rst diff --git a/doc/api/next_api_changes/removals/26918-EW.rst b/doc/api/next_api_changes/removals/26918-EW.rst new file mode 100644 index 000000000000..454f35d5e200 --- /dev/null +++ b/doc/api/next_api_changes/removals/26918-EW.rst @@ -0,0 +1,3 @@ +``Quiver.quiver_doc`` and ``Barbs.barbs_doc`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... are removed. These are the doc-string and should not be accessible as a named class member. diff --git a/lib/matplotlib/quiver.py b/lib/matplotlib/quiver.py index c8f8ba566106..52f56deb40c0 100644 --- a/lib/matplotlib/quiver.py +++ b/lib/matplotlib/quiver.py @@ -721,8 +721,6 @@ def _h_arrows(self, length): # Mask handling is deferred to the caller, _make_verts. return X, Y - quiver_doc = _api.deprecated("3.7")(property(lambda self: _quiver_doc)) - _barbs_doc = r""" Plot a 2D field of barbs. @@ -1177,5 +1175,3 @@ def set_offsets(self, xy): xy = np.column_stack((x, y)) super().set_offsets(xy) self.stale = True - - barbs_doc = _api.deprecated("3.7")(property(lambda self: _barbs_doc)) diff --git a/lib/matplotlib/quiver.pyi b/lib/matplotlib/quiver.pyi index c673c5dd3aff..2a043a92b4b5 100644 --- a/lib/matplotlib/quiver.pyi +++ b/lib/matplotlib/quiver.pyi @@ -125,8 +125,6 @@ class Quiver(mcollections.PolyCollection): def set_UVC( self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... ) -> None: ... - @property - def quiver_doc(self) -> str: ... class Barbs(mcollections.PolyCollection): sizes: dict[str, float] @@ -183,5 +181,3 @@ class Barbs(mcollections.PolyCollection): self, U: ArrayLike, V: ArrayLike, C: ArrayLike | None = ... ) -> None: ... def set_offsets(self, xy: ArrayLike) -> None: ... - @property - def barbs_doc(self) -> str: ... From d69ace945fd80ae8356227b055a4b94142f32f62 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 22 Sep 2023 18:12:51 -0700 Subject: [PATCH 12/19] DOC: improve removal for julian dates [ci doc] --- doc/api/next_api_changes/removals/26852-OG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/api/next_api_changes/removals/26852-OG.rst b/doc/api/next_api_changes/removals/26852-OG.rst index dc7d595f575f..08ad0105b70a 100644 --- a/doc/api/next_api_changes/removals/26852-OG.rst +++ b/doc/api/next_api_changes/removals/26852-OG.rst @@ -3,3 +3,10 @@ ... of the `.dates` module are removed without replacements. These were undocumented and not exported. + +Julian dates in Matplotlib were calculated from a Julian date epoch: ``jdate = +(date - np.datetime64(EPOCH)) / np.timedelta64(1, 'D')``. Conversely, a Julian +date was converted to datetime as ``date = np.timedelta64(int(jdate * 24 * +3600), 's') + np.datetime64(EPOCH)``. Matplotlib was using +``EPOCH='-4713-11-24T12:00'`` so that 2000-01-01 at 12:00 is 2_451_545.0 (see +`). From ab94dd766af49d996f8c48ee88e75944277ef574 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 25 Sep 2023 11:57:32 -0700 Subject: [PATCH 13/19] DOC: add a couple more placement examples, crosslink axes_grid [ci doc] Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .../demo_colorbar_with_axes_divider.py | 6 + .../demo_colorbar_with_inset_locator.py | 10 +- .../users_explain/axes/colorbar_placement.py | 113 ++++++++++++++++-- .../axes/constrainedlayout_guide.py | 4 +- 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py index e314c2dcea21..9e4611c65bb7 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_axes_divider.py @@ -1,4 +1,6 @@ """ +.. _demo-colorbar-with-axes-divider: + ========================= Colorbar with AxesDivider ========================= @@ -8,6 +10,10 @@ method of the `.AxesDivider` can then be used to create a new axes on a given side ("top", "right", "bottom", or "left") of the original axes. This example uses `.append_axes` to add colorbars next to axes. + +Users should consider simply passing the main axes to the *ax* keyword argument of +`~.Figure.colorbar` instead of creating a locatable axes manually like this. +See :ref:`colorbar_placement`. """ import matplotlib.pyplot as plt diff --git a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py index 0c8d48e23101..8ec7d0e7271b 100644 --- a/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py +++ b/galleries/examples/axes_grid1/demo_colorbar_with_inset_locator.py @@ -1,15 +1,21 @@ """ +.. _demo-colorbar-with-inset-locator: + ============================================================== Controlling the position and size of colorbars with Inset Axes ============================================================== -This example shows how to control the position, height, and width of -colorbars using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. +This example shows how to control the position, height, and width of colorbars +using `~mpl_toolkits.axes_grid1.inset_locator.inset_axes`. Inset axes placement is controlled as for legends: either by providing a *loc* option ("upper right", "best", ...), or by providing a locator with respect to the parent bbox. Parameters such as *bbox_to_anchor* and *borderpad* likewise work in the same way, and are also demonstrated here. + +Users should consider using `.Axes.inset_axes` instead (see +:ref:`colorbar_placement`). + """ import matplotlib.pyplot as plt diff --git a/galleries/users_explain/axes/colorbar_placement.py b/galleries/users_explain/axes/colorbar_placement.py index de767a4fa130..1e43d4940a98 100644 --- a/galleries/users_explain/axes/colorbar_placement.py +++ b/galleries/users_explain/axes/colorbar_placement.py @@ -10,7 +10,11 @@ Colorbars indicate the quantitative extent of image data. Placing in a figure is non-trivial because room needs to be made for them. -The simplest case is just attaching a colorbar to each axes: +Automatic placement of colorbars +================================ + +The simplest case is just attaching a colorbar to each axes. Note in this +example that the colorbars steal some space from the parent axes. """ import matplotlib.pyplot as plt import numpy as np @@ -28,9 +32,9 @@ fig.colorbar(pcm, ax=ax) # %% -# The first column has the same type of data in both rows, so it may -# be desirable to combine the colorbar which we do by calling -# `.Figure.colorbar` with a list of axes instead of a single axes. +# The first column has the same type of data in both rows, so it may be +# desirable to have just one colorbar. We do this by passing `.Figure.colorbar` +# a list of axes with the *ax* kwarg. fig, axs = plt.subplots(2, 2) cmaps = ['RdBu_r', 'viridis'] @@ -41,6 +45,27 @@ cmap=cmaps[col]) fig.colorbar(pcm, ax=axs[:, col], shrink=0.6) +# %% +# The stolen space can lead to axes in the same subplot layout +# being different sizes, which is often undesired if the the +# x-axis on each plot is meant to be comparable as in the following: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True) +X = np.random.randn(20, 20) +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + +# %% +# This is usually undesired, and can be worked around in various ways, e.g. +# adding a colorbar to the other axes and then removing it. However, the most +# straightforward is to use :ref:`constrained layout `: + +fig, axs = plt.subplots(2, 1, figsize=(4, 5), sharex=True, layout='constrained') +axs[0].plot(np.sum(X, axis=0)) +axs[1].pcolormesh(X) +fig.colorbar(pcm, ax=axs[1], shrink=0.6) + # %% # Relatively complicated colorbar layouts are possible using this # paradigm. Note that this example works far better with @@ -56,8 +81,67 @@ fig.colorbar(pcm, ax=[axs[2, 1]], location='left') # %% -# Colorbars with fixed-aspect-ratio axes -# ====================================== +# Adjusting the spacing between colorbars and parent axes +# ======================================================= +# +# The distance a colorbar is from the parent axes can be adjusted with the +# *pad* keyword argument. This is in units of fraction of the parent axes +# width, and the default for a vertical axes is 0.05 (or 0.15 for a horizontal +# axes). + +fig, axs = plt.subplots(3, 1, layout='constrained', figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("layout='constrained'") + +# %% +# Note that if you do not use constrained layout, the pad command makes the +# parent axes shrink: + +fig, axs = plt.subplots(3, 1, figsize=(5, 5)) +for ax, pad in zip(axs, [0.025, 0.05, 0.1]): + pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') + fig.colorbar(pcm, ax=ax, pad=pad, label=f'pad: {pad}') +fig.suptitle("No layout manager") + +# %% +# Manual placement of colorbars +# ============================= +# +# Sometimes the automatic placement provided by ``colorbar`` does not +# give the desired effect. We can manually create an axes and tell +# ``colorbar`` to use that axes by passing the axes to the *cax* keyword +# argument. +# +# Using ``inset_axes`` +# -------------------- +# +# We can manually create any type of axes for the colorbar to use, but an +# `.Axes.inset_axes` is useful because it is a child of the parent axes and can +# be positioned relative to the parent. Here we add a colorbar centered near +# the bottom of the parent axes. + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([0.3, 0.07, 0.4, 0.04]) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# `.Axes.inset_axes` can also specify its position in data coordinates +# using the *transform* keyword argument if you want your axes at a +# certain data position on the graph: + +fig, ax = plt.subplots(layout='constrained', figsize=(4, 4)) +pcm = ax.pcolormesh(np.random.randn(20, 20), cmap='viridis') +ax.set_ylim([-4, 20]) +cax = ax.inset_axes([7.5, -1.7, 5, 1.2], transform=ax.transData) +fig.colorbar(pcm, cax=cax, orientation='horizontal') + +# %% +# Colorbars attached to fixed-aspect-ratio axes +# --------------------------------------------- # # Placing colorbars for axes with a fixed aspect ratio pose a particular # challenge as the parent axes changes size depending on the data view. @@ -77,9 +161,10 @@ fig.colorbar(pcm, ax=ax, shrink=0.6) # %% -# One way around this issue is to use an `.Axes.inset_axes` to locate the -# axes in axes coordinates. Note that if you zoom in on the axes, and -# change the shape of the axes, the colorbar will also change position. +# We solve this problem using `.Axes.inset_axes` to locate the axes in "axes +# coordinates" (see :ref:`transforms_tutorial`). Note that if you zoom in on +# the parent axes, and thus change the shape of it, the colorbar will also +# change position. fig, axs = plt.subplots(2, 2, layout='constrained') cmaps = ['RdBu_r', 'viridis'] @@ -94,6 +179,12 @@ ax.set_aspect(1/2) if row == 1: cax = ax.inset_axes([1.04, 0.2, 0.05, 0.6]) - fig.colorbar(pcm, ax=ax, cax=cax) + fig.colorbar(pcm, cax=cax) -plt.show() +# %% +# .. seealso:: +# +# :ref:`axes_grid` has methods for manually creating colorbar axes as well: +# +# - :ref:`demo-colorbar-with-inset-locator` +# - :ref:`demo-colorbar-with-axes-divider` diff --git a/galleries/users_explain/axes/constrainedlayout_guide.py b/galleries/users_explain/axes/constrainedlayout_guide.py index 0a2752674c6a..260a4f76bf71 100644 --- a/galleries/users_explain/axes/constrainedlayout_guide.py +++ b/galleries/users_explain/axes/constrainedlayout_guide.py @@ -4,9 +4,9 @@ .. _constrainedlayout_guide: -================================ +======================== Constrained Layout Guide -================================ +======================== Use *constrained layout* to fit plots within your figure cleanly. From afcc560f3ea70dc9f3964dabf2b7632118f6085d Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Tue, 26 Sep 2023 15:05:25 -0500 Subject: [PATCH 14/19] [TYP] Remove some stubtest allowlist entries Mark the expected interface for some things such as Transform.input_dims as read-only via a property and remove redundant versions in subclasses Same for some offsetbox code Fix rcsetup ignores --- ci/mypy-stubtest-allowlist.txt | 18 ------------------ lib/matplotlib/offsetbox.pyi | 8 ++++++-- lib/matplotlib/rcsetup.pyi | 9 ++++++--- lib/matplotlib/transforms.pyi | 20 +++++++++----------- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e0890b3f7117..b92ef9c67688 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -32,18 +32,6 @@ matplotlib.ticker.LogitLocator.nonsingular matplotlib.backend_bases._Mode.__new__ matplotlib.units.Number.__hash__ -# Property read-write vs read-only weirdness, fix if possible -matplotlib.offsetbox.DraggableBase.canvas -matplotlib.offsetbox.DraggableBase.cids -matplotlib.transforms.BboxTransform.is_separable -matplotlib.transforms.BboxTransformFrom.is_separable -matplotlib.transforms.BboxTransformTo.is_separable -matplotlib.transforms.BlendedAffine2D.is_separable -matplotlib.transforms.CompositeGenericTransform.is_separable -matplotlib.transforms.TransformWrapper.input_dims -matplotlib.transforms.TransformWrapper.is_separable -matplotlib.transforms.TransformWrapper.output_dims - # 3.6 Pending deprecations matplotlib.figure.Figure.set_constrained_layout matplotlib.figure.Figure.set_constrained_layout_pads @@ -148,16 +136,10 @@ matplotlib.text.Text.set_weight matplotlib.axes._base._AxesBase.get_fc matplotlib.axes._base._AxesBase.set_fc -# Other dynamic python behaviors not type hinted -matplotlib.rcsetup.defaultParams - # Maybe should be abstractmethods, required for subclasses, stubs define once matplotlib.tri.*TriInterpolator.__call__ matplotlib.tri.*TriInterpolator.gradient -# Functionally a method call, but actually a class instance, type hinted as former -matplotlib.rcsetup.validate_fillstyle - # TypeVar used only in type hints matplotlib.backend_bases.FigureCanvasBase._T matplotlib.backend_managers.ToolManager._T diff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi index 09f89aed2bc8..c222a9b2973e 100644 --- a/lib/matplotlib/offsetbox.pyi +++ b/lib/matplotlib/offsetbox.pyi @@ -280,11 +280,15 @@ class AnnotationBbox(martist.Artist, mtext._AnnotationBase): class DraggableBase: ref_artist: martist.Artist got_artist: bool - canvas: FigureCanvasBase - cids: list[int] mouse_x: int mouse_y: int background: Any + + @property + def canvas(self) -> FigureCanvasBase: ... + @property + def cids(self) -> list[int]: ... + def __init__(self, ref_artist: martist.Artist, use_blit: bool = ...) -> None: ... def on_motion(self, evt: Event) -> None: ... def on_pick(self, evt: Event) -> None: ... diff --git a/lib/matplotlib/rcsetup.pyi b/lib/matplotlib/rcsetup.pyi index 8a8a9e71d666..70e94a7694a9 100644 --- a/lib/matplotlib/rcsetup.pyi +++ b/lib/matplotlib/rcsetup.pyi @@ -129,9 +129,9 @@ def validate_fontstretch( def validate_font_properties(s: Any) -> dict[str, Any]: ... def validate_whiskers(s: Any) -> list[float] | float: ... def validate_ps_distiller(s: Any) -> None | Literal["ghostscript", "xpdf"]: ... -def validate_fillstyle( - s: Any, -) -> Literal["full", "left", "right", "bottom", "top", "none"]: ... + +validate_fillstyle: ValidateInStrings + def validate_fillstylelist( s: Any, ) -> list[Literal["full", "left", "right", "bottom", "top", "none"]]: ... @@ -152,3 +152,6 @@ def validate_hist_bins( ) -> Literal["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"] | int | list[ float ]: ... + +# At runtime is added in __init__.py +defaultParams: dict[str, Any] diff --git a/lib/matplotlib/transforms.pyi b/lib/matplotlib/transforms.pyi index 68e55612b7f1..90a527e5bfc5 100644 --- a/lib/matplotlib/transforms.pyi +++ b/lib/matplotlib/transforms.pyi @@ -175,12 +175,17 @@ class LockableBbox(BboxBase): def locked_y1(self, y1: float | None) -> None: ... class Transform(TransformNode): - input_dims: int | None - output_dims: int | None - is_separable: bool - # Implemented as a standard attr in base class, but functionally readonly and some subclasses implement as such + + # Implemented as a standard attrs in base class, but functionally readonly and some subclasses implement as such + @property + def input_dims(self) -> int | None: ... + @property + def output_dims(self) -> int | None: ... + @property + def is_separable(self) -> bool: ... @property def has_inverse(self) -> bool: ... + def __add__(self, other: Transform) -> Transform: ... @property def depth(self) -> int: ... @@ -225,8 +230,6 @@ class Affine2DBase(AffineBase): input_dims: Literal[2] output_dims: Literal[2] def frozen(self) -> Affine2D: ... - @property - def is_separable(self): ... def to_values(self) -> tuple[float, float, float, float, float, float]: ... class Affine2D(Affine2DBase): @@ -255,7 +258,6 @@ class _BlendedMixin: class BlendedGenericTransform(_BlendedMixin, Transform): input_dims: Literal[2] output_dims: Literal[2] - is_separable: bool pass_through: bool def __init__( self, x_transform: Transform, y_transform: Transform, **kwargs @@ -265,8 +267,6 @@ class BlendedGenericTransform(_BlendedMixin, Transform): def contains_branch(self, other: Transform) -> Literal[False]: ... @property def is_affine(self) -> bool: ... - @property - def has_inverse(self) -> bool: ... class BlendedAffine2D(_BlendedMixin, Affine2DBase): def __init__( @@ -279,8 +279,6 @@ def blended_transform_factory( class CompositeGenericTransform(Transform): pass_through: bool - input_dims: int | None - output_dims: int | None def __init__(self, a: Transform, b: Transform, **kwargs) -> None: ... class CompositeAffine2D(Affine2DBase): From e8f21d271a498fb6bcee936eae239ce7298e8a9e Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 27 Sep 2023 11:53:16 -0500 Subject: [PATCH 15/19] add tomli to rstcheck extras required for reading config from pyproject.toml on py<3.11 --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18707e44e5d0..05a14a1a08cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,3 +55,4 @@ repos: - id: rstcheck additional_dependencies: - sphinx>=1.8.1 + - tomli From 6dadeca73c5d5a60999e68351018fa3928db55a1 Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Wed, 27 Sep 2023 13:07:30 -0500 Subject: [PATCH 16/19] Add ArrayLike to scatter c arg type hint Closes #26936 --- lib/matplotlib/axes/_axes.pyi | 2 +- lib/matplotlib/pyplot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 96ea087f7eb9..9602db3b950c 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -402,7 +402,7 @@ class Axes(_AxesBase): x: float | ArrayLike, y: float | ArrayLike, s: float | ArrayLike | None = ..., - c: Sequence[ColorType] | ColorType | None = ..., + c: ArrayLike | Sequence[ColorType] | ColorType | None = ..., marker: MarkerType | None = ..., cmap: str | Colormap | None = ..., norm: str | Normalize | None = ..., diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 74a73725d5d9..f48ca312e58e 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3670,7 +3670,7 @@ def scatter( x: float | ArrayLike, y: float | ArrayLike, s: float | ArrayLike | None = None, - c: Sequence[ColorType] | ColorType | None = None, + c: ArrayLike | Sequence[ColorType] | ColorType | None = None, marker: MarkerType | None = None, cmap: str | Colormap | None = None, norm: str | Normalize | None = None, From 3f72eca907dbe6ebedd4e4437139ee6393b12947 Mon Sep 17 00:00:00 2001 From: Aditi Gautam Date: Fri, 22 Sep 2023 11:34:20 -0700 Subject: [PATCH 17/19] Cleaned up the span_where class method from Polycollections. --- ci/mypy-stubtest-allowlist.txt | 1 - doc/api/artist_api.rst | 2 +- .../next_api_changes/removals/26874-AG.rst | 4 ++ doc/users/prev_whats_new/whats_new_1.4.rst | 2 +- lib/matplotlib/axes/_axes.pyi | 3 +- lib/matplotlib/collections.py | 45 ------------------- lib/matplotlib/collections.pyi | 12 ----- lib/matplotlib/pyplot.py | 3 +- 8 files changed, 8 insertions(+), 64 deletions(-) create mode 100644 doc/api/next_api_changes/removals/26874-AG.rst diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index e0890b3f7117..78f269a5f5bf 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -52,7 +52,6 @@ matplotlib.figure.Figure.set_tight_layout # 3.7 deprecations matplotlib.cm.register_cmap matplotlib.cm.unregister_cmap -matplotlib.collections.PolyCollection.span_where # 3.8 deprecations matplotlib.cbook.get_sample_data diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index 3903bbd5924d..df7a6e8b1c04 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -11,7 +11,7 @@ Inheritance Diagrams ==================== -.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.BrokenBarHCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet +.. inheritance-diagram:: matplotlib.axes._axes.Axes matplotlib.axes._base._AxesBase matplotlib.axis.Axis matplotlib.axis.Tick matplotlib.axis.XAxis matplotlib.axis.XTick matplotlib.axis.YAxis matplotlib.axis.YTick matplotlib.collections.AsteriskPolygonCollection matplotlib.collections.CircleCollection matplotlib.collections.Collection matplotlib.collections.EllipseCollection matplotlib.collections.EventCollection matplotlib.collections.LineCollection matplotlib.collections.PatchCollection matplotlib.collections.PathCollection matplotlib.collections.PolyCollection matplotlib.collections.QuadMesh matplotlib.collections.RegularPolyCollection matplotlib.collections.StarPolygonCollection matplotlib.collections.TriMesh matplotlib.collections._CollectionWithSizes matplotlib.contour.ClabelText matplotlib.contour.ContourSet matplotlib.contour.QuadContourSet matplotlib.figure.FigureBase matplotlib.figure.Figure matplotlib.figure.SubFigure matplotlib.image.AxesImage matplotlib.image.BboxImage matplotlib.image.FigureImage matplotlib.image.NonUniformImage matplotlib.image.PcolorImage matplotlib.image._ImageBase matplotlib.legend.Legend matplotlib.lines.Line2D matplotlib.offsetbox.AnchoredOffsetbox matplotlib.offsetbox.AnchoredText matplotlib.offsetbox.AnnotationBbox matplotlib.offsetbox.AuxTransformBox matplotlib.offsetbox.DrawingArea matplotlib.offsetbox.HPacker matplotlib.offsetbox.OffsetBox matplotlib.offsetbox.OffsetImage matplotlib.offsetbox.PackerBase matplotlib.offsetbox.PaddedBox matplotlib.offsetbox.TextArea matplotlib.offsetbox.VPacker matplotlib.patches.Annulus matplotlib.patches.Arc matplotlib.patches.Arrow matplotlib.patches.Circle matplotlib.patches.CirclePolygon matplotlib.patches.ConnectionPatch matplotlib.patches.Ellipse matplotlib.patches.FancyArrow matplotlib.patches.FancyArrowPatch matplotlib.patches.FancyBboxPatch matplotlib.patches.Patch matplotlib.patches.PathPatch matplotlib.patches.Polygon matplotlib.patches.Rectangle matplotlib.patches.RegularPolygon matplotlib.patches.Shadow matplotlib.patches.StepPatch matplotlib.patches.Wedge matplotlib.projections.geo.AitoffAxes matplotlib.projections.geo.GeoAxes matplotlib.projections.geo.HammerAxes matplotlib.projections.geo.LambertAxes matplotlib.projections.geo.MollweideAxes matplotlib.projections.polar.PolarAxes matplotlib.projections.polar.RadialAxis matplotlib.projections.polar.RadialTick matplotlib.projections.polar.ThetaAxis matplotlib.projections.polar.ThetaTick matplotlib.quiver.Barbs matplotlib.quiver.Quiver matplotlib.quiver.QuiverKey matplotlib.spines.Spine matplotlib.table.Cell matplotlib.table.Table matplotlib.text.Annotation matplotlib.text.Text matplotlib.tri.TriContourSet :parts: 1 :private-bases: diff --git a/doc/api/next_api_changes/removals/26874-AG.rst b/doc/api/next_api_changes/removals/26874-AG.rst new file mode 100644 index 000000000000..ad305cf9d96c --- /dev/null +++ b/doc/api/next_api_changes/removals/26874-AG.rst @@ -0,0 +1,4 @@ +``collections.PolyCollection.span_where`` and ``collections.BrokenBarHCollection`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +... removed as it was deprecated during 3.7. Use ``fill_between`` instead diff --git a/doc/users/prev_whats_new/whats_new_1.4.rst b/doc/users/prev_whats_new/whats_new_1.4.rst index 39eefa81b168..eb0e93fd8883 100644 --- a/doc/users/prev_whats_new/whats_new_1.4.rst +++ b/doc/users/prev_whats_new/whats_new_1.4.rst @@ -221,7 +221,7 @@ Added size related functions to specialized `.Collection`\s Added the ``get_size`` and ``set_size`` functions to control the size of elements of specialized collections ( :class:`~matplotlib.collections.AsteriskPolygonCollection` -:class:`~matplotlib.collections.BrokenBarHCollection` +``matplotlib.collections.BrokenBarHCollection`` :class:`~matplotlib.collections.CircleCollection` :class:`~matplotlib.collections.PathCollection` :class:`~matplotlib.collections.PolyCollection` diff --git a/lib/matplotlib/axes/_axes.pyi b/lib/matplotlib/axes/_axes.pyi index 96ea087f7eb9..5dff356be4a9 100644 --- a/lib/matplotlib/axes/_axes.pyi +++ b/lib/matplotlib/axes/_axes.pyi @@ -6,7 +6,6 @@ from matplotlib.backend_bases import RendererBase from matplotlib.collections import ( Collection, LineCollection, - BrokenBarHCollection, PathCollection, PolyCollection, EventCollection, @@ -282,7 +281,7 @@ class Axes(_AxesBase): *, data=..., **kwargs - ) -> BrokenBarHCollection: ... + ) -> PolyCollection: ... def stem( self, *args: ArrayLike | str, diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 81db24d0c026..cc20e5cebc1b 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -1253,51 +1253,6 @@ def set_verts_and_codes(self, verts, codes): for xy, cds in zip(verts, codes)] self.stale = True - @classmethod - @_api.deprecated("3.7", alternative="fill_between") - def span_where(cls, x, ymin, ymax, where, **kwargs): - """ - Return a `.BrokenBarHCollection` that plots horizontal bars from - over the regions in *x* where *where* is True. The bars range - on the y-axis from *ymin* to *ymax* - - *kwargs* are passed on to the collection. - """ - xranges = [] - for ind0, ind1 in cbook.contiguous_regions(where): - xslice = x[ind0:ind1] - if not len(xslice): - continue - xranges.append((xslice[0], xslice[-1] - xslice[0])) - return BrokenBarHCollection(xranges, [ymin, ymax - ymin], **kwargs) - - -@_api.deprecated("3.7") -class BrokenBarHCollection(PolyCollection): - """ - A collection of horizontal bars spanning *yrange* with a sequence of - *xranges*. - """ - def __init__(self, xranges, yrange, **kwargs): - """ - Parameters - ---------- - xranges : list of (float, float) - The sequence of (left-edge-position, width) pairs for each bar. - yrange : (float, float) - The (lower-edge, height) common to all bars. - **kwargs - Forwarded to `.Collection`. - """ - ymin, ywidth = yrange - ymax = ymin + ywidth - verts = [[(xmin, ymin), - (xmin, ymax), - (xmin + xwidth, ymax), - (xmin + xwidth, ymin), - (xmin, ymin)] for xmin, xwidth in xranges] - super().__init__(verts, **kwargs) - class RegularPolyCollection(_CollectionWithSizes): """A collection of n-sided regular polygons.""" diff --git a/lib/matplotlib/collections.pyi b/lib/matplotlib/collections.pyi index 01682a55b374..7162c4687dfa 100644 --- a/lib/matplotlib/collections.pyi +++ b/lib/matplotlib/collections.pyi @@ -106,18 +106,6 @@ class PolyCollection(_CollectionWithSizes): self, verts: Sequence[ArrayLike | Path], codes: Sequence[int] ) -> None: ... -class BrokenBarHCollection(PolyCollection): - def __init__( - self, - xranges: Iterable[tuple[float, float]], - yrange: tuple[float, float], - **kwargs - ) -> None: ... - @classmethod - def span_where( - cls, x: ArrayLike, ymin: float, ymax: float, where: ArrayLike, **kwargs - ) -> BrokenBarHCollection: ... - class RegularPolyCollection(_CollectionWithSizes): def __init__( self, numsides: int, *, rotation: float = ..., sizes: ArrayLike = ..., **kwargs diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 74a73725d5d9..d5c100089993 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -99,7 +99,6 @@ from matplotlib.collections import ( Collection, LineCollection, - BrokenBarHCollection, PolyCollection, PathCollection, EventCollection, @@ -2873,7 +2872,7 @@ def broken_barh( *, data=None, **kwargs, -) -> BrokenBarHCollection: +) -> PolyCollection: return gca().broken_barh( xranges, yrange, **({"data": data} if data is not None else {}), **kwargs ) From 93cab2e642521f86f10b6d65824776a1f623ec72 Mon Sep 17 00:00:00 2001 From: Haoying Zhang <38875181+stevezhang1999@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:43:26 -0400 Subject: [PATCH 18/19] DOC: Clarify description and add examples in colors.Normalize (#26915) Co-authored-by: Zihao Yang zihaoyng@gmail.com --- lib/matplotlib/colors.py | 64 ++++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 904b6ecfa04b..584adba8eace 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1215,8 +1215,24 @@ def reversed(self, name=None): class Normalize: """ - A class which, when called, linearly normalizes data into the - ``[0.0, 1.0]`` interval. + A class which, when called, maps values within the interval + ``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of + values outside ``[vmin, vmax]`` depends on *clip*. + + Examples + -------- + :: + + x = [-2, -1, 0, 1, 2] + + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=False) + norm(x) # [-0.5, 0., 0.5, 1., 1.5] + norm = mpl.colors.Normalize(vmin=-1, vmax=1, clip=True) + norm(x) # [0., 0., 0.5, 1., 1.] + + See Also + -------- + :ref:`colormapnorms` """ def __init__(self, vmin=None, vmax=None, clip=False): @@ -1224,27 +1240,30 @@ def __init__(self, vmin=None, vmax=None, clip=False): Parameters ---------- vmin, vmax : float or None - If *vmin* and/or *vmax* is not given, they are initialized from the - minimum and maximum value, respectively, of the first input - processed; i.e., ``__call__(A)`` calls ``autoscale_None(A)``. + Values within the range ``[vmin, vmax]`` from the input data will be + linearly mapped to ``[0, 1]``. If either *vmin* or *vmax* is not + provided, they default to the minimum and maximum values of the input, + respectively. + clip : bool, default: False Determines the behavior for mapping values outside the range ``[vmin, vmax]``. - If clipping is off, values outside the range ``[vmin, vmax]`` are also - transformed linearly, resulting in values outside ``[0, 1]``. For a - standard use with colormaps, this behavior is desired because colormaps - mark these outside values with specific colors for *over* or *under*. + If *clip* is ``False``, values outside ``[vmin, vmax]`` are also transformed + linearly, leading to results outside ``[0, 1]``. For a standard use with + colormaps, this behavior is desired because colormaps mark these outside + values with specific colors for over or under. - If ``True`` values falling outside the range ``[vmin, vmax]``, - are mapped to 0 or 1, whichever is closer. This makes these values + If *clip* is ``True``, values outside ``[vmin, vmax]`` are set to 0 or 1, + depending on which boundary they're closer to. This makes these values indistinguishable from regular boundary values and can lead to misinterpretation of the data. + Notes ----- - Returns 0 if ``vmin == vmax``. + If ``vmin == vmax``, input data will be mapped to 0. """ self._vmin = _sanitize_extrema(vmin) self._vmax = _sanitize_extrema(vmax) @@ -1298,6 +1317,11 @@ def process_value(value): *value* can be a scalar or sequence. + Parameters + ---------- + value + Data to normalize. + Returns ------- result : masked array @@ -1328,8 +1352,7 @@ def process_value(value): def __call__(self, value, clip=None): """ - Normalize *value* data in the ``[vmin, vmax]`` interval into the - ``[0.0, 1.0]`` interval and return it. + Normalize the data and return the normalized data. Parameters ---------- @@ -1375,6 +1398,15 @@ def __call__(self, value, clip=None): return result def inverse(self, value): + """ + Maps the normalized value (i.e., index in the colormap) back to image + data value. + + Parameters + ---------- + value + Normalized value. + """ if not self.scaled(): raise ValueError("Not invertible until both vmin and vmax are set") (vmin,), _ = self.process_value(self.vmin) @@ -1396,7 +1428,7 @@ def autoscale(self, A): self._changed() def autoscale_None(self, A): - """If vmin or vmax are not set, use the min/max of *A* to set them.""" + """If *vmin* or *vmax* are not set, use the min/max of *A* to set them.""" A = np.asanyarray(A) if isinstance(A, np.ma.MaskedArray): @@ -1410,7 +1442,7 @@ def autoscale_None(self, A): self.vmax = A.max() def scaled(self): - """Return whether vmin and vmax are set.""" + """Return whether *vmin* and *vmax* are both set.""" return self.vmin is not None and self.vmax is not None From 890d4b38a3387a3616069ed4f5f159d7f6b70e15 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 28 Sep 2023 10:17:35 +0200 Subject: [PATCH 19/19] Inline Cursor._update into its sole caller. ... to make the Cursor redraw logic easier to follow. Also remove the unused return value from _update. --- lib/matplotlib/widgets.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 9fde066afb5c..a1974a43fe12 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -1886,23 +1886,19 @@ def onmove(self, event): if not self.ax.contains(event)[0]: self.linev.set_visible(False) self.lineh.set_visible(False) - if self.needclear: self.canvas.draw() self.needclear = False return self.needclear = True - xdata, ydata = self._get_data_coords(event) self.linev.set_xdata((xdata, xdata)) self.linev.set_visible(self.visible and self.vertOn) self.lineh.set_ydata((ydata, ydata)) self.lineh.set_visible(self.visible and self.horizOn) - - if self.visible and (self.vertOn or self.horizOn): - self._update() - - def _update(self): + if not (self.visible and (self.vertOn or self.horizOn)): + return + # Redraw. if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) @@ -1911,7 +1907,6 @@ def _update(self): self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() - return False class MultiCursor(Widget): @@ -2026,10 +2021,9 @@ def onmove(self, event): for line in self.hlines: line.set_ydata((ydata, ydata)) line.set_visible(self.visible and self.horizOn) - if self.visible and (self.vertOn or self.horizOn): - self._update() - - def _update(self): + if not (self.visible and (self.vertOn or self.horizOn)): + return + # Redraw. if self.useblit: for canvas, info in self._canvas_infos.items(): if info["background"]: