gsplot 0.4.0__tar.gz → 0.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {gsplot-0.4.0 → gsplot-0.4.2}/PKG-INFO +1 -1
  2. {gsplot-0.4.0 → gsplot-0.4.2}/pyproject.toml +1 -1
  3. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/__init__.py +6 -2
  4. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/__init__.pyi +6 -2
  5. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy_api.py +56 -20
  6. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/root.py +1 -0
  7. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/root_api.py +98 -85
  8. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/plans.py +4 -3
  9. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/targets.py +4 -3
  10. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/types.py +100 -15
  11. gsplot-0.4.2/src/gsplot/_figure/fit.py +365 -0
  12. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_figure/layout.py +42 -16
  13. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_figure/output.py +34 -11
  14. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_plot/basic.py +2 -2
  15. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_plot/colored.py +34 -4
  16. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_plot/series.py +4 -4
  17. gsplot-0.4.2/src/gsplot/_style/axes.py +1404 -0
  18. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_style/legends.py +452 -100
  19. gsplot-0.4.2/src/gsplot/_style/panels.py +673 -0
  20. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_style/paper.py +3 -1
  21. gsplot-0.4.0/src/gsplot/_style/axes.py +0 -793
  22. gsplot-0.4.0/src/gsplot/_style/panels.py +0 -373
  23. {gsplot-0.4.0 → gsplot-0.4.2}/LICENSE +0 -0
  24. {gsplot-0.4.0 → gsplot-0.4.2}/README.md +0 -0
  25. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/__init__.py +0 -0
  26. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/config.py +0 -0
  27. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/__init__.py +0 -0
  28. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/base/__init__.py +0 -0
  29. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/base/base.py +0 -0
  30. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/base/base_alias_validator.py +0 -0
  31. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/color/__init__.py +0 -0
  32. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/color/colormap.py +0 -0
  33. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/config/__init__.py +0 -0
  34. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/config/config.py +0 -0
  35. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/data/__init__.py +0 -0
  36. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/data/load_file.py +0 -0
  37. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/__init__.py +0 -0
  38. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/axes.py +0 -0
  39. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/axes_base.py +0 -0
  40. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/axes_inset.py +0 -0
  41. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/axes_range_base.py +0 -0
  42. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/figure_tools.py +0 -0
  43. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/show.py +0 -0
  44. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/figure/store.py +0 -0
  45. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/hello_world/__init__.py +0 -0
  46. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/hello_world/hello_world.py +0 -0
  47. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/logger.py +0 -0
  48. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/path/__init__.py +0 -0
  49. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/path/path.py +0 -0
  50. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/__init__.py +0 -0
  51. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/line.py +0 -0
  52. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/line_base.py +0 -0
  53. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/line_colormap_base.py +0 -0
  54. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/line_colormap_dashed.py +0 -0
  55. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/line_colormap_solid.py +0 -0
  56. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/scatter.py +0 -0
  57. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/plot/scatter_colormap.py +0 -0
  58. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/__init__.py +0 -0
  59. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/graph.py +0 -0
  60. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/label.py +0 -0
  61. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/legend.py +0 -0
  62. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/legend_colormap.py +0 -0
  63. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/ticks.py +0 -0
  64. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/legacy/style/title.py +0 -0
  65. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/root_legacy.py +0 -0
  66. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_compat/shim.py +0 -0
  67. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_config/__init__.py +0 -0
  68. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_config/loader.py +0 -0
  69. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_config/model.py +0 -0
  70. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_config/schema.py +0 -0
  71. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/__init__.py +0 -0
  72. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/errors.py +0 -0
  73. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/numerics.py +0 -0
  74. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/options.py +0 -0
  75. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_core/validation.py +0 -0
  76. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_figure/__init__.py +0 -0
  77. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_figure/backend.py +0 -0
  78. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_figure/inset.py +0 -0
  79. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_io/__init__.py +0 -0
  80. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_io/arrays.py +0 -0
  81. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_io/build.py +0 -0
  82. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_io/metadata.py +0 -0
  83. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_io/paths.py +0 -0
  84. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_plot/__init__.py +0 -0
  85. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_plot/colormap.py +0 -0
  86. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_style/__init__.py +0 -0
  87. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/_style/themes.py +0 -0
  88. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/base/__init__.py +0 -0
  89. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/base/base.py +0 -0
  90. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/base/base_alias_validator.py +0 -0
  91. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/color/__init__.py +0 -0
  92. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/color/colormap.py +0 -0
  93. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/config/__init__.py +0 -0
  94. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/config/config.py +0 -0
  95. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/data/__init__.py +0 -0
  96. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/data/load_file.py +0 -0
  97. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/__init__.py +0 -0
  98. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/axes.py +0 -0
  99. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/axes_base.py +0 -0
  100. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/axes_inset.py +0 -0
  101. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/axes_range_base.py +0 -0
  102. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/figure_tools.py +0 -0
  103. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/show.py +0 -0
  104. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/figure/store.py +0 -0
  105. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/hello_world/__init__.py +0 -0
  106. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/hello_world/hello_world.py +0 -0
  107. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/logger.py +0 -0
  108. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/path/__init__.py +0 -0
  109. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/path/path.py +0 -0
  110. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/__init__.py +0 -0
  111. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/line.py +0 -0
  112. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/line_base.py +0 -0
  113. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/line_colormap_base.py +0 -0
  114. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/line_colormap_dashed.py +0 -0
  115. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/line_colormap_solid.py +0 -0
  116. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/scatter.py +0 -0
  117. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/plot/scatter_colormap.py +0 -0
  118. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/py.typed +0 -0
  119. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/__init__.py +0 -0
  120. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/graph.py +0 -0
  121. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/label.py +0 -0
  122. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/legend.py +0 -0
  123. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/legend_colormap.py +0 -0
  124. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/ticks.py +0 -0
  125. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/style/title.py +0 -0
  126. {gsplot-0.4.0 → gsplot-0.4.2}/src/gsplot/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gsplot
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Concise publication-quality scientific plotting built on Matplotlib
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gsplot"
3
- version = "0.4.0"
3
+ version = "0.4.2"
4
4
  description = "Concise publication-quality scientific plotting built on Matplotlib"
