Skip to content

Commit b6682c2

Browse files
committed
Fix after_scale aesthetic to show up in the legend
fixes #926
1 parent 52588be commit b6682c2

File tree

6 files changed

+44
-13
lines changed

6 files changed

+44
-13
lines changed

doc/changelog.qmd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ title: Changelog
8787
- Fixed bug where facetting along a column with unused categories could raise
8888
an error. {{< issue 930 >}}
8989

90+
- Fixed bug where computed aesthetic from `after_scale` are were not applied to the legend.
91+
{{< issue 926 >}}
92+
93+
9094
## v0.14.5
9195
(2025-01-02)
9296
[![](https://zenodo.org/badge/DOI/10.5281/zenodo.14587381.svg)](https://doi.org/10.5281/zenodo.14587381)

plotnine/guides/guide.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from typing_extensions import Self
1919

2020
from plotnine import aes, guides
21-
from plotnine.layer import Layers
21+
from plotnine.layer import Layers, layer
2222
from plotnine.scales.scale import scale
2323
from plotnine.typing import (
2424
LegendPosition,
@@ -79,7 +79,7 @@ def __post_init__(self):
7979
self.elements = cast("GuideElements", None)
8080
self.guides_elements: GuidesElements
8181

82-
def legend_aesthetics(self, layer):
82+
def legend_aesthetics(self, layer: layer):
8383
"""
8484
Return the aesthetics that contribute to the legend
8585

plotnine/guides/guide_legend.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,21 +171,20 @@ def create_geoms(self):
171171
# Modify aesthetics
172172

173173
# When doing after_scale evaluations, we only consider those
174-
# for the aesthetics of this legend. The reduces the spurious
175-
# warnings where an evaluation of another aesthetic failed yet
176-
# it is not needed.
174+
# for the aesthetics that are valid for this layer/geom.
177175
aes_modifiers = {
178-
ae: expr
179-
for ae, expr in l.mapping._scaled.items()
180-
if ae in matched_set
176+
ae: l.mapping._scaled[ae]
177+
for ae in l.geom.aesthetics() & l.mapping._scaled.keys()
181178
}
182179

183180
try:
184181
data = l.use_defaults(data, aes_modifiers)
185182
except PlotnineError:
186183
warn(
187-
"Failed to apply `after_scale` modifications "
188-
"to the legend.",
184+
"Failed to apply `after_scale` modifications to the "
185+
"legend. This probably should not happen. Help us "
186+
"discover why, please open and issue at "
187+
"https://github.com/has2k1/plotnine/issues",
189188
PlotnineWarning,
190189
)
191190
data = l.use_defaults(data, {})

plotnine/mapping/aes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from contextlib import suppress
77
from copy import deepcopy
88
from dataclasses import fields
9+
from functools import cached_property
910
from typing import Any, Dict
1011

1112
import pandas as pd
@@ -237,7 +238,7 @@ def _convert_deprecated_expr(self, kwargs):
237238
kwargs[name] = after_stat(_after_stat)
238239
return kwargs
239240

240-
@property
241+
@cached_property
241242
def _starting(self) -> dict[str, Any]:
242243
"""
243244
Return the subset of aesthetics mapped from the layer data
@@ -254,7 +255,7 @@ def _starting(self) -> dict[str, Any]:
254255

255256
return d
256257

257-
@property
258+
@cached_property
258259
def _calculated(self) -> dict[str, Any]:
259260
"""
260261
Return only the aesthetics mapped to calculated statistics
@@ -269,7 +270,7 @@ def _calculated(self) -> dict[str, Any]:
269270

270271
return d
271272

272-
@property
273+
@cached_property
273274
def _scaled(self) -> dict[str, Any]:
274275
"""
275276
Return only the aesthetics mapped to after scaling
Loading

tests/test_guide_internals.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import warnings
22

3+
import pandas as pd
4+
35
from plotnine import (
46
aes,
7+
after_scale,
8+
geom_bar,
59
geom_point,
610
ggplot,
711
)
@@ -14,3 +18,26 @@ def test_no_after_scale_warning():
1418
with warnings.catch_warnings():
1519
warnings.simplefilter("error")
1620
p.draw_test() # type: ignore
21+
22+
23+
def test_guide_legend_after_scale():
24+
def alphen(series, a):
25+
ha = f"{round(a * 255):#04X}"[2:]
26+
return [f"{hex_color}{ha}" for hex_color in series]
27+
28+
data = pd.DataFrame(
29+
{"var1": [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5]}
30+
)
31+
p = (
32+
ggplot(
33+
data,
34+
aes(
35+
"var1",
36+
color="factor(var1)",
37+
fill=after_scale("alphen(color, .5)"),
38+
),
39+
)
40+
+ geom_bar()
41+
)
42+
43+
assert p == "guide_legend_after_scale"

0 commit comments

Comments
 (0)