Repository navigation
Restore pyplot getter semantics for limits and ticks #470
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
01dfed2
18a589a
edfedad
aad8727
02ade64
5d21269
221e761
512f70f
e08c1c9
4b2912a
3b5fda6
265720c
4e1c163
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The pyplot `xlim`, `ylim`, `xticks`, and `yticks` helpers now return values that match the requested getter or setter operation. Limit helpers also accept Matplotlib's `xmin`/`xmax` and `ymin`/`ymax` aliases, and `legend()` returns its live handle. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4236,7 +4236,9 @@ def set(self, **kwargs: Any) -> "Axes": | |
| self._invalidate() | ||
| return self | ||
|
|
||
| def set_xlim(self, left: float | LimitsLike | None = None, right: float | None = None) -> None: | ||
| def set_xlim( | ||
| self, left: float | LimitsLike | None = None, right: float | None = None | ||
| ) -> tuple[float, float]: | ||
| """Set the x view limits. | ||
|
|
||
| Call as ``set_xlim(left, right)`` or ``set_xlim((left, right))``; | ||
|
|
@@ -4246,6 +4248,8 @@ def set_xlim(self, left: float | LimitsLike | None = None, right: float | None = | |
| """ | ||
| if isinstance(left, (tuple, list)): | ||
| left, right = left | ||
| if left is None and right is None: | ||
| return self.get_xlim() | ||
| host = (self._y2_of or self)._shared_ticker_source("x") | ||
| spec = host._scale_specs["x"] | ||
| current_start, current_end = self.get_xlim() | ||
|
|
@@ -4282,6 +4286,7 @@ def set_xlim(self, left: float | LimitsLike | None = None, right: float | None = | |
| host._explicit_domains.add("x") | ||
| host._tick_expanded_domains.discard("x") | ||
| host._invalidate_shared_ticker_axis("x") | ||
| return self.get_xlim() | ||
|
|
||
| def get_xlim(self) -> tuple[float, float]: | ||
| """The current x view limits, in data space and display order.""" | ||
|
|
@@ -4299,14 +4304,18 @@ def get_xlim(self) -> tuple[float, float]: | |
| ) | ||
| return (hi, lo) if host._axis["x"].get("reverse") else (lo, hi) | ||
|
|
||
| def set_ylim(self, bottom: float | LimitsLike | None = None, top: float | None = None) -> None: | ||
| def set_ylim( | ||
| self, bottom: float | LimitsLike | None = None, top: float | None = None | ||
| ) -> tuple[float, float]: | ||
| """Set the y view limits (forms as in `set_xlim`). | ||
|
|
||
| A descending ``(bottom, top)`` pair inverts the axis; on a twin axes | ||
| the limits apply to its own right-hand y-axis. | ||
| """ | ||
| if isinstance(bottom, (tuple, list)): | ||
| bottom, top = bottom | ||
| if bottom is None and top is None: | ||
| return self.get_ylim() | ||
| base = self._y2_of or self | ||
| key = "y2" if self._y2_of is not None else "y" | ||
| host = base._shared_ticker_source(key) | ||
|
|
@@ -4345,6 +4354,7 @@ def set_ylim(self, bottom: float | LimitsLike | None = None, top: float | None = | |
| host._explicit_domains.add(key) | ||
| host._tick_expanded_domains.discard(key) | ||
| host._invalidate_shared_ticker_axis(key) | ||
| return self.get_ylim() | ||
|
|
||
| def get_ylim(self) -> tuple[float, float]: | ||
| """The current y view limits, in data space and display order.""" | ||
|
|
@@ -6737,7 +6747,7 @@ def set_xticks( | |
| *, | ||
| rotation: float | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| ) -> list[_TickLabel]: | ||
| """Place the x ticks at the given positions, optionally relabeled. | ||
|
|
||
| ``labels`` must match ``ticks`` in length and displaces any user | ||
|
|
@@ -6746,7 +6756,7 @@ def set_xticks( | |
| Positions are in data space, so nonlinear scales transform them. | ||
| """ | ||
| if kwargs.pop("minor", False): | ||
| return | ||
| return [] | ||
| host = (self._y2_of or self)._shared_ticker_source("x") | ||
| props = host._axis["x"] | ||
| if ticks is not None: | ||
|
|
@@ -6774,6 +6784,7 @@ def set_xticks( | |
| if rotation is not None: | ||
| self._axis_props("x")["tick_label_angle"] = float(rotation) | ||
| host._invalidate_shared_ticker_axis("x") | ||
| return self.get_xticklabels() | ||
|
|
||
| def set_yticks( | ||
| self, | ||
|
|
@@ -6782,10 +6793,10 @@ def set_yticks( | |
| *, | ||
| rotation: float | None = None, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| ) -> list[_TickLabel]: | ||
| """Place the y ticks at the given positions (see `set_xticks`).""" | ||
| if kwargs.pop("minor", False): | ||
| return | ||
| return [] | ||
| base = self._y2_of or self | ||
| key = "y2" if self._y2_of is not None else "y" | ||
| host = base._shared_ticker_source(key) | ||
|
|
@@ -6813,6 +6824,7 @@ def set_yticks( | |
| if rotation is not None: | ||
| self._axis_props("y")["tick_label_angle"] = float(rotation) | ||
| host._invalidate_shared_ticker_axis(key) | ||
| return self.get_yticklabels() | ||
|
|
||
| def _expand_domain_to_ticks(self, axis: str) -> None: | ||
| """Apply Matplotlib's mandatory view expansion for explicit ticks.""" | ||
|
|
@@ -6949,11 +6961,66 @@ def _computed_ticks(self, axis: str, minor: bool) -> np.ndarray: | |
| host = base._shared_ticker_source(key) | ||
| props = host._axis[key] | ||
| if minor: | ||
| key = "y2" if axis == "y" and self._y2_of is not None else axis | ||
| # Materialize minor positions against a throwaway props mapping. | ||
| # `_apply_tickers` normally runs while building a chart, where it | ||
| # fills a copied wire payload; getters must return the same | ||
| # locations without requiring a render or mutating axis state. | ||
| minor_props = dict(props) | ||
| figure = base.figure | ||
| canvas_width, canvas_height = rc_figsize_px(figure._figsize, figure._dpi) | ||
|
|
||
| def chart_size(axes: Axes) -> tuple[int, int]: | ||
| if axes._figure_rect is not None: | ||
| return ( | ||
| max(1, round(canvas_width * axes._figure_rect[2])), | ||
| max(1, round(canvas_height * axes._figure_rect[3])), | ||
| ) | ||
| if axes._subplot_index is not None and hasattr(figure, "_grid_cell_sizes"): | ||
| widths, heights = figure._grid_cell_sizes() | ||
| index = axes._subplot_index | ||
| return widths[index % figure._ncols], heights[index // figure._ncols] | ||
| if axes in figure._axes: | ||
| return canvas_width, canvas_height | ||
| for parent in figure._axes: | ||
| for inset, (_left, _bottom, inset_width, inset_height) in parent._insets: | ||
| if inset is axes: | ||
| parent_width, parent_height = chart_size(parent) | ||
| return ( | ||
| max(1, round(parent_width * inset_width)), | ||
| max(1, round(parent_height * inset_height)), | ||
| ) | ||
| return canvas_width, canvas_height | ||
|
|
||
| width, height = chart_size(base) | ||
| # Rendering recomputes this hint from the live chart size on every | ||
| # build, so a locator's cached hint can be stale after resizing. | ||
| count_key = "y" if key == "y2" else key | ||
| nbins_hint = base._auto_tick_counts(base._axis["x"], width, height)[count_key] | ||
| if ( | ||
| key in {"x", "y"} | ||
| and rcParams["axes.autolimit_mode"] == "round_numbers" | ||
| and not (base._aspect_equal and base._aspect_bounds is not None) | ||
| and not base._tight | ||
| ): | ||
| # Drawing applies the locator's view_limits to automatic data | ||
| # domains before resolving ticks. Mirror that snapshot here so | ||
| # minor getters include the same edge ticks without rendering. | ||
| x_props = { | ||
| name: value for name, value in base._axis["x"].items() if value is not None | ||
| } | ||
| y_props = { | ||
| name: value for name, value in base._axis["y"].items() if value is not None | ||
| } | ||
| base._inherit_shared_axis_state("x", x_props) | ||
| base._inherit_shared_axis_state("y", y_props) | ||
| counts = base._auto_tick_counts(x_props, width, height) | ||
| base._apply_round_number_domains(x_props, y_props, counts) | ||
| minor_props.update(x_props if key == "x" else y_props) | ||
| base._apply_tickers(key, minor_props, nbins_hint) | ||
|
Comment on lines
+6998
to
+7019
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With Resolve the same view limits as drawing before calling |
||
| return np.asarray( | ||
| _scale_values( | ||
| props.get("minor_tick_values", []), | ||
| (self._y2_of or self)._scale_specs[key], | ||
| minor_props.get("minor_tick_values", []), | ||
| base._scale_specs[key], | ||
| inverse=True, | ||
| ), | ||
| dtype=float, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.