5
5
  authors = [
6
6
  { name = "Giordano Mattoni" },
@@ -7,7 +7,7 @@ configuration, logging, metadata-file, or backend initialization.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from typing import TYPE_CHECKING, Any
10
+ from typing import TYPE_CHECKING, Any, Callable
11
11
 
12
12
  from ._compat.root import canonical_names as _canonical_names
13
13
  from ._compat.root import legacy_names as _legacy_names
@@ -20,6 +20,8 @@ if TYPE_CHECKING:
20
20
  # Runtime lookup stays lazy. Static analyzers see the canonical source
21
21
  # objects instead of an ``Any``-typed dynamic facade, so the shipped
22
22
  # ``py.typed`` marker provides useful signatures to downstream callers.
23
+ from matplotlib.legend import Legend as _Legend
24
+
23
25
  from ._compat.config import load_config
24
26
  from ._config.model import Config
25
27
  from ._core.errors import (
@@ -75,6 +77,7 @@ if TYPE_CHECKING:
75
77
  square,
76
78
  style_axes,
77
79
  suptitle,
80
+ ticks,
78
81
  title,
79
82
  )
80
83
  from ._style.legends import cmap_legend, legend, legend_entries, legends
@@ -114,7 +117,7 @@ if TYPE_CHECKING:
114
117
  legend_handlers: Any
115
118
  legend_reverse: Any
116
119
  legend_get_handlers: Any
117
- legend_colormap: Any
120
+ legend_colormap: Callable[..., _Legend]
118
121
  ticks_off: Any
119
122
  ticks_on: Any
120
123
  ticks_on_axes: Any
@@ -139,6 +142,7 @@ __all__ = [
139
142
  "title",
140
143
  "suptitle",
141
144
  "minor_ticks",
145
+ "ticks",
142
146
  "box_aspect",
143
147
  "panel_labels",
144
148
  "index",
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
3
+ from typing import Any, Callable
4
+
5
+ from matplotlib.legend import Legend as _Legend
4
6
 
5
7
  from ._compat.config import load_config as load_config
6
8
  from ._config.model import Config as Config
@@ -60,6 +62,7 @@ from ._style.axes import minor_ticks as minor_ticks
60
62
  from ._style.axes import square as square
61
63
  from ._style.axes import style_axes as style_axes
62
64
  from ._style.axes import suptitle as suptitle
65
+ from ._style.axes import ticks as ticks
63
66
  from ._style.axes import title as title
64
67
  from ._style.legends import cmap_legend as cmap_legend
65
68
  from ._style.legends import legend as legend
@@ -104,7 +107,7 @@ legend_axes: Any
104
107
  legend_handlers: Any
105
108
  legend_reverse: Any
106
109
  legend_get_handlers: Any
107
- legend_colormap: Any
110
+ legend_colormap: Callable[..., _Legend]
108
111
  ticks_off: Any
109
112
  ticks_on: Any
110
113
  ticks_on_axes: Any
@@ -129,6 +132,7 @@ __all__ = [
129
132
  "title",
130
133
  "suptitle",
131
134
  "minor_ticks",
135
+ "ticks",
132
136
  "box_aspect",
133
137
  "panel_labels",
134
138
  "index",
@@ -20,7 +20,9 @@ from typing import Any, Literal, cast
20
20
  import numpy as np
21
21
  from matplotlib import ticker
22
22
  from matplotlib.axes import Axes
23
+ from matplotlib.axes._base import _AxesBase
23
24
  from matplotlib.figure import Figure
25
+ from matplotlib.legend import Legend
24
26
  from mpl_toolkits.axes_grid1.inset_locator import inset_axes as _mpl_inset_axes
25
27
  from numpy.typing import ArrayLike, NDArray
26
28
 
@@ -41,7 +43,7 @@ from .._style.axes import box_aspect as _box_aspect
41
43
  from .._style.axes import minor_ticks as _minor_ticks
42
44
  from .._style.axes import style_axes as _style_axes
43
45
  from .._style.axes import title as _title
44
- from .._style.legends import cmap_legend as _cmap_legend
46
+ from .._style.legends import _create_cmap_legend, _legacy_colormap_values
45
47
  from .._style.legends import legend as _legend
46
48
  from .._style.legends import legend_entries as _legend_entries
47
49
  from .._style.legends import legends as _legends
@@ -310,16 +312,21 @@ def _legacy_limits(
310
312
  if value is None:
311
313
  return None, "linear", None, None
312
314
  if isinstance(value, (str, bytes)):
315
+ if value in ("", "*"):
316
+ return None, "linear", None, None
313
317
  raise LayoutError("legacy limits must be a finite sequence")
314
318
  values = tuple(value)
315
319
  if len(values) < 2:
316
320
  raise LayoutError("legacy limits must contain two values")
317
- try:
318
- limits = (float(values[0]), float(values[1]))
319
- except (TypeError, ValueError) as exc:
320
- raise LayoutError("legacy limits must contain finite values") from exc
321
- if not np.all(np.isfinite(limits)) or limits[0] == limits[1]:
322
- raise LayoutError("legacy limits must contain finite unequal values")
321
+ if values[0] in (None, "", "*") and values[1] in (None, "", "*"):
322
+ limits = None
323
+ else:
324
+ try:
325
+ limits = (float(cast(Any, values[0])), float(cast(Any, values[1])))
326
+ except (TypeError, ValueError) as exc:
327
+ raise LayoutError("legacy limits must contain finite values") from exc
328
+ if not np.all(np.isfinite(limits)) or limits[0] == limits[1]:
329
+ raise LayoutError("legacy limits must contain finite unequal values")
323
330
  scale = values[2] if len(values) > 2 and isinstance(values[2], str) else "linear"
324
331
  if scale not in {"linear", "log", "symlog", "logit"}:
325
332
  raise LayoutError(f"unsupported legacy scale: {scale!r}")
@@ -820,40 +827,69 @@ def legend_colormap(
820
827
  vmax: float = 1,
821
828
  reverse: bool = False,
822
829
  **props: Any,
823
- ) -> Any:
824
- """Adapt legacy colormap legends to :func:`gsplot.cmap_legend`."""
830
+ ) -> Legend:
831
+ """Create a v0.2-compatible horizontal gradient Legend.
832
+
833
+ Parameters
834
+ ----------
835
+ ax
836
+ Explicit target Axes.
837
+ cmap
838
+ Non-empty registered Matplotlib colormap name.
839
+ label
840
+ Optional gradient label. ``None`` returns an empty Legend.
841
+ num_stripes
842
+ Positive requested stripe count; values above 256 are clamped.
843
+ vmin, vmax
844
+ Finite raw values passed directly to the colormap through
845
+ ``linspace(vmin, vmax, N_effective)``. Equal and descending bounds are
846
+ valid and retain their sampled order.
847
+ reverse
848
+ Reverse the final sampled RGBA sequence from left to right.
849
+ **props
850
+ Finite Matplotlib Legend properties. If omitted, Matplotlib's
851
+ call-time rcParams provide the defaults.
852
+
853
+ Notes
854
+ -----
855
+ This deprecated adapter always replaces an existing Legend because the
856
+ historical function has no ``replace`` keyword. Its rendering helper is
857
+ shared with :func:`gsplot.cmap_legend`, but its raw ``vmin``/``vmax``
858
+ sampling is deliberately not translated to canonical ``norm`` semantics.
859
+ """
825
860
 
826
861
  _warn("legend_colormap", "cmap_legend")
827
- return _cmap_legend(
862
+ colors = _legacy_colormap_values(cmap, num_stripes, vmin, vmax, reverse)
863
+ return _create_cmap_legend(
828
864
  ax,
829
- cmap=cmap,
830
- label=label,
831
- stripes=num_stripes,
832
- norm=(vmin, vmax),
833
- reverse=reverse,
834
- props=props or None,
865
+ colors,
866
+ label,
867
+ replace=True,
868
+ props=None,
869
+ kwargs=props,
870
+ ambient=True,
835
871
  )
836
872
 
837
873
 
838
- def ticks_off(ax: Axes, mode: str = "xy") -> None:
874
+ def ticks_off(ax: Axes | _AxesBase, mode: str = "xy") -> None:
839
875
  """Adapt legacy minor-tick disabling to the canonical selector."""
840
876
 
841
877
  _warn("ticks_off", "minor_ticks")
842
878
  selected = {"xy": "both"}.get(mode, mode)
843
879
  _minor_ticks(
844
- ax,
880
+ cast(Any, ax),
845
881
  False,
846
882
  axis=cast(Literal["x", "y", "both"], selected),
847
883
  )
848
884
 
849
885
 
850
- def ticks_on(ax: Axes, mode: str = "xy") -> None:
886
+ def ticks_on(ax: Axes | _AxesBase, mode: str = "xy") -> None:
851
887
  """Adapt legacy minor-tick enabling to the canonical selector."""
852
888
 
853
889
  _warn("ticks_on", "minor_ticks")
854
890
  selected = {"xy": "both"}.get(mode, mode)
855
891
  _minor_ticks(
856
- ax,
892
+ cast(Any, ax),
857
893
  True,
858
894
  axis=cast(Literal["x", "y", "both"], selected),
859
895
  )
@@ -86,6 +86,7 @@ _CANONICAL_EXPORTS: Final[dict[str, tuple[str, str]]] = {
86
86
  "title": ("gsplot._compat.root_api", "title"),
87
87
  "suptitle": ("gsplot._style.axes", "suptitle"),
88
88
  "minor_ticks": ("gsplot._style.axes", "minor_ticks"),
89
+ "ticks": ("gsplot._style.axes", "ticks"),
89
90
  "box_aspect": ("gsplot._style.axes", "box_aspect"),
90
91
  "panel_labels": ("gsplot._style.panels", "panel_labels"),
91
92
  "index": ("gsplot._style.panels", "index"),
@@ -15,6 +15,7 @@ from typing import Any, get_type_hints
15
15
  from weakref import WeakKeyDictionary
16
16
 
17
17
  from matplotlib.axes import Axes
18
+ from matplotlib.axes._base import _AxesBase
18
19
  from matplotlib.figure import Figure
19
20
  from numpy.typing import ArrayLike
20
21
 
@@ -32,7 +33,7 @@ from .._style.legends import legend as _legend
32
33
  from .._style.paper import PAPER_CYCLE_RGBA as _LEGACY_COLORS
33
34
 
34
35
  _UNSET = object()
35
- _LEGACY_PLOT_COUNTS: WeakKeyDictionary[Axes, int] = WeakKeyDictionary()
36
+ _LEGACY_PLOT_COUNTS: WeakKeyDictionary[Axes | _AxesBase, int] = WeakKeyDictionary()
36
37
 
37
38
  _LEGACY_LEGEND_KEYS = {
38
39
  "handlers",
@@ -389,7 +390,7 @@ def scatter(
389
390
  def _legacy_label_records(value: Any) -> tuple[Any, ...] | None:
390
391
  """Recognize non-empty historical records without reading pyplot state."""
391
392
 
392
- if isinstance(value, (str, bytes, Mapping, Axes)):
393
+ if isinstance(value, (str, bytes, Mapping, Axes, _AxesBase)):
393
394
  return None
394
395
  try:
395
396
  records = tuple(value)
@@ -430,6 +431,14 @@ def label(
430
431
  pad: Any = _UNSET,
431
432
  xpad: Any = _UNSET,
432
433
  ypad: Any = _UNSET,
434
+ margin: Any = _UNSET,
435
+ xmargin: Any = _UNSET,
436
+ ymargin: Any = _UNSET,
437
+ top: Any = _UNSET,
438
+ bottom: Any = _UNSET,
439
+ left: Any = _UNSET,
440
+ right: Any = _UNSET,
441
+ direction: Any = _UNSET,
433
442
  square: Any = _UNSET,
434
443
  index: Any = _UNSET,
435
444
  lab_lims: Any = _UNSET,
@@ -464,6 +473,7 @@ def label(
464
473
  fontdict: Any = _UNSET,
465
474
  labelpad: Any = _UNSET,
466
475
  loc: Any = _UNSET,
476
+ props: Any = _UNSET,
467
477
  ) -> Any:
468
478
  """Dispatch explicit concise labels or historical current-Figure records."""
469
479
 
@@ -479,6 +489,34 @@ def label(
479
489
  records = _legacy_label_records(target)
480
490
  else:
481
491
  records = None
492
+ text_props = {
493
+ name: value
494
+ for name, value in (
495
+ ("alpha", alpha),
496
+ ("color", color),
497
+ ("fontfamily", fontfamily),
498
+ ("fontproperties", fontproperties),
499
+ ("fontsize", fontsize),
500
+ ("fontstretch", fontstretch),
501
+ ("fontstyle", fontstyle),
502
+ ("fontvariant", fontvariant),
503
+ ("fontweight", fontweight),
504
+ ("ha", ha),
505
+ ("horizontalalignment", horizontalalignment),
506
+ ("label", label),
507
+ ("linespacing", linespacing),
508
+ ("math_fontfamily", math_fontfamily),
509
+ ("multialignment", multialignment),
510
+ ("parse_math", parse_math),
511
+ ("rotation", rotation),
512
+ ("rotation_mode", rotation_mode),
513
+ ("va", va),
514
+ ("verticalalignment", verticalalignment),
515
+ ("visible", visible),
516
+ ("zorder", zorder),
517
+ )
518
+ if value is not _UNSET
519
+ }
482
520
  legacy_controls = _provided(
483
521
  {
484
522
  "legacy_xpad_layout": legacy_xpad_layout,
@@ -489,28 +527,6 @@ def label(
489
527
  "tight_layout": tight_layout,
490
528
  "xpad_layout": xpad_layout,
491
529
  "ypad_layout": ypad_layout,
492
- "alpha": alpha,
493
- "color": color,
494
- "fontfamily": fontfamily,
495
- "fontproperties": fontproperties,
496
- "fontsize": fontsize,
497
- "fontstretch": fontstretch,
498
- "fontstyle": fontstyle,
499
- "fontvariant": fontvariant,
500
- "fontweight": fontweight,
501
- "ha": ha,
502
- "horizontalalignment": horizontalalignment,
503
- "label": label,
504
- "linespacing": linespacing,
505
- "math_fontfamily": math_fontfamily,
506
- "multialignment": multialignment,
507
- "parse_math": parse_math,
508
- "rotation": rotation,
509
- "rotation_mode": rotation_mode,
510
- "va": va,
511
- "verticalalignment": verticalalignment,
512
- "visible": visible,
513
- "zorder": zorder,
514
530
  "fontdict": fontdict,
515
531
  "labelpad": labelpad,
516
532
  "loc": loc,
@@ -536,10 +552,22 @@ def label(
536
552
  "pad": pad,
537
553
  "xpad": xpad,
538
554
  "ypad": ypad,
555
+ "margin": margin,
556
+ "xmargin": xmargin,
557
+ "ymargin": ymargin,
558
+ "top": top,
559
+ "bottom": bottom,
560
+ "left": left,
561
+ "right": right,
562
+ "direction": direction,
539
563
  "square": square,
540
564
  "index": index,
541
565
  }
542
566
  )
567
+ if props is not _UNSET:
568
+ canonical["props"] = props
569
+ elif text_props:
570
+ canonical["props"] = text_props
543
571
  return _label(target, **canonical)
544
572
 
545
573
  if records is None:
@@ -559,6 +587,14 @@ def label(
559
587
  "pad": pad,
560
588
  "xpad": xpad,
561
589
  "ypad": ypad,
590
+ "margin": margin,
591
+ "xmargin": xmargin,
592
+ "ymargin": ymargin,
593
+ "top": top,
594
+ "bottom": bottom,
595
+ "left": left,
596
+ "right": right,
597
+ "direction": direction,
562
598
  "square": square,
563
599
  "index": index,
564
600
  }
@@ -677,11 +713,41 @@ def legend(
677
713
  ) -> Any:
678
714
  """Dispatch canonical ``legend`` or finite legacy options."""
679
715
 
680
- legacy = _provided(
716
+ is_legacy = (
717
+ legacy_handles is not _UNSET
718
+ or legacy_labels is not _UNSET
719
+ or handlers is not _UNSET
720
+ or ncol is not _UNSET
721
+ )
722
+ if is_legacy:
723
+ if (legacy_handles is not _UNSET or legacy_labels is not _UNSET) and (
724
+ handles is not None or labels is not None
725
+ ):
726
+ raise OptionError("legend cannot combine positional and canonical entries")
727
+ if handlers is not _UNSET:
728
+ if handler_map is not None:
729
+ raise OptionError("legend cannot combine handlers and handler_map")
730
+ handler_map = handlers
731
+ if ncol is not _UNSET:
732
+ if ncols is not _UNSET:
733
+ raise OptionError(
734
+ "legend: props cannot contain both 'ncol' and 'ncols'"
735
+ )
736
+ ncols = ncol
737
+ if legacy_handles is not _UNSET:
738
+ handles = legacy_handles
739
+ if legacy_labels is not _UNSET:
740
+ labels = legacy_labels
741
+ _warn("legend")
742
+
743
+ direct = _provided(
681
744
  {
682
- "handlers": handlers,
745
+ "loc": loc,
746
+ "frameon": frameon,
747
+ "fancybox": fancybox,
748
+ "labelspacing": labelspacing,
749
+ "handlelength": handlelength,
683
750
  "ncols": ncols,
684
- "ncol": ncol,
685
751
  "fontsize": fontsize,
686
752
  "title": title,
687
753
  "title_fontsize": title_fontsize,
@@ -702,59 +768,6 @@ def legend(
702
768
  "labelcolor": labelcolor,
703
769
  }
704
770
  )
705
- if legacy_handles is not _UNSET:
706
- legacy["_legacy_handles"] = legacy_handles
707
- if legacy_labels is not _UNSET:
708
- legacy["_legacy_labels"] = legacy_labels
709
- if (legacy_handles is not _UNSET or legacy_labels is not _UNSET) and (
710
- handles is not None or labels is not None
711
- ):
712
- raise OptionError("legend cannot combine positional and canonical entries")
713
- if legacy and props is not None:
714
- raise OptionError(
715
- "gsplot.legend cannot combine canonical props with legacy options"
716
- )
717
- direct = _provided(
718
- {
719
- "loc": loc,
720
- "frameon": frameon,
721
- "fancybox": fancybox,
722
- "labelspacing": labelspacing,
723
- "handlelength": handlelength,
724
- }
725
- )
726
- if not legacy:
727
- return _legend(
728
- target,
729
- handles=handles,
730
- labels=labels,
731
- handler_map=handler_map,
732
- reverse=reverse,
733
- replace=replace,
734
- props=props,
735
- **direct,
736
- )
737
- positional_handles = legacy.pop("_legacy_handles", _UNSET)
738
- positional_labels = legacy.pop("_legacy_labels", _UNSET)
739
- if handlers is not _UNSET:
740
- if handler_map is not None:
741
- raise OptionError("legend cannot combine handlers and handler_map")
742
- handler_map = handlers
743
- legacy.pop("handlers", None)
744
- translated = _translate_props(
745
- "legend",
746
- legacy,
747
- {"ncol": "ncols"},
748
- )
749
- if positional_handles is not _UNSET:
750
- if handles is not None:
751
- raise OptionError("legend received handles twice")
752
- handles = positional_handles
753
- if positional_labels is not _UNSET:
754
- if labels is not None:
755
- raise OptionError("legend received labels twice")
756
- labels = positional_labels
757
- _warn("legend")
758
771
  return _legend(
759
772
  target,
760
773
  handles=handles,
@@ -762,7 +775,7 @@ def legend(
762
775
  handler_map=handler_map,
763
776
  reverse=reverse,
764
777
  replace=replace,
765
- props=translated,
778
+ props=props,
766
779
  **direct,
767
780
  )
768
781
 
@@ -834,10 +847,10 @@ def title(
834
847
  "y": y,
835
848
  }
836
849
  )
837
- if isinstance(ax, Axes):
838
- if text is _UNSET or title is not _UNSET or legacy:
839
- raise OptionError("canonical title requires text and props")
840
- return _title(ax, text, props=props)
850
+ if isinstance(ax, (Axes, _AxesBase)):
851
+ if text is _UNSET or title is not _UNSET:
852
+ raise OptionError("canonical title requires an explicit text value")
853
+ return _title(ax, text, props=props, **legacy)
841
854
  if text is not _UNSET:
842
855
  raise TypeError("legacy Figure title accepts one text value")
843
856
  if title is not _UNSET:
@@ -7,6 +7,7 @@ from dataclasses import dataclass
7
7
  from typing import Any, Generic, Literal, TypeVar
8
8
 
9
9
  from matplotlib.axes import Axes
10
+ from matplotlib.axes._base import _AxesBase
10
11
  from matplotlib.figure import Figure
11
12
 
12
13
  from .errors import PlotError
@@ -24,7 +25,7 @@ def _operation_name(value: str) -> str:
24
25
  return value
25
26
 
26
27
 
27
- def _axis_root_figure(axis: Axes) -> Figure | None:
28
+ def _axis_root_figure(axis: Any) -> Figure | None:
28
29
  """Return an Axes root Figure without requiring ``root=True`` support."""
29
30
 
30
31
  owner: Any = axis.get_figure()
@@ -47,7 +48,7 @@ class TargetPlan:
47
48
 
48
49
  operation: str
49
50
  figure: Figure
50
- axes: tuple[Axes, ...]
51
+ axes: tuple[Axes | _AxesBase, ...]
51
52
  keys: tuple[object, ...]
52
53
  kind: TargetKind
53
54
 
@@ -61,7 +62,7 @@ class TargetPlan:
61
62
  raise PlotError(f"{self.operation}: target plan is empty or incomplete")
62
63
  if self.kind not in {"single", "sequence", "array", "mapping"}:
63
64
  raise PlotError(f"{self.operation}: target kind is invalid")
64
- if any(not isinstance(axis, Axes) for axis in self.axes):
65
+ if any(not isinstance(axis, (Axes, _AxesBase)) for axis in self.axes):
65
66
  raise PlotError(f"{self.operation}: target contains a non-Axes value")
66
67
  if len({id(axis) for axis in self.axes}) != len(self.axes):
67
68
  raise PlotError(f"{self.operation}: target contains a duplicate Axes")
@@ -7,6 +7,7 @@ from typing import Any, TypeVar
7
7
 
8
8
  import numpy as np
9
9
  from matplotlib.axes import Axes
10
+ from matplotlib.axes._base import _AxesBase
10
11
  from matplotlib.figure import Figure
11
12
 
12
13
  from .errors import PlotError
@@ -16,7 +17,7 @@ from .types import AxesTarget
16
17
  T = TypeVar("T")
17
18
 
18
19
 
19
- def _root_figure(axis: Axes, operation: str) -> Figure:
20
+ def _root_figure(axis: Axes | _AxesBase, operation: str) -> Figure:
20
21
  """Resolve an Axes root Figure without version-specific root arguments."""
21
22
 
22
23
  owner = _axis_root_figure(axis)
@@ -30,7 +31,7 @@ def _snapshot_target(
30
31
  ) -> tuple[TargetKind, tuple[object, ...], tuple[Any, ...]]:
31
32
  """Snapshot one supported target shape without retaining its container."""
32
33
 
33
- if isinstance(target, Axes):
34
+ if isinstance(target, (Axes, _AxesBase)):
34
35
  return "single", (target,), (target,)
35
36
  if isinstance(target, Mapping):
36
37
  items = tuple(target.items())
@@ -67,7 +68,7 @@ def normalize_axes(target: AxesTarget, *, operation: str) -> TargetPlan:
67
68
  kind, keys, values = _snapshot_target(target, operation)
68
69
  if not values:
69
70
  raise PlotError(f"{operation}: target must contain at least one Axes")
70
- if any(not isinstance(value, Axes) for value in values):
71
+ if any(not isinstance(value, (Axes, _AxesBase)) for value in values):
71
72
  raise PlotError(f"{operation}: target contains a non-Axes value")
72
73
  axes = tuple(values)
73
74
  if len({id(axis) for axis in axes}) != len(axes):