Skip to content

Commit b2092d0

Browse files
iangowhas2k1
authored andcommitted
Added guide_axis_theta() (initial pass).
1 parent c493b3d commit b2092d0

6 files changed

Lines changed: 99 additions & 2 deletions

File tree

plotnine/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
save_as_pdf_pages,
9191
)
9292
from .guides import (
93+
guide_axis_theta,
9394
guide_colorbar,
9495
guide_colourbar,
9596
guide_legend,
@@ -349,6 +350,7 @@
349350
"ggplot",
350351
"ggsave",
351352
"ggtitle",
353+
"guide_axis_theta",
352354
"guide_colorbar",
353355
"guide_colourbar",
354356
"guide_legend",

plotnine/coords/coord_radial.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,21 @@ def setup_ax(
334334
super().setup_ax(ax, panel_params, theme)
335335
if self.theta_labels or self.end is not None:
336336
ax.tick_params(axis="x", pad=self.theta_label_pad)
337+
if (angle := self._theta_guide_angle(theme)) is not None:
338+
ax.tick_params(axis="x", labelrotation=angle)
339+
for label in ax.get_xticklabels():
340+
label.set_rotation(angle)
341+
label.set_rotation_mode("anchor")
337342
# Allow geom_text labels to extend past the polar axes bounding box
338343
# (e.g. spoke labels placed just beyond the outermost bar tip).
339344
for text in ax.texts:
340345
text.set_clip_on(False)
346+
347+
@staticmethod
348+
def _theta_guide_angle(theme: theme) -> float | None:
349+
"""
350+
Return the angle from guides(theta=guide_axis_theta(...)).
351+
"""
352+
guide = getattr(getattr(theme, "owner", None), "guides", None)
353+
guide = getattr(guide, "theta", None)
354+
return getattr(guide, "angle", None)

plotnine/guides/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
from .guide_axis_theta import guide_axis_theta
12
from .guide_colorbar import guide_colorbar, guide_colourbar
23
from .guide_legend import guide_legend
34
from .guides import guides
45

5-
__all__ = ("guide_colorbar", "guide_colourbar", "guide_legend", "guides")
6+
__all__ = (
7+
"guide_axis_theta",
8+
"guide_colorbar",
9+
"guide_colourbar",
10+
"guide_legend",
11+
"guides",
12+
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
5+
from ..themes.theme import theme as Theme
6+
7+
8+
@dataclass
9+
class guide_axis_theta:
10+
"""
11+
Theta-axis guide for radial coordinates.
12+
13+
This guide is consumed by ``coord_radial`` when it draws theta-axis
14+
tick labels. It is not drawn as a legend or colorbar.
15+
"""
16+
17+
title: str | None = None
18+
"""Title of the guide. Currently unused."""
19+
20+
theme: Theme = field(default_factory=Theme)
21+
"""A theme to style the guide. Currently unused."""
22+
23+
angle: float | None = None
24+
"""
25+
Angle in degrees at which to draw theta-axis tick labels.
26+
27+
If ``None``, Matplotlib's default theta tick label rotation is used.
28+
"""
29+
30+
minor_ticks: bool | None = None
31+
"""Whether to draw minor ticks. Currently unused."""
32+
33+
cap: bool | None = None
34+
"""Whether to cap the axis line. Currently unused."""
35+
36+
order: int = 0
37+
"""Order of this guide among multiple guides. Currently unused."""
38+
39+
position: str | None = None
40+
"""Guide position. Currently unused."""

plotnine/guides/guides.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
from matplotlib.figure import Figure
2828
from matplotlib.offsetbox import OffsetBox, PackerBase
2929

30-
from plotnine import ggplot, guide_colorbar, guide_legend, theme
30+
from plotnine import (
31+
ggplot,
32+
guide_axis_theta,
33+
guide_colorbar,
34+
guide_legend,
35+
theme,
36+
)
3137
from plotnine._mpl.offsetbox import FlexibleAnchoredOffsetbox
3238
from plotnine.composition import Compose
3339
from plotnine.iapi import labels_view
@@ -47,6 +53,7 @@
4753
guide_legend | guide_colorbar | Literal["legend", "colorbar"]
4854
)
4955
LegendOnly: TypeAlias = guide_legend | Literal["legend"]
56+
ThetaGuide: TypeAlias = guide_axis_theta
5057

5158
class LegendOwner(Protocol):
5259
"""
@@ -116,6 +123,9 @@ class guides:
116123
colour: Optional[LegendOnly | NoGuide] = None
117124
"""Guide for colour scale."""
118125

126+
theta: Optional[ThetaGuide | NoGuide] = None
127+
"""Guide for theta axis labels in radial coordinates."""
128+
119129
def __post_init__(self):
120130
self.plot: ggplot
121131
self.plot_scales: Scales

tests/test_coord_polar.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
geom_point,
1313
geom_text,
1414
ggplot,
15+
guide_axis_theta,
16+
guides,
1517
theme,
1618
)
1719
from plotnine.coords.coord_cartesian import coord_cartesian
@@ -307,3 +309,25 @@ def test_coord_radial_setup_ax_sets_pad_and_unclips_text():
307309
assert not ax.texts[0].get_clip_on()
308310
finally:
309311
plt.close(fig)
312+
313+
314+
def test_coord_radial_uses_guide_axis_theta_angle():
315+
data = pd.DataFrame({"x": [1, 2, 3], "y": [1, 2, 3]})
316+
p = (
317+
ggplot(data, aes("x", "y"))
318+
+ geom_point()
319+
+ coord_radial(theta_labels=True)
320+
+ guides(theta=guide_axis_theta(angle=35))
321+
)
322+
323+
fig = p.draw()
324+
try:
325+
rotations = [
326+
label.get_rotation()
327+
for label in fig.axes[0].get_xticklabels()
328+
if label.get_text()
329+
]
330+
assert rotations
331+
assert rotations == [35] * len(rotations)
332+
finally:
333+
plt.close(fig)

0 commit comments

Comments
 (0)