diff --git a/pyproject.toml b/pyproject.toml index a74433ee..623b1797 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,8 @@ testing = [ "plotly>=5.3.1", "vispy>=0.14.1", "bokeh>=3.3.1", + "pandas>=1.3.3", + "polars>=0.20.10", ] docs = [ diff --git a/tests/conftest.py b/tests/conftest.py index 0e808bcd..6f506583 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import matplotlib.pyplot as plt +from whitecanvas.canvas import Canvas import pytest ALL_BACKENDS = ["mock", "matplotlib", "pyqtgraph", "plotly", "bokeh", "vispy"] @@ -19,3 +20,6 @@ def backend(request: pytest.FixtureRequest): # TODO: how to skip tests if failed in mock backend? if request.param == "matplotlib": plt.close("all") + elif request.param == "pyqtgraph": + if Canvas._CURRENT_INSTANCE is not None: + Canvas._CURRENT_INSTANCE.native.update() diff --git a/tests/test_canvas.py b/tests/test_canvas.py index cd6eaea7..9c44557f 100644 --- a/tests/test_canvas.py +++ b/tests/test_canvas.py @@ -5,7 +5,7 @@ import pytest import whitecanvas as wc -from whitecanvas import new_canvas +from whitecanvas import new_canvas, wrap_canvas from ._utils import assert_color_equal @@ -20,6 +20,10 @@ def test_namespaces(backend: str): assert canvas.title.size == 20 canvas.title.family = "Arial" assert canvas.title.family == "Arial" + canvas.title.visible = False + assert not canvas.title.visible + canvas.title.visible = True + assert canvas.title.visible canvas.x.label.text = "X-Label-0" assert canvas.x.label.text == "X-Label-0" @@ -29,6 +33,10 @@ def test_namespaces(backend: str): assert canvas.x.label.size == 20 canvas.x.label.family = "Arial" assert canvas.x.label.family == "Arial" + canvas.x.label.visible = False + assert not canvas.x.label.visible + canvas.x.label.visible = True + assert canvas.x.label.visible canvas.y.label.text = "Y-Label-0" assert canvas.y.label.text == "Y-Label-0" @@ -39,6 +47,24 @@ def test_namespaces(backend: str): canvas.y.label.family = "Arial" assert canvas.y.label.family == "Arial" + if backend != "pyqtgraph": # not implemented in pyqtgraph + canvas.x.ticks.rotation = 45 + assert canvas.x.ticks.rotation == pytest.approx(45) + + canvas.x.ticks.visible = False + assert not canvas.x.ticks.visible + canvas.x.ticks.visible = True + assert canvas.x.ticks.visible + canvas.x.ticks.set_labels([0, 1, 2], ["a", "b", "c"]) + + # get tick positions and labels are still hard to implement + if backend in ("mock", "pyqtgraph", "plotly"): + assert_allclose(canvas.x.ticks.pos, [0, 1, 2]) + assert canvas.x.ticks.labels == ["a", "b", "c"] + canvas.x.ticks.reset_labels() + + canvas.x.set_gridlines() + def test_namespace_pointing_at_different_objects(): c0 = new_canvas(backend="matplotlib") c1 = new_canvas(backend="matplotlib") @@ -53,12 +79,29 @@ def test_namespace_pointing_at_different_objects(): assert_color_equal(c0.x.color, "red") assert_color_equal(c1.x.color, "blue") +def test_update_methods(): + canvas = new_canvas(backend="mock") + canvas.update_axes(visible=False) + canvas.update_labels(title="Title", x="X", y="Y") + canvas.update_font(size=24, color="red", family="Arial") + +def test_native_and_wrapping(backend: str): + canvas = new_canvas(backend=backend) + assert canvas.native is not None + if backend == "mock": + return + new = wrap_canvas(canvas.native) + repr(new._get_backend()) + assert new._get_backend() == canvas._get_backend() + def test_grid(backend: str): cgrid = wc.new_grid(2, 2, backend=backend).link_x().link_y() c00 = cgrid.add_canvas(0, 0) c01 = cgrid.add_canvas(0, 1) c10 = cgrid.add_canvas(1, 0) c11 = cgrid.add_canvas(1, 1) + assert cgrid[0, 0] is not cgrid[1, 0] + assert cgrid[0, 1] is not cgrid[1, 1] c00.add_line([0, 1, 2], [0, 1, 2]) c01.add_hist([0, 1, 2, 3, 4, 3, 2, 1]) @@ -106,6 +149,7 @@ def test_vgrid_hgrid(backend: str): cgrid = wc.new_col(2, backend=backend, size=(100, 100)).link_x().link_y() c0 = cgrid.add_canvas(0) c1 = cgrid.add_canvas(1) + assert cgrid[0] is not cgrid[1] c0.add_line([0, 1, 2], [0, 1, 2]) c1.add_hist([0, 1, 2, 3, 4, 3, 2, 1]) @@ -119,6 +163,7 @@ def test_vgrid_hgrid(backend: str): cgrid = wc.new_row(2, backend=backend, size=(100, 100)).link_x().link_y() c0 = cgrid.add_canvas(0) c1 = cgrid.add_canvas(1) + assert cgrid[0] is not cgrid[1] c0.add_line([0, 1, 2], [0, 1, 2]) c1.add_hist([0, 1, 2, 3, 4, 3, 2, 1]) @@ -144,6 +189,8 @@ def test_jointgrid(backend: str): rng = np.random.default_rng(0) joint = wc.new_jointgrid(backend=backend, size=(100, 100)).with_hist().with_kde().with_rug() joint.add_markers(rng.random(100), rng.random(100), color="red") + if backend != "vispy": + joint.add_legend() def test_legend(backend: str): if backend == "vispy": diff --git a/tests/test_categorical.py b/tests/test_categorical.py index 77e80c10..a6f4ddab 100644 --- a/tests/test_categorical.py +++ b/tests/test_categorical.py @@ -1,7 +1,7 @@ -import warnings import numpy as np from whitecanvas import new_canvas +from whitecanvas.core import new_jointgrid from ._utils import assert_color_array_equal, filter_warning import pytest @@ -33,6 +33,9 @@ def test_cat(backend: str): hist = cplt.along_y().add_hist(bins=6, color="label") cplt.along_x().add_kde() kde = cplt.along_x().add_kde(color="label") + cplt.along_x().add_rug() + with filter_warning(backend, "plotly"): + cplt.along_x().add_rug(color="label") hist.update_color("black") kde.update_color("black") hist.update_width(1.5) @@ -105,6 +108,7 @@ def test_markers(backend: str): assert_color_array_equal(out._base_layer.face.color, "black") out = _c.add_markers(color="transparent").update_edge_colormap("size") + _c.mean_for_each("label0").add_markers(symbol="D") def test_heatmap(backend: str): canvas = new_canvas(backend=backend) @@ -245,3 +249,36 @@ def test_stack(backend: str): cat_plt = canvas.cat_x(df, "label", "y", numeric_axis=True) cat_plt.stack("c").add_bars(color="c") cat_plt.stack("c").add_area(hatch="c") + +def test_joint_cat(backend: str): + joint = new_jointgrid(backend=backend, loc=(0, 0), size=(180, 180)) + df = { + "x": np.arange(30), + "y": np.arange(30), + "c": np.repeat(["A", "B", "C"], 10), + } + joint.cat(df, "x", "y").add_hist2d() + joint.cat(df, "x", "y").add_markers(color="c") + +def test_pandas_and_polars(): + import pandas as pd + import polars as pl + + canvas = new_canvas(backend="mock") + _dict = { + "y": np.arange(30), + "label": np.repeat(["A", "B", "C"], 10), + "c": ["P", "Q"] * 15, + } + df_pd = pd.DataFrame(_dict) + df_pl = pl.DataFrame(_dict) + + cat_pd = canvas.cat_x(df_pd, "label", "y") + cat_pl = canvas.cat_x(df_pl, "label", "y") + cat_pd.add_swarmplot(color="c") + cat_pd.mean().add_markers(color="c") + cat_pd.first().add_markers(color="c") + + cat_pl.add_swarmplot(color="c") + cat_pl.mean().add_markers(color="c") + cat_pl.first().add_markers(color="c") diff --git a/tests/test_layers.py b/tests/test_layers.py index eb60bff7..139831cd 100644 --- a/tests/test_layers.py +++ b/tests/test_layers.py @@ -30,6 +30,7 @@ def test_line(backend: str): _test_visibility(layer) layer.with_hover_template("x={x:.2f}, y={y:.2f}") canvas.add_cdf(np.sqrt(np.arange(20))) + canvas.autoscale() def test_markers(backend: str): canvas = new_canvas(backend=backend) @@ -70,6 +71,7 @@ def test_markers(backend: str): layer.symbol = sym assert layer.symbol == sym _test_visibility(layer) + canvas.autoscale() def test_bars(backend: str): canvas = new_canvas(backend=backend) @@ -101,6 +103,7 @@ def test_bars(backend: str): layer.bar_width = 0.5 assert layer.bar_width == 0.5 _test_visibility(layer) + canvas.autoscale() def test_infcurve(backend: str): canvas = new_canvas(backend=backend) @@ -129,6 +132,9 @@ def test_infcurve(backend: str): canvas.x.lim = (-4, 4) layer.angle = 90 canvas.x.lim = (-4, 4) + canvas.autoscale() + canvas.add_hline(1) + canvas.add_vline(1) def test_band(backend: str): canvas = new_canvas(backend=backend) @@ -155,11 +161,13 @@ def test_band(backend: str): layer.edge.width = 2 assert layer.edge.width == 2 _test_visibility(layer) + canvas.autoscale() def test_image(backend: str): canvas = new_canvas(backend=backend) - layer = canvas.add_image(np.random.random((10, 10)) * 2) + rng = np.random.default_rng(0) + layer = canvas.add_image(rng.random((10, 10)) * 2) layer.cmap = "viridis" assert layer.cmap == "viridis" @@ -173,6 +181,8 @@ def test_image(backend: str): layer.origin = "edge" layer.shift = (-1, -1) layer.fit_to(2, 2, 5, 5) + canvas.autoscale() + canvas.add_heatmap(rng.random((10, 10))) def test_errorbars(backend: str): canvas = new_canvas(backend=backend) @@ -196,6 +206,7 @@ def test_errorbars(backend: str): layer.width = 3 assert all(w == 3 for w in layer.width) _test_visibility(layer) + canvas.autoscale() def test_texts(backend: str): canvas = new_canvas(backend=backend) @@ -246,6 +257,8 @@ def test_texts(backend: str): assert layer.rotation == 10 layer.color = "red" layer.family = "Arial" + canvas.autoscale() + canvas.add_text(0, 0, "Hello, World!") def test_with_text(backend: str): @@ -270,6 +283,7 @@ def test_with_text(backend: str): canvas.add_bars(x, y).with_yerr(y/4).with_text([f"{i}" for i in range(10)]) canvas.add_bars(x, y).with_xerr(y/4).with_text("{x:1f}, {y:1f},") canvas.add_bars(x, y).with_yerr(y/4).with_text("{x:1f}, {y:1f}") + canvas.autoscale() def test_rug(backend: str): canvas = new_canvas(backend=backend) @@ -289,6 +303,7 @@ def test_rug(backend: str): layer.high = 1.5 assert np.allclose(layer.low, 0.5) assert np.allclose(layer.high, 1.5) + canvas.autoscale(xpad=(0.01, 0.02), ypad=(0.01, 0.02)) def test_spans(backend: str): @@ -308,3 +323,4 @@ def test_spans(backend: str): if backend != "vispy": canvas.add_legend() + canvas.autoscale(xpad=0.01, ypad=0.01) diff --git a/whitecanvas/backend/_instance.py b/whitecanvas/backend/_instance.py index 5b2b6ef5..166da06c 100644 --- a/whitecanvas/backend/_instance.py +++ b/whitecanvas/backend/_instance.py @@ -46,6 +46,11 @@ def __init__(self, name: Backend | str | None = None) -> None: def __repr__(self) -> str: return f"" + def __eq__(self, other) -> bool: + if not isinstance(other, Backend): + return False + return self._name == other._name and self._app == other._app + @property def name(self) -> str: """Name of the backend.""" diff --git a/whitecanvas/backend/bokeh/_labels.py b/whitecanvas/backend/bokeh/_labels.py index 78f137f1..d38d4821 100644 --- a/whitecanvas/backend/bokeh/_labels.py +++ b/whitecanvas/backend/bokeh/_labels.py @@ -152,7 +152,7 @@ def _plt_get_axis(self) -> BokehAxis: raise NotImplementedError def _plt_get_tick_labels(self) -> tuple[list[float], list[str]]: - return tuple(zip(*self._plt_get_axis().ticker)) + return tuple(zip(*self._plt_get_axis().ticker.ticks)) def _plt_override_labels(self, pos: list[float], labels: list[str]): self._plt_get_axis().ticker = pos @@ -160,7 +160,7 @@ def _plt_override_labels(self, pos: list[float], labels: list[str]): def _plt_reset_override(self): self._plt_get_axis().ticker = [] - self._plt_get_axis().major_label_overrides = None + self._plt_get_axis().major_label_overrides = {} def _plt_get_visible(self) -> bool: return self._visible diff --git a/whitecanvas/backend/mock/canvas.py b/whitecanvas/backend/mock/canvas.py index 6a43de38..f8a66c44 100644 --- a/whitecanvas/backend/mock/canvas.py +++ b/whitecanvas/backend/mock/canvas.py @@ -19,8 +19,6 @@ def __init__(self): self._xaxis = Axis() self._yaxis = Axis() self._title = Title() - self._xlabel = Label() - self._ylabel = Label() self._xticks = Ticks() self._yticks = Ticks() self._aspect_ratio = None @@ -40,10 +38,10 @@ def _plt_get_yaxis(self): return self._yaxis def _plt_get_xlabel(self): - return self._xlabel + return self._xaxis._label def _plt_get_ylabel(self): - return self._ylabel + return self._yaxis._label def _plt_get_xticks(self): return self._xticks @@ -191,6 +189,7 @@ def __init__(self): self._limits = (0, 1) self._flipped = False self._color = np.array([0, 0, 0, 1], dtype=np.float32) + self._label = Label() def _plt_get_visible(self) -> bool: return self._visible @@ -216,6 +215,9 @@ def _plt_get_limits(self) -> tuple[float, float]: def _plt_set_limits(self, limits: tuple[float, float]): self._limits = limits + def _plt_set_grid_state(self, *args, **kwargs): + pass + class Ticks(_SupportsText): def __init__(self): diff --git a/whitecanvas/backend/plotly/_base.py b/whitecanvas/backend/plotly/_base.py index 8408ffc2..027ad417 100644 --- a/whitecanvas/backend/plotly/_base.py +++ b/whitecanvas/backend/plotly/_base.py @@ -93,6 +93,15 @@ def asdict(self): out["secondary_y"] = True return out + def asdictn(self): + out = {} + if self.row > 1 or self.col > 1: + out["rows"] = self.row + out["cols"] = self.col + if self.secondary_y: + out["secondary_y"] = True + return out + _LINE_STYLES = { "solid": LineStyle.SOLID, diff --git a/whitecanvas/backend/plotly/_labels.py b/whitecanvas/backend/plotly/_labels.py index 1a7c7ce0..a6a91833 100644 --- a/whitecanvas/backend/plotly/_labels.py +++ b/whitecanvas/backend/plotly/_labels.py @@ -137,7 +137,7 @@ def _plt_get_axis(self): def _plt_get_tick_labels(self) -> tuple[list[float], list[str]]: return ( self._plt_get_axis().tickvals, - self._plt_get_axis().ticktext, + list(self._plt_get_axis().ticktext), ) def _plt_override_labels(self, pos: list[float], labels: list[str]): diff --git a/whitecanvas/backend/plotly/canvas.py b/whitecanvas/backend/plotly/canvas.py index 9a1833fb..06cdcfa6 100644 --- a/whitecanvas/backend/plotly/canvas.py +++ b/whitecanvas/backend/plotly/canvas.py @@ -211,7 +211,7 @@ def _plt_make_legend( sample.name = label plotly_traces.append(sample) legend_kwargs = _LEGEND_KWARGS[anchor] - self._fig.add_traces(plotly_traces, **self._loc.asdict()) + self._fig.add_traces(plotly_traces, **self._loc.asdictn()) self._fig.update_layout(showlegend=True, legend=legend_kwargs, overwrite=True) def _repr_mimebundle_(self, *args, **kwargs): diff --git a/whitecanvas/backend/vispy/canvas.py b/whitecanvas/backend/vispy/canvas.py index 89f0354c..35b1d610 100644 --- a/whitecanvas/backend/vispy/canvas.py +++ b/whitecanvas/backend/vispy/canvas.py @@ -91,7 +91,7 @@ def _set_scene_ref(self, scene): self._viewbox.freeze() def _plt_get_native(self): - return self._viewbox.scene + return self._outer_viewbox def _plt_get_title(self): return self._title diff --git a/whitecanvas/backend/vispy/markers.py b/whitecanvas/backend/vispy/markers.py index 25558723..c0170dab 100644 --- a/whitecanvas/backend/vispy/markers.py +++ b/whitecanvas/backend/vispy/markers.py @@ -59,6 +59,8 @@ def _plt_get_symbol(self) -> Symbol: return Symbol.DIAMOND elif sym == "-": return Symbol.HBAR + elif sym is None: + return Symbol.CIRCLE return Symbol(sym) def _plt_set_symbol(self, symbol: Symbol): diff --git a/whitecanvas/canvas/dataframe/_joint_cat.py b/whitecanvas/canvas/dataframe/_joint_cat.py index 341d8905..b1bd6cae 100644 --- a/whitecanvas/canvas/dataframe/_joint_cat.py +++ b/whitecanvas/canvas/dataframe/_joint_cat.py @@ -9,7 +9,7 @@ from whitecanvas.canvas.dataframe._base import BaseCatPlotter from whitecanvas.layers import tabular as _lt from whitecanvas.layers.tabular import _jitter -from whitecanvas.types import ColormapType, HistBinType +from whitecanvas.types import HistBinType if TYPE_CHECKING: from whitecanvas.canvas import JointGrid @@ -115,20 +115,19 @@ def add_markers( def add_hist2d( self, *, - cmap: ColormapType = "inferno", + color: str | Sequence[str] | None = None, name: str | None = None, bins: HistBinType | tuple[HistBinType, HistBinType] = "auto", rangex: tuple[float, float] | None = None, rangey: tuple[float, float] | None = None, - density: bool = False, ) -> _lt.DFHeatmap[_DF]: """ Add 2-D histogram of given x/y columns. Parameters ---------- - cmap : colormap-like, default "inferno" - Colormap to use for the heatmap. + color : str or sequence of str, optional + Column name(s) for coloring the heatmaps. Must be categorical. name : str, optional Name of the layer. bins : int, array, str or tuple of them, default "auto" @@ -138,9 +137,6 @@ def add_hist2d( Range of x values in which histogram will be built. rangey : (float, float), optional Range of y values in which histogram will be built. - density : bool, default False - If True, the result is the value of the probability density function at the - bin, normalized such that the integral over the range is 1. Returns ------- @@ -149,9 +145,9 @@ def add_hist2d( """ grid = self._canvas() main = grid.main_canvas - layer = _lt.DFHeatmap.build_hist( - self._df, self._get_x(), self._get_y(), cmap=cmap, name=name, bins=bins, - range=(rangex, rangey), density=density, backend=grid._backend, + layer = _lt.DFMultiHeatmap.build_hist( + self._df, self._get_x(), self._get_y(), color=color, name=name, bins=bins, + range=(rangex, rangey), backend=grid._backend, ) # fmt: skip main.add_layer(layer) for _x_plt in grid._iter_x_plotters(): diff --git a/whitecanvas/core.py b/whitecanvas/core.py index 4bad02b4..a7b4427b 100644 --- a/whitecanvas/core.py +++ b/whitecanvas/core.py @@ -175,13 +175,13 @@ def wrap_canvas(obj: Any, palette=None) -> Canvas: if not isinstance(obj, Figure): raise TypeError(f"Expected plotly Figure, got {typ}") backend = "plotly" - elif _is_in_module(typ, "bokeh", "Plot"): - from bokeh.models import Plot + elif _is_in_module(typ, "bokeh", "figure"): + from bokeh.plotting import figure from whitecanvas.backend.bokeh import Canvas as BackendCanvas - if not isinstance(obj, Plot): - raise TypeError(f"Expected bokeh Plot, got {typ}") + if not isinstance(obj, figure): + raise TypeError(f"Expected bokeh figure, got {typ}") backend = "bokeh" elif _is_in_module(typ, "vispy", "ViewBox"): from vispy.scene import ViewBox @@ -191,13 +191,13 @@ def wrap_canvas(obj: Any, palette=None) -> Canvas: if not isinstance(obj, ViewBox): raise TypeError(f"Expected vispy ViewBox, got {typ}") backend = "vispy" - elif _is_in_module(typ, "pyqtgraph", "ViewBox"): - from pyqtgraph import ViewBox + elif _is_in_module(typ, "pyqtgraph", "PlotItem"): + from pyqtgraph import PlotItem from whitecanvas.backend.pyqtgraph import Canvas as BackendCanvas - if not isinstance(obj, ViewBox): - raise TypeError(f"Expected pyqtgraph ViewBox, got {typ}") + if not isinstance(obj, PlotItem): + raise TypeError(f"Expected pyqtgraph PlotItem, got {typ}") backend = "pyqtgraph" else: raise TypeError(f"Cannot convert {typ} to Canvas") diff --git a/whitecanvas/layers/group/band_collection.py b/whitecanvas/layers/group/band_collection.py index 778cfc95..ed351b43 100644 --- a/whitecanvas/layers/group/band_collection.py +++ b/whitecanvas/layers/group/band_collection.py @@ -3,7 +3,7 @@ from typing import Any, Iterable, Literal import numpy as np -from numpy.typing import ArrayLike, NDArray +from numpy.typing import ArrayLike from whitecanvas.backend import Backend from whitecanvas.layers._mixin import CollectionFaceEdgeMixin @@ -60,31 +60,6 @@ def orient(self) -> Orientation: """Orientation of the bands.""" return self._orient - @classmethod - def from_arrays( - cls, - y: list[float], - data: list[XYYData], - *, - band_width: float | None = None, - name: str | None = None, - orient: Orientation = Orientation.VERTICAL, - backend: str | Backend | None = None, - ): - from whitecanvas.utils.kde import gaussian_kde - - input_ = [] - for bottom, each in zip(y, data): - _each = as_array_1d(each) - kde = gaussian_kde(_each, bw_method=band_width) - sigma = np.sqrt(kde.covariance[0, 0]) - pad = sigma * 2.5 - x = np.linspace(_each.min() - pad, _each.max() + pad, 100) - y1 = kde(x) - y0 = np.full(y1.size, bottom) - input_.append(XYYData(x, y0, y1)) - return cls(input_, name=name, orient=orient.transpose(), backend=backend) - def with_hover_text(self, text: str | Iterable[Any]): """Set hover text for each band.""" if isinstance(text, str): @@ -150,27 +125,6 @@ def from_arrays( backend=backend, ) - @property - def offsets(self) -> NDArray[np.floating]: - if self._shape == "both": - - def _getter(x: XYYData): - return (x.y0[0] + x.y0[1]) / 2 - - elif self._shape == "left": - - def _getter(x: XYYData): - return x.y1[0] - - elif self._shape == "right": - - def _getter(x: XYYData): - return x.y0[0] - - else: - raise ValueError(self._shape) - return np.array([_getter(band.data) for band in self]) - @property def orient(self) -> Orientation: """Orientation of the violin plot (perpendicular to the fill orientation).""" @@ -197,55 +151,6 @@ def extent(self, width: float): band.set_data(bd.x, y0, y1) self._extent = width - def set_datasets( - self, - offsets: list[float] | None = None, - dataset: list[np.ndarray] | None = None, - kde_band_width: float | str = "scott", - ): - from whitecanvas.utils.kde import gaussian_kde - - if offsets is None: - _offsets = self.offsets - else: - _offsets = offsets - if dataset is None: - raise NotImplementedError - if len(offsets) != len(dataset): - raise ValueError("Length mismatch.") - xyy_values: list[XYYData] = [] - for offset, values in zip(_offsets, dataset): - arr = as_array_1d(values) - kde = gaussian_kde(arr, bw_method=kde_band_width) - - sigma = np.sqrt(kde.covariance[0, 0]) - pad = sigma * 2.5 - x_ = np.linspace(arr.min() - pad, arr.max() + pad, 100) - y = kde(x_) - if self._shape in ("both", "left"): - y0 = -y + offset - else: - y0 = np.zeros_like(y) + offset - if self._shape in ("both", "right"): - y1 = y + offset - else: - y1 = np.zeros_like(y) + offset - - data = XYYData(x_, y0, y1) - xyy_values.append(data) - - half_widths: list[float] = [] - for xyy in xyy_values: - half_width = np.max(np.abs(xyy.ydiff)) - if self._shape == "both": - half_width /= 2 - half_widths.append(half_width) - factor = self.extent / np.max(half_widths) / 2 - for xyy, xoffset, band in zip(xyy_values, _offsets, self): - y0 = (xyy.y0 - xoffset) * factor + xoffset - y1 = (xyy.y1 - xoffset) * factor + xoffset - band.data = XYYData(xyy.x, y0, y1) - @staticmethod def _convert_data( x: list[float],