From 01c6ecbbab3ed2da5b036c65d687ae5ca4381fe0 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 7 Apr 2024 17:02:04 +0900 Subject: [PATCH 1/3] add alpha methods --- pyproject.toml | 2 + tests/test_categorical.py | 21 +++--- whitecanvas/canvas/dataframe/_one_cat.py | 71 ++++++++++++++------ whitecanvas/layers/_mixin.py | 6 ++ whitecanvas/layers/_primitive/bars.py | 76 +++++++++++----------- whitecanvas/layers/tabular/_box_like.py | 16 +++++ whitecanvas/layers/tabular/_dataframe.py | 18 ++--- whitecanvas/layers/tabular/_marker_like.py | 73 ++++++++++++++++----- whitecanvas/layers/tabular/_plans.py | 6 ++ 9 files changed, 196 insertions(+), 93 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1332fa0c..1b8e8b7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -183,6 +183,7 @@ lint.ignore = [ "S101", "S105", "S106", "S107", # Ignore complexity "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLW0603", + "ISC001", ] lint.unfixable = [ # Don't touch unused imports @@ -223,4 +224,5 @@ exclude_lines = [ "@overload", "@abstractmethod", "@deprecated", + "raise NotImplementedError", ] diff --git a/tests/test_categorical.py b/tests/test_categorical.py index ffa9980f..afbb024e 100644 --- a/tests/test_categorical.py +++ b/tests/test_categorical.py @@ -54,13 +54,14 @@ def test_cat_plots(backend: str, orient: str): "y": np.arange(30), "label": np.repeat(["A", "B", "C"], 10), "c": ["P", "Q"] * 15, + "val": np.cos(np.arange(30) / 10), } if orient == "v": cat_plt = canvas.cat_x(df, "label", "y") else: cat_plt = canvas.cat_y(df, "y", "label") - cat_plt.add_stripplot(color="c").move(0.1) - cat_plt.add_swarmplot(color="c").move(0.1) + cat_plt.add_stripplot(color="c", alpha=0.8).move(0.1) + cat_plt.add_swarmplot(color="c", alpha="val").move(0.1) cat_plt.add_boxplot(color="c").with_edge().with_outliers(ratio=0.5) with filter_warning(backend, "plotly"): box = cat_plt.add_boxplot(color="c").as_edge_only().move(0.1) @@ -158,14 +159,14 @@ def test_agg(backend: str, orient: str): cat_plt = canvas.cat_x(df, "label", "y") else: cat_plt = canvas.cat_y(df, "y", "label") - cat_plt.mean().add_line(color="c") - cat_plt.mean().add_markers(color="c") - cat_plt.mean().add_bars(color="c") - cat_plt.std().add_line(color="c") - cat_plt.sum().add_line(color="c") - cat_plt.median().add_line(color="c") - cat_plt.max().add_line(color="c") - cat_plt.min().add_line(color="c") + cat_plt.mean().add_line(color="c", alpha=0.8) + cat_plt.mean().add_markers(color="c", alpha=0.8) + cat_plt.mean().add_bars(color="c", alpha=0.8).as_edge_only() + cat_plt.std().add_line(color="c", width=2.5) + cat_plt.sum().add_line() + cat_plt.median().add_bars(color="c").update_width(1.5) + cat_plt.max().add_markers() + cat_plt.min().add_bars(color="c").update_hatch("/") cat_plt.first().add_line(color="c") if orient == "v": canvas.cat_x(df, x="label").count().add_line(color="c") diff --git a/whitecanvas/canvas/dataframe/_one_cat.py b/whitecanvas/canvas/dataframe/_one_cat.py index 83f7eaff..780da456 100644 --- a/whitecanvas/canvas/dataframe/_one_cat.py +++ b/whitecanvas/canvas/dataframe/_one_cat.py @@ -42,7 +42,14 @@ def __repr__(self) -> str: return f"Aggregator<{self._method}>" def __call__(self) -> OneAxisCatAggPlotter[_C, _DF]: - """Aggregate the values before plotting it.""" + """ + Aggregate the values before plotting it. + + Examples + -------- + Calculate mean of "value" for each "group" and connect them with lines. + >>> canvas.cat_x(df, "group", "value").mean().add_line() + """ plotter = self._plotter if plotter is None: raise TypeError("Cannot call this method from a class.") @@ -245,6 +252,7 @@ def add_violinplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | None = None, hatch: NStr | None = None, dodge: NStr | bool = True, extent: float = 0.8, @@ -267,6 +275,8 @@ def add_violinplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float, optional + Opacity of the layer. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. dodge : str, sequence of str or bool, optional @@ -288,7 +298,7 @@ def add_violinplot( dodge=dodge, extent=extent, shape=shape, orient=self._orient, backend=canvas._get_backend(), ) # fmt: skip - self._post_add_boxlike(layer, color) + self._post_add_boxlike(layer, color, alpha) return canvas.add_layer(layer) def add_boxplot( @@ -296,6 +306,7 @@ def add_boxplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | None = None, hatch: NStr | None = None, dodge: NStr | bool = True, capsize: float = 0.1, @@ -318,6 +329,8 @@ def add_boxplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float, optional + Opacity of the layer. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. dodge : str, sequence of str or bool, optional @@ -339,7 +352,7 @@ def add_boxplot( dodge=dodge, orient=self._orient, capsize=capsize, extent=extent, backend=canvas._get_backend(), ) # fmt: skip - self._post_add_boxlike(layer, color) + self._post_add_boxlike(layer, color, alpha) return canvas.add_layer(layer) def add_pointplot( @@ -347,6 +360,7 @@ def add_pointplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | None = None, hatch: NStr | None = None, dodge: NStr | bool = True, capsize: float = 0.1, @@ -374,6 +388,8 @@ def add_pointplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float, optional + Opacity of the layer. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. dodge : str, sequence of str or bool, optional @@ -393,7 +409,7 @@ def add_pointplot( dodge=dodge, orient=self._orient, capsize=capsize, backend=canvas._get_backend(), ) # fmt: skip - self._post_add_boxlike(layer, color) + self._post_add_boxlike(layer, color, alpha) return canvas.add_layer(layer) def add_barplot( @@ -401,6 +417,7 @@ def add_barplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | None = None, hatch: NStr | None = None, dodge: NStr | bool = True, capsize: float = 0.1, @@ -429,6 +446,8 @@ def add_barplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float, optional + Opacity of the layer. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. dodge : str, sequence of str or bool, optional @@ -450,21 +469,24 @@ def add_barplot( dodge=dodge, orient=self._orient, capsize=capsize, extent=extent, backend=canvas._get_backend(), ) # fmt: skip - self._post_add_boxlike(layer, color) + self._post_add_boxlike(layer, color, alpha) return canvas.add_layer(layer) - def _post_add_boxlike(self, layer: _BoxLikeMixin, color): + def _post_add_boxlike(self, layer: _BoxLikeMixin, color, alpha): canvas = self._canvas() if color is not None and not layer._color_by.is_const(): layer.update_color_palette(canvas._color_palette) elif color is None: layer.update_const(color=canvas._color_palette.next()) + if alpha is not None: + layer.update_const(alpha=alpha) def add_stripplot( self, *, name: str | None = None, color: NStr | None = None, + alpha: float | str | None = None, hatch: NStr | None = None, symbol: NStr | None = None, size: str | None = None, @@ -489,6 +511,8 @@ def add_stripplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float or str, optional + Opacity of the markers. If str, it must be a numerical column name. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. symbol : str or sequence of str, optional @@ -526,10 +550,7 @@ def add_stripplot( df, xj, yj, name=name, color=color, hatch=hatch, orient=self._orient, symbol=symbol, size=size, backend=canvas._get_backend(), ) # fmt: skip - if color is not None and not layer._color_by.is_const(): - layer.update_color(layer._color_by.by, palette=canvas._color_palette) - elif color is None: - layer.update_color(canvas._color_palette.next()) + layer._init_color_for_canvas(color, alpha, canvas) return canvas.add_layer(layer) def add_markers( @@ -537,6 +558,7 @@ def add_markers( *, name: str | None = None, color: NStr | None = None, + alpha: float | str | None = None, hatch: NStr | None = None, symbol: NStr | None = None, size: str | None = None, @@ -544,8 +566,8 @@ def add_markers( ) -> _lt.DFMarkerGroups[_DF]: """Alias of `add_stripplot` with no jittering.""" return self.add_stripplot( - color=color, hatch=hatch, symbol=symbol, size=size, dodge=dodge, - extent=0, seed=0, name=name, + color=color, alpha=alpha, hatch=hatch, symbol=symbol, size=size, + dodge=dodge, extent=0, seed=0, name=name, ) # fmt: skip def add_swarmplot( @@ -553,6 +575,7 @@ def add_swarmplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | str | None = None, hatch: NStr | None = None, symbol: NStr | None = None, size: str | None = None, @@ -577,6 +600,8 @@ def add_swarmplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float or str, optional + Opacity of the markers. If str, it must be a numerical column name. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. symbol : str or sequence of str, optional @@ -618,10 +643,7 @@ def add_swarmplot( df, xj, yj, name=name, color=color, hatch=hatch, orient=self._orient, symbol=symbol, size=size, backend=canvas._get_backend(), ) # fmt: skip - if color is not None and not layer._color_by.is_const(): - layer.update_color(layer._color_by.by, palette=canvas._color_palette) - elif color is None: - layer.update_color(canvas._color_palette.next()) + layer._init_color_for_canvas(color, alpha, canvas) return canvas.add_layer(layer) def add_rugplot( @@ -629,6 +651,7 @@ def add_rugplot( *, name: str | None = None, color: NStr | None = None, + alpha: float | str | None = None, width: float | None = None, style: NStr | None = None, dodge: NStr | bool = True, @@ -651,6 +674,8 @@ def add_rugplot( Name of the layer. color : str or sequence of str, optional Column name(s) for coloring the lines. Must be categorical. + alpha : float or str, optional + Opacity of the markers. If str, it must be a numerical column name. hatch : str or sequence of str, optional Column name(s) for hatches. Must be categorical. symbol : str or sequence of str, optional @@ -684,10 +709,7 @@ def add_rugplot( df, jitter, self._get_value(), name=name, color=color, orient=self._orient, extent=_extent, width=width, style=style, backend=canvas._get_backend(), ) # fmt: skip - if color is not None and not layer._color_by.is_const(): - layer.update_color(layer._color_by.by, palette=canvas._color_palette) - elif color is None: - layer.update_color(canvas._color_palette.next()) + layer._init_color_for_canvas(color, alpha, canvas) return canvas.add_layer(layer) def add_heatmap_hist( @@ -775,6 +797,7 @@ def add_line( *, name: str | None = None, color: NStr | None = None, + alpha: float | None = None, width: float | None = None, style: NStr | None = None, dodge: NStr | bool = False, @@ -821,6 +844,8 @@ def add_line( layer.update_color(color, palette=canvas._color_palette) elif color is None: layer.update_color(canvas._color_palette.next()) + if alpha is not None: + layer.update_alpha(alpha) return canvas.add_layer(layer) def add_markers( @@ -828,6 +853,7 @@ def add_markers( *, name: str | None = None, color: NStr | ColorType | None = None, + alpha: float | None = None, hatch: NStr | Hatch | None = None, size: str | float | None = None, symbol: NStr | Symbol | None = None, @@ -880,6 +906,8 @@ def add_markers( layer.update_color(color, palette=canvas._color_palette) elif color is None: layer.update_color(canvas._color_palette.next()) + if alpha is not None: + layer.update_alpha(alpha) return canvas.add_layer(layer) def add_bars( @@ -887,6 +915,7 @@ def add_bars( *, name: str | None = None, color: NStr | ColorType | None = None, + alpha: float | None = None, hatch: NStr | Hatch | None = None, extent: float = 0.8, dodge: NStr | bool = True, @@ -938,6 +967,8 @@ def add_bars( layer.update_color(color, palette=canvas._color_palette) elif color is None: layer.update_color(canvas._color_palette.next()) + if alpha is not None: + layer.update_alpha(alpha) return canvas.add_layer(layer) def _aggregate( diff --git a/whitecanvas/layers/_mixin.py b/whitecanvas/layers/_mixin.py index c9f40765..061d0a22 100644 --- a/whitecanvas/layers/_mixin.py +++ b/whitecanvas/layers/_mixin.py @@ -428,6 +428,12 @@ def width(self) -> float: @width.setter def width(self, value: float): + if not is_real_number(value): + raise ValueError( + "Width must be a real number for a constant-edge layer. If you want to " + "set multiple width values, use `with_edge_multi` method to convert " + "the layer to a multi-edge layer." + ) if value < 0: raise ValueError(f"Edge width must be non-negative, got {value!r}") value = float(value) diff --git a/whitecanvas/layers/_primitive/bars.py b/whitecanvas/layers/_primitive/bars.py index 55033ccd..ca3a1474 100644 --- a/whitecanvas/layers/_primitive/bars.py +++ b/whitecanvas/layers/_primitive/bars.py @@ -351,45 +351,45 @@ def _create_errorbars( backend=self._backend_name ) # fmt: skip - if TYPE_CHECKING: + def with_face( + self, + *, + color: ColorType | _Void = _void, + hatch: Hatch | str = Hatch.SOLID, + alpha: float = 1, + ) -> Bars[ConstFace, _Edge]: + return super().with_face(color=color, hatch=hatch, alpha=alpha) - def with_face( - self, - *, - color: ColorType | _Void = _void, - hatch: Hatch | str = Hatch.SOLID, - alpha: float = 1, - ) -> Bars[ConstFace, _Edge]: - ... - - def with_face_multi( - self, - *, - color: ColorType | Sequence[ColorType] | _Void = _void, - hatch: str | Hatch | Sequence[str | Hatch] | _Void = _void, - alpha: float = 1, - ) -> Bars[MultiFace, _Edge]: - ... - - def with_edge( - self, - *, - color: ColorType | None = None, - width: float = 1, - style: LineStyle | str = LineStyle.SOLID, - alpha: float = 1, - ) -> Bars[_Face, ConstEdge]: - ... - - def with_edge_multi( - self, - *, - color: ColorType | Sequence[ColorType] | None = None, - width: float | Sequence[float] = 1, - style: str | LineStyle | list[str | LineStyle] = LineStyle.SOLID, - alpha: float = 1, - ) -> Bars[_Face, MultiEdge]: - ... + def with_face_multi( + self, + *, + color: ColorType | Sequence[ColorType] | _Void = _void, + hatch: str | Hatch | Sequence[str | Hatch] | _Void = _void, + alpha: float = 1, + ) -> Bars[MultiFace, _Edge]: + return super().with_face_multi(color=color, hatch=hatch, alpha=alpha) + + def with_edge( + self, + *, + color: ColorType | None = None, + width: float = 1, + style: LineStyle | str = LineStyle.SOLID, + alpha: float = 1, + ) -> Bars[_Face, ConstEdge]: + return super().with_edge(color=color, width=width, style=style, alpha=alpha) + + def with_edge_multi( + self, + *, + color: ColorType | Sequence[ColorType] | None = None, + width: float | Sequence[float] = 1, + style: str | LineStyle | list[str | LineStyle] = LineStyle.SOLID, + alpha: float = 1, + ) -> Bars[_Face, MultiEdge]: + return super().with_edge_multi( + color=color, width=width, style=style, alpha=alpha + ) def as_edge_only( self, diff --git a/whitecanvas/layers/tabular/_box_like.py b/whitecanvas/layers/tabular/_box_like.py index 032d87b3..63fab830 100644 --- a/whitecanvas/layers/tabular/_box_like.py +++ b/whitecanvas/layers/tabular/_box_like.py @@ -166,6 +166,7 @@ def update_const( *, color: ColorType | _Void = _void, hatch: str | Hatch | _Void = _void, + alpha: float | _Void = _void, ) -> Self: """ Update the plot features to the constant values. @@ -186,6 +187,21 @@ def update_const( hatch_by = _p.HatchPlan.from_const(Hatch(hatch)) self._get_base().face.hatch = hatch_by.generate(cat, self._splitby) self._hatch_by = hatch_by + if alpha is not _void: + self._get_base().face.alpha = alpha + self._get_base().edge.alpha = alpha + return self + + def with_face( + self, + *, + color: ColorType | None = None, + hatch: str | Hatch | None = None, + alpha: float = 1.0, + ) -> Self: + """Add face to the plot with given settings.""" + if color is not None: + self._get_base().with_face(color=color, hatch=hatch, alpha=alpha) return self def with_edge( diff --git a/whitecanvas/layers/tabular/_dataframe.py b/whitecanvas/layers/tabular/_dataframe.py index c1e91602..2ff91a5e 100644 --- a/whitecanvas/layers/tabular/_dataframe.py +++ b/whitecanvas/layers/tabular/_dataframe.py @@ -98,16 +98,14 @@ def from_table( ) # fmt: skip @overload - def update_color(self, value: ColorType) -> Self: - ... + def update_color(self, value: ColorType) -> Self: ... @overload def update_color( self, by: str | Iterable[str], palette: ColormapType | None = None, - ) -> Self: - ... + ) -> Self: ... def update_color(self, by, /, palette=None): """Update the color rule of the layer.""" @@ -140,6 +138,12 @@ def update_style(self, by: str | Iterable[str], styles=None) -> Self: self._style_by = style_by return self + def update_alpha(self, value: float) -> Self: + """Update alpha of the lines.""" + for line in self._base_layer: + line.alpha = value + return self + def move(self, dx: float = 0.0, dy: float = 0.0, autoscale: bool = True) -> Self: """Add a constant shift to the layer.""" for layer in self._base_layer: @@ -513,16 +517,14 @@ def __init__( self._hatch_by = _p.HatchPlan.default() @overload - def update_color(self, value: ColorType) -> Self: - ... + def update_color(self, value: ColorType) -> Self: ... @overload def update_color( self, by: str | Iterable[str], palette: ColormapType | None = None, - ) -> Self: - ... + ) -> Self: ... def update_color(self, by, /, palette=None): cov = _shared.ColumnOrValue(by, self._source) diff --git a/whitecanvas/layers/tabular/_marker_like.py b/whitecanvas/layers/tabular/_marker_like.py index 115f8ab0..c952bf8a 100644 --- a/whitecanvas/layers/tabular/_marker_like.py +++ b/whitecanvas/layers/tabular/_marker_like.py @@ -37,6 +37,8 @@ if TYPE_CHECKING: from typing_extensions import Self + from whitecanvas.canvas import CanvasBase + _DF = TypeVar("_DF") _Cols = Union[str, "tuple[str, ...]"] _void = _Void() @@ -46,8 +48,7 @@ class _MarkerLikeMixin: _source: DataFrameWrapper[_DF] @overload - def update_color(self, value: ColorType) -> Self: - ... + def update_color(self, value: ColorType) -> Self: ... @overload def update_color( @@ -74,6 +75,28 @@ def update_color(self, by, /, palette=None) -> Self: self._color_by = color_by return self + @overload + def update_alpha(self, value: float) -> Self: ... + + @overload + def update_alpha( + self, + by: str | Iterable[str], + map_from: tuple[float, float] | None = None, + map_to: tuple[float, float] = (0.2, 1.0), + ) -> Self: ... + + def update_alpha(self, by, /, map_from=None, map_to=(0.2, 1.0)) -> Self: + if isinstance(by, str): + alpha_by = _p.AlphaPlan.from_range(by, range=map_to, domain=map_from) + else: + if map_from is not None: + raise TypeError("`map_from` can only be used with a column name.") + alpha_by = _p.AlphaPlan.from_const(float(by)) + self._apply_alpha(alpha_by.map(self._source)) + self._alpha_by = alpha_by + return self + def update_colormap( self, by: str, @@ -94,8 +117,7 @@ def update_colormap( return self @overload - def update_width(self, value: float) -> Self: - ... + def update_width(self, value: float) -> Self: ... @overload def update_width( @@ -131,16 +153,14 @@ def update_width(self, by, /, map_from=None, map_to=(1.0, 4.0)) -> Self: return self @overload - def update_style(self, value: ColorType) -> Self: - ... + def update_style(self, value: ColorType) -> Self: ... @overload def update_style( self, by: str | Iterable[str], palette: ColormapType | None = None, - ) -> Self: - ... + ) -> Self: ... def update_style(self, by, /, palette=None) -> Self: cov = _shared.ColumnOrValue(by, self._source) @@ -154,6 +174,15 @@ def update_style(self, by, /, palette=None) -> Self: self._style_by = style_by return self + def _init_color_for_canvas(self, color, alpha, canvas: CanvasBase): + if color is not None and not self._color_by.is_const(): + self.update_color(self._color_by.by, palette=canvas._color_palette) + elif color is None: + self.update_color(canvas._color_palette.next()) + if alpha is not None: + self.update_alpha(alpha) + return self + def _apply_color(self, color): """Set color array to the layer.""" raise NotImplementedError @@ -162,6 +191,10 @@ def _apply_width(self, width): """Set width array to the layer.""" raise NotImplementedError + def _apply_alpha(self, alpha): + """Set alpha array to the layer.""" + raise NotImplementedError + def _apply_style(self, style): """Set style array to the layer.""" raise NotImplementedError @@ -217,6 +250,9 @@ def _apply_color(self, color): def _apply_width(self, width): self.base.with_edge(color=_void, width=width, style=_void) + def _apply_alpha(self, alpha): + self.base.face.alpha = alpha + def _apply_style(self, style): self.base.with_edge(color=_void, width=_void, style=style) @@ -261,8 +297,7 @@ def update_hatch(self, by: str | Iterable[str], palette=None) -> Self: return self @overload - def update_size(self, value: float) -> Self: - ... + def update_size(self, value: float) -> Self: ... @overload def update_size( @@ -296,14 +331,12 @@ def update_size(self, by, /, map_from=None, map_to=(3, 15)): return self @overload - def update_symbol(self, value: str | Symbol) -> Self: - ... + def update_symbol(self, value: str | Symbol) -> Self: ... @overload def update_symbol( self, by: str | Iterable[str] | None = None, symbols=None - ) -> Self: - ... + ) -> Self: ... def update_symbol(self, by, /, symbols=None) -> Self: cov = _shared.ColumnOrValue(by, self._source) @@ -498,7 +531,7 @@ def __init__( base = _l.Bars( x, y, bottom=bottom, name=name, orient=orient, extent=extent, backend=backend - ).with_face_multi() # fmt: skip + ).with_face_multi().with_edge_multi() # fmt: skip super().__init__(base, source) if color is not None: self.update_color(color) @@ -567,10 +600,13 @@ def _apply_color(self, color): self.base.face.color = color def _apply_width(self, width): - self._base_layer.with_edge(color=_void, width=width, style=_void) + self._base_layer.with_edge_multi(color=_void, width=width, style=_void) + + def _apply_alpha(self, alpha): + self.base.face.alpha = alpha def _apply_style(self, style): - self._base_layer.with_edge(color=_void, width=_void, style=style) + self._base_layer.with_edge_multi(color=_void, width=_void, style=style) def update_hatch(self, by: str | Iterable[str], choices=None) -> Self: cov = _shared.ColumnOrValue(by, self._source) @@ -691,6 +727,9 @@ def orient(self) -> Orientation: def _apply_color(self, color): self.base.color = color + def _apply_alpha(self, alpha): + self.base.alpha = alpha + def _apply_width(self, width): self.base.width = width diff --git a/whitecanvas/layers/tabular/_plans.py b/whitecanvas/layers/tabular/_plans.py index f04cc5b7..06710a30 100644 --- a/whitecanvas/layers/tabular/_plans.py +++ b/whitecanvas/layers/tabular/_plans.py @@ -403,6 +403,12 @@ def _default_mapper(cls): return lambda _: 12.0 +class AlphaPlan(ScalarMapPlan): + @classmethod + def _default_mapper(cls): + return lambda _: 1.0 + + class ConstMap: def __init__(self, value): self._value = value From b4933622a768c935104cf0c921ecfe6f9e5e29af Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 7 Apr 2024 22:19:47 +0900 Subject: [PATCH 2/3] fix edge alpha not updated --- whitecanvas/layers/tabular/_marker_like.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/whitecanvas/layers/tabular/_marker_like.py b/whitecanvas/layers/tabular/_marker_like.py index c952bf8a..47927788 100644 --- a/whitecanvas/layers/tabular/_marker_like.py +++ b/whitecanvas/layers/tabular/_marker_like.py @@ -252,6 +252,7 @@ def _apply_width(self, width): def _apply_alpha(self, alpha): self.base.face.alpha = alpha + self.base.edge.alpha = alpha def _apply_style(self, style): self.base.with_edge(color=_void, width=_void, style=style) @@ -604,6 +605,7 @@ def _apply_width(self, width): def _apply_alpha(self, alpha): self.base.face.alpha = alpha + self.base.edge.alpha = alpha def _apply_style(self, style): self._base_layer.with_edge_multi(color=_void, width=_void, style=style) From abeac1fa9dc8cf31b3f3495f379b7c3728b4ba96 Mon Sep 17 00:00:00 2001 From: Hanjin Liu Date: Sun, 7 Apr 2024 22:23:05 +0900 Subject: [PATCH 3/3] more test --- tests/test_categorical.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_categorical.py b/tests/test_categorical.py index afbb024e..f661f27f 100644 --- a/tests/test_categorical.py +++ b/tests/test_categorical.py @@ -205,10 +205,10 @@ def test_catx_legend(backend: str): "label": np.repeat(["A", "B", "C"], 10), } _c = canvas.cat_x(df, "x", "y") - _c.add_boxplot(color="label") - _c.add_violinplot(color="label").with_rug() - _c.add_pointplot(color="label").err_by_se() - _c.add_barplot(color="label") + _c.add_boxplot(color="label", alpha=0.8) + _c.add_violinplot(color="label", alpha=0.8).with_rug() + _c.add_pointplot(color="label", alpha=0.8).err_by_se() + _c.add_barplot(color="label", alpha=0.8) canvas.add_legend() def test_marker_legend():