Skip to content

Commit fc84802

Browse files
committed
refactor(scales): parameterize scale_discrete over its guide type
Make scale_discrete generic over GuideTypeT instead of hardcoding guide: Literal["legend"] | None, and add OptionalLegend, OptionalColorbar and OptionalGuide aliases to _runtime_typing. Each discrete scale subclass now declares its own guide field. Behavior change: identity scales for color, fill, shape, linetype and alpha now default to guide="legend" (size and stroke keep None). scale_position_discrete declares guide: None as a field instead of assigning it in __post_init__.
1 parent e846b3a commit fc84802

11 files changed

Lines changed: 77 additions & 55 deletions

plotnine/scales/_runtime_typing.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
the documentation.
66
"""
77

8-
from typing import Callable, Sequence, Type, TypeAlias, TypeVar
8+
from typing import Callable, Literal, Sequence, Type, TypeAlias, TypeVar
99

1010
from mizani.transforms import trans
1111

@@ -51,6 +51,10 @@
5151

5252
TransUser: TypeAlias = trans | str | Type[trans] | None
5353

54+
OptionalLegend: TypeAlias = Literal["legend"] | None
55+
OptionalColorbar: TypeAlias = Literal["colorbar"] | None
56+
OptionalGuide: TypeAlias = Literal["legend", "colorbar"] | None
57+
5458
RangeT = TypeVar("RangeT", bound=Range)
5559
BreaksUserT = TypeVar("BreaksUserT")
5660
LimitsUserT = TypeVar("LimitsUserT")

plotnine/scales/scale_alpha.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from dataclasses import KW_ONLY, InitVar, dataclass
2-
from typing import Literal
32
from warnings import warn
43

54
import numpy as np
65

6+
from plotnine.scales._runtime_typing import OptionalLegend
7+
78
from .._utils.registry import alias
89
from ..exceptions import PlotnineWarning
910
from .scale_continuous import scale_continuous
@@ -12,7 +13,7 @@
1213

1314

1415
@dataclass
15-
class scale_alpha(scale_continuous[Literal["legend"]]):
16+
class scale_alpha(scale_continuous[OptionalLegend]):
1617
"""
1718
Continuous Alpha Scale
1819
"""
@@ -25,7 +26,7 @@ class scale_alpha(scale_continuous[Literal["legend"]]):
2526
"""
2627

2728
_: KW_ONLY
28-
guide: Literal["legend"] = "legend"
29+
guide: OptionalLegend = "legend"
2930

3031
def __post_init__(self, range):
3132
from mizani.palettes import rescale_pal
@@ -40,11 +41,14 @@ class scale_alpha_continuous(scale_alpha):
4041

4142

4243
@dataclass
43-
class scale_alpha_ordinal(scale_discrete):
44+
class scale_alpha_ordinal(scale_discrete[OptionalLegend]):
4445
"""
4546
Ordinal Alpha Scale
4647
"""
4748

49+
_: KW_ONLY
50+
guide: OptionalLegend = "legend"
51+
4852
_aesthetics = ["alpha"]
4953
range: InitVar[tuple[float, float]] = (0.1, 1)
5054
"""
@@ -89,7 +93,7 @@ class scale_alpha_datetime(scale_datetime):
8993
"""
9094

9195
_: KW_ONLY
92-
guide: Literal["legend"] = "legend"
96+
guide: OptionalLegend = "legend"
9397

9498
def __post_init__(
9599
self,

plotnine/scales/scale_color.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from typing import Literal, Sequence
55
from warnings import warn
66

7+
from plotnine.scales._runtime_typing import OptionalGuide, OptionalLegend
8+
79
from .._utils.registry import alias
810
from ..exceptions import PlotnineWarning
911
from .scale_continuous import scale_continuous
@@ -12,13 +14,14 @@
1214

1315

1416
@dataclass
15-
class _scale_color_discrete(scale_discrete):
17+
class _scale_color_discrete(scale_discrete[OptionalLegend]):
1618
"""
1719
Base class for all discrete color scales
1820
"""
1921

2022
_aesthetics = ["color"]
2123
_: KW_ONLY
24+
guide: OptionalLegend = "legend"
2225
na_value: str = "#7F7F7F"
2326
"""
2427
Color of missing values.
@@ -27,15 +30,15 @@ class _scale_color_discrete(scale_discrete):
2730

2831
@dataclass
2932
class _scale_color_continuous(
30-
scale_continuous[Literal["legend", "colorbar"] | None],
33+
scale_continuous[OptionalGuide],
3134
):
3235
"""
3336
Base class for all continuous color scales
3437
"""
3538

3639
_aesthetics = ["color"]
3740
_: KW_ONLY
38-
guide: Literal["legend", "colorbar"] | None = "colorbar"
41+
guide: OptionalGuide = "colorbar"
3942
na_value: str = "#7F7F7F"
4043
"""
4144
Color of missing values.
@@ -563,7 +566,7 @@ class scale_color_datetime(scale_datetime, scale_color_cmap): # pyright: ignore
563566
"""
564567

565568
_: KW_ONLY
566-
guide: Literal["legend", "colorbar"] | None = "colorbar"
569+
guide: OptionalGuide = "colorbar"
567570

568571
def __post_init__(
569572
self,

plotnine/scales/scale_discrete.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from typing import TYPE_CHECKING, Any, Literal, Sequence
4+
from typing import TYPE_CHECKING, Any, Sequence
55

66
import numpy as np
77
import pandas as pd
@@ -11,7 +11,7 @@
1111
from .._utils import match
1212
from ..iapi import range_view, scale_view
1313
from ._expand import expand_range
14-
from ._runtime_typing import DiscreteBreaksUser, DiscreteLimitsUser
14+
from ._runtime_typing import DiscreteBreaksUser, DiscreteLimitsUser, GuideTypeT
1515
from .range import RangeDiscrete
1616
from .scale import scale
1717

@@ -29,7 +29,7 @@ class scale_discrete(
2929
RangeDiscrete,
3030
DiscreteBreaksUser,
3131
DiscreteLimitsUser,
32-
Literal["legend"] | None,
32+
GuideTypeT,
3333
]
3434
):
3535
"""
@@ -69,8 +69,6 @@ class scale_discrete(
6969
`nan` is always placed on the right.
7070
"""
7171

72-
guide: Literal["legend"] | None = "legend"
73-
7472
def __post_init__(self):
7573
super().__post_init__()
7674
self._range = RangeDiscrete()

plotnine/scales/scale_identity.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

33
from dataclasses import KW_ONLY, dataclass
4-
from typing import TYPE_CHECKING, Literal
4+
from typing import TYPE_CHECKING
5+
6+
from plotnine.scales._runtime_typing import OptionalLegend
57

68
from .._utils.registry import alias
79
from .scale_continuous import scale_continuous
@@ -37,14 +39,14 @@ def train(self, x, drop=False):
3739

3840

3941
@dataclass
40-
class scale_color_identity(MapTrainMixin, scale_discrete):
42+
class scale_color_identity(MapTrainMixin, scale_discrete[OptionalLegend]):
4143
"""
4244
No color scaling
4345
"""
4446

4547
_aesthetics = ["color"]
4648
_: KW_ONLY
47-
guide: Literal["legend"] | None = None
49+
guide: OptionalLegend = "legend"
4850

4951

5052
@dataclass
@@ -54,67 +56,61 @@ class scale_fill_identity(scale_color_identity):
5456
"""
5557

5658
_aesthetics = ["fill"]
57-
_: KW_ONLY
58-
guide: Literal["legend"] | None = None
5959

6060

6161
@dataclass
62-
class scale_shape_identity(MapTrainMixin, scale_discrete):
62+
class scale_shape_identity(MapTrainMixin, scale_discrete[OptionalLegend]):
6363
"""
6464
No shape scaling
6565
"""
6666

6767
_aesthetics = ["shape"]
6868
_: KW_ONLY
69-
guide: Literal["legend"] | None = None
69+
guide: OptionalLegend = "legend"
7070

7171

7272
@dataclass
73-
class scale_linetype_identity(MapTrainMixin, scale_discrete):
73+
class scale_linetype_identity(MapTrainMixin, scale_discrete[OptionalLegend]):
7474
"""
7575
No linetype scaling
7676
"""
7777

7878
_aesthetics = ["linetype"]
7979
_: KW_ONLY
80-
guide: Literal["legend"] | None = None
80+
guide: OptionalLegend = "legend"
8181

8282

8383
@dataclass
84-
class scale_alpha_identity(
85-
MapTrainMixin, scale_continuous[Literal["legend"] | None]
86-
):
84+
class scale_alpha_identity(MapTrainMixin, scale_continuous[OptionalLegend]):
8785
"""
8886
No alpha scaling
8987
"""
9088

9189
_aesthetics = ["alpha"]
9290
_: KW_ONLY
93-
guide: Literal["legend"] | None = None
91+
guide: OptionalLegend = "legend"
9492

9593

9694
@dataclass
97-
class scale_size_identity(
98-
MapTrainMixin, scale_continuous[Literal["legend"] | None]
99-
):
95+
class scale_size_identity(MapTrainMixin, scale_continuous[OptionalLegend]):
10096
"""
10197
No size scaling
10298
"""
10399

104100
_aesthetics = ["size"]
105101
_: KW_ONLY
106-
guide: Literal["legend"] | None = None
102+
guide: OptionalLegend = None
107103

108104

109105
@dataclass
110-
class scale_stroke_identity(MapTrainMixin, scale_discrete):
106+
class scale_stroke_identity(MapTrainMixin, scale_discrete[OptionalLegend]):
111107
"""
112108
No stroke scaling
113109
"""
114110

115111
_aesthetics = ["stroke"]
116112
_: KW_ONLY
117-
guide: Literal["legend"] | None = None
113+
guide: OptionalLegend = None
118114

119115

120116
# American to British spelling

plotnine/scales/scale_linetype.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from dataclasses import dataclass
1+
from dataclasses import KW_ONLY, dataclass
22
from warnings import warn
33

4+
from plotnine.scales._runtime_typing import OptionalLegend
5+
46
from .._utils.registry import alias
57
from ..exceptions import PlotnineError, PlotnineWarning
68
from .scale_continuous import scale_continuous
@@ -10,7 +12,7 @@
1012

1113

1214
@dataclass
13-
class scale_linetype(scale_discrete):
15+
class scale_linetype(scale_discrete[OptionalLegend]):
1416
"""
1517
Scale for line patterns
1618
@@ -24,6 +26,9 @@ class scale_linetype(scale_discrete):
2426

2527
_aesthetics = ["linetype"]
2628

29+
_: KW_ONLY
30+
guide: OptionalLegend = "legend"
31+
2732
def __post_init__(self):
2833
from mizani.palettes import manual_pal
2934

plotnine/scales/scale_manual.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from typing import Any, Sequence
66
from warnings import warn
77

8+
from plotnine.scales._runtime_typing import OptionalLegend
9+
810
from .._utils.registry import alias
911
from ..exceptions import PlotnineWarning
1012
from .scale_discrete import scale_discrete
1113

1214

1315
@dataclass
14-
class _scale_manual(scale_discrete):
16+
class _scale_manual(scale_discrete[OptionalLegend]):
1517
"""
1618
Abstract class for manual scales
1719
"""
@@ -20,6 +22,8 @@ class _scale_manual(scale_discrete):
2022
"""
2123
Exact values the scale should map to.
2224
"""
25+
_: KW_ONLY
26+
guide: OptionalLegend = "legend"
2327

2428
def __post_init__(self, values):
2529
from collections.abc import Iterable, Sized

plotnine/scales/scale_shape.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from dataclasses import InitVar, dataclass
1+
from dataclasses import KW_ONLY, InitVar, dataclass
22
from warnings import warn
33

4+
from plotnine.scales._runtime_typing import OptionalLegend
5+
46
from .._utils.registry import alias
57
from ..exceptions import PlotnineError, PlotnineWarning
68
from .scale_continuous import scale_continuous
@@ -50,7 +52,7 @@
5052

5153

5254
@dataclass
53-
class scale_shape(scale_discrete):
55+
class scale_shape(scale_discrete[OptionalLegend]):
5456
"""
5557
Scale for shapes
5658
"""
@@ -61,6 +63,8 @@ class scale_shape(scale_discrete):
6163
If `True`, then all shapes will have no interiors
6264
that can be a filled.
6365
"""
66+
_: KW_ONLY
67+
guide: OptionalLegend = "legend"
6468

6569
def __post_init__(self, unfilled):
6670
from mizani.palettes import manual_pal

0 commit comments

Comments
 (0)