Skip to content

Commit

Permalink
Fix setting text artists to blank
Browse files Browse the repository at this point in the history
fixes #764
  • Loading branch information
has2k1 committed Apr 3, 2024
1 parent 4e6c97c commit f4c577a
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 150 deletions.
8 changes: 8 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
title: Changelog
---

## v0.13.4
(not-yet-released)

### Bug Fixes

- Fixed regression in v0.13.3 where setting some text elements `element_blank` led to an
error. {{< issue 764 >}}

## v0.13.3
(2024-03-27)
[![](https://zenodo.org/badge/DOI/10.5281/zenodo.10887259.svg)](https://doi.org/10.5281/zenodo.10887259)
Expand Down
6 changes: 4 additions & 2 deletions plotnine/ggplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,14 @@ def draw(self, show: bool = False) -> Figure:

# setup
self.figure, self.axs = self.facet.setup(self)
self.guides._setup(self)
self.theme.setup(self)

# Drawing
self._draw_layers()
self._draw_panel_borders()
self._draw_breaks_and_labels()
self.guides.draw(self)
self.guides.draw()
self._draw_figure_texts()
self._draw_watermarks()

Expand Down Expand Up @@ -314,12 +315,13 @@ def _draw_using_figure(self, figure: Figure, axs: list[Axes]) -> ggplot:

# setup
self.figure, self.axs = self.facet.setup(self)
self.guides._setup(self)
self.theme.setup(self)

# drawing
self._draw_layers()
self._draw_breaks_and_labels()
self.guides.draw(self)
self.guides.draw()

# artist theming
self.theme.apply()
Expand Down
16 changes: 5 additions & 11 deletions plotnine/guides/guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def setup(self, guides: guides):
# guide theme has priority and its targets are tracked
# independently.
self.theme = guides.plot.theme + self.theme
self.theme.setup(guides.plot)
self.plot_layers = guides.plot.layers
self.plot_mapping = guides.plot.mapping
self.elements = self._elements_cls(self.theme, self)
Expand Down Expand Up @@ -189,11 +190,8 @@ def title(self):
ha = self.theme.getp(("legend_title", "ha"))
va = self.theme.getp(("legend_title", "va"), "center")
_margin = self.theme.getp(("legend_title", "margin"))
if _margin is not None:
_loc = get_opposite_side(self.title_position)[0]
margin = _margin.get_as(_loc, "pt")
else:
margin = 0
_loc = get_opposite_side(self.title_position)[0]
margin = _margin.get_as(_loc, "pt")
top_or_bottom = self.title_position in ("top", "bottom")
is_blank = self.theme.T.is_blank("legend_title")

Expand All @@ -219,12 +217,8 @@ def text_position(self) -> SidePosition:
@cached_property
def _text_margin(self) -> float:
_margin = self.theme.getp((f"legend_text_{self.guide_kind}", "margin"))
if _margin is not None:
_loc = get_opposite_side(self.text_position)
margin = _margin.get_as(_loc[0], "pt")
else:
margin = 0
return margin
_loc = get_opposite_side(self.text_position)
return _margin.get_as(_loc[0], "pt")

@cached_property
def title_position(self) -> SidePosition:
Expand Down
2 changes: 1 addition & 1 deletion plotnine/guides/guide_colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ def add_labels(
labels: Sequence[str],
ys: Sequence[float],
elements: GuideElementsColorbar,
) -> Sequence[Text]:
) -> list[Text]:
"""
Return Texts added to the auxbox
"""
Expand Down
3 changes: 0 additions & 3 deletions plotnine/guides/guide_legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from matplotlib.artist import Artist
from matplotlib.offsetbox import PackerBase

from plotnine import theme
from plotnine.geoms.geom import geom
from plotnine.layer import layer
from plotnine.typing import SidePosition, TupleFloat2, TupleInt2
Expand Down Expand Up @@ -236,8 +235,6 @@ def draw(self):

from .._mpl.offsetbox import ColoredDrawingArea

self.theme = cast("theme", self.theme)

obverse = slice(0, None)
reverse = slice(None, None, -1)
nbreak = len(self.key)
Expand Down
148 changes: 68 additions & 80 deletions plotnine/guides/guides.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@

from plotnine import ggplot, theme
from plotnine.iapi import labels_view
from plotnine.scales.scale import scale
from plotnine.scales.scales import Scales
from plotnine.typing import (
LegendOnly,
LegendOrColorbar,
LegendPosition,
NoGuide,
Orientation,
ScaledAestheticsName,
SidePosition,
TextJustification,
TupleFloat2,
Expand Down Expand Up @@ -93,9 +95,10 @@ def __post_init__(self):
self.plot_scales: Scales
self.plot_labels: labels_view
self.elements: GuidesElements

self._lookup: dict[tuple[scale, ScaledAestheticsName], guide] = {}
if self.colour is not None and self.color is not None:
raise ValueError("Got a guide for color and colour, choose one.")
rename_aesthetics(self)

def __radd__(self, plot: ggplot):
"""
Expand All @@ -117,16 +120,6 @@ def __radd__(self, plot: ggplot):

return plot

def _guide_lookup(self) -> dict[str, guide | NoGuide]:
"""
Lookup dict for guides that have been set
"""
return {
f.name: g
for f in fields(self)
if (g := getattr(self, f.name)) is not None
}

def _build(self) -> Sequence[guide]:
"""
Build the guides
Expand All @@ -139,18 +132,18 @@ def _build(self) -> Sequence[guide]:
"""
return self._create_geoms(self._merge(self._train()))

def _train(self) -> Sequence[guide]:
def _setup(self, plot: ggplot):
"""
Compute all the required guides
Returns
-------
gdefs : list
Guides for the plots
Setup all guides that will be active
"""
gdefs: list[guide] = []
rename_aesthetics(self)
guide_lookup = self._guide_lookup()
self.plot = plot
self.elements = GuidesElements(self.plot.theme)

guide_lookup = {
f.name: g
for f in fields(self)
if (g := getattr(self, f.name)) is not None
}

for scale in self.plot.scales:
for ae in scale.aesthetics:
Expand All @@ -171,58 +164,62 @@ def _train(self) -> Sequence[guide]:

# check the validity of guide.
# if guide is str, then find the guide object
g = self._setup(g)

# Guide turned off
if not g.elements.position:
continue
if isinstance(g, str):
g = Registry[f"guide_{g}"]()
elif not isinstance(g, guide):
raise PlotnineError(f"Unknown guide: {g}")

# check the consistency of the guide and scale.
if (
"any" not in g.available_aes
and scale.aesthetics[0] not in g.available_aes
):
raise PlotnineError(
f"{g.__class__.__name__} cannot be used for "
f"{scale.aesthetics}"
)

# title
if g.title is None:
if scale.name:
g.title = scale.name
else:
g.title = getattr(self.plot.labels, ae)
if g.title is None:
warn(
f"Cannot generate legend for the {ae!r} "
"aesthetic. Make sure you have mapped a "
"variable to it",
PlotnineWarning,
)

# each guide object trains scale within the object,
# so Guides (i.e., the container of guides)
# need not to know about them
g = g.train(scale, ae)

if g is not None:
gdefs.append(g)

return gdefs
g.setup(self)
self._lookup[(scale, ae)] = g

def _setup(self, g: str | guide) -> guide:
"""
Validate guide object
def _train(self) -> Sequence[guide]:
"""
if isinstance(g, str):
g = Registry[f"guide_{g}"]()
Compute all the required guides
if not isinstance(g, guide):
raise PlotnineError(f"Unknown guide: {g}")
Returns
-------
gdefs : list
Guides for the plots
"""
gdefs: list[guide] = []
for (scale, ae), g in self._lookup.items():
# Guide turned off
if not g.elements.position:
continue

# check the consistency of the guide and scale.
if (
"any" not in g.available_aes
and scale.aesthetics[0] not in g.available_aes
):
raise PlotnineError(
f"{g.__class__.__name__} cannot be used for "
f"{scale.aesthetics}"
)

# title
if g.title is None:
if scale.name:
g.title = scale.name
else:
g.title = getattr(self.plot.labels, ae)
if g.title is None:
warn(
f"Cannot generate legend for the {ae!r} "
"aesthetic. Make sure you have mapped a "
"variable to it",
PlotnineWarning,
)

# each guide object trains scale within the object,
# so Guides (i.e., the container of guides)
# need not to know about them
g = g.train(scale, ae)

if g is not None:
gdefs.append(g)

g.setup(self)
return g
return gdefs

def _merge(self, gdefs: Sequence[guide]) -> Sequence[guide]:
"""
Expand Down Expand Up @@ -274,7 +271,6 @@ def _apply_guide_themes(self, gdefs: list[guide]):
Apply the theme for each guide
"""
for g in gdefs:
g.theme = cast("theme", g.theme)
g.theme.apply()

def _assemble_guides(
Expand Down Expand Up @@ -347,24 +343,16 @@ def _anchored_offset_box(boxes: list[PackerBase]):

return legends

def draw(self, plot: ggplot) -> Optional[OffsetBox]:
def draw(self) -> Optional[OffsetBox]:
"""
Draw guides onto the figure
Parameters
----------
plot :
ggplot object
Returns
-------
:matplotlib.offsetbox.Offsetbox | None
A box that contains all the guides for the plot.
If there are no guides, **None** is returned.
"""
self.plot = plot
self.elements = GuidesElements(plot.theme)

if self.elements.position == "none":
return

Expand All @@ -387,9 +375,9 @@ def draw(self, plot: ggplot) -> Optional[OffsetBox]:
self._apply_guide_themes(gdefs)
legends = self._assemble_guides(gdefs, guide_boxes)
for aob in legends.boxes:
plot.figure.add_artist(aob)
self.plot.figure.add_artist(aob)

plot.theme.targets.legends = legends
self.plot.theme.targets.legends = legends


VALID_JUSTIFICATION_WORDS = {"left", "right", "top", "bottom", "center"}
Expand Down
4 changes: 1 addition & 3 deletions plotnine/themes/elements/element_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,8 @@ def __init__(

super().__init__()
self.properties.update(**kwargs)

if margin is not None:
margin = Margin(self, **margin) # type: ignore
self.properties["margin"] = Margin(self, **margin)

# Use the parameters that have been set
names = (
Expand All @@ -138,7 +137,6 @@ def __init__(
"style",
"va",
"weight",
"margin",
)
variables = locals()
for name in names:
Expand Down
6 changes: 3 additions & 3 deletions plotnine/themes/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Optional, Sequence
from typing import Optional

from matplotlib.collections import LineCollection
from matplotlib.patches import Rectangle
Expand All @@ -30,8 +30,8 @@ class ThemeTargets:
legend_frame: Optional[Rectangle] = None
legend_key: list[ColoredDrawingArea] = field(default_factory=list)
legends: Optional[legend_artists] = None
legend_text_colorbar: Sequence[Text] = field(default_factory=list)
legend_text_legend: Sequence[Text] = field(default_factory=list)
legend_text_colorbar: list[Text] = field(default_factory=list)
legend_text_legend: list[Text] = field(default_factory=list)
legend_ticks: Optional[LineCollection] = None
legend_title: Optional[Text] = None
panel_border: list[Rectangle] = field(default_factory=list)
Expand Down
Loading

0 comments on commit f4c577a

Please sign in to comment.