Source code for glyphx.fill_between
"""
GlyphX FillBetweenSeries — shaded area between two lines or between
a line and a baseline (zero or a constant).
from glyphx import Figure
from glyphx.fill_between import FillBetweenSeries
fig = Figure()
fig.add(FillBetweenSeries(x, y_lower, y_upper,
color="#2563eb", label="95% CI"))
fig.show()
"""
from __future__ import annotations
import numpy as np
from .series import BaseSeries, LineSeries
from .utils import svg_escape
[docs]
class FillBetweenSeries(BaseSeries):
"""
Shaded area between two Y arrays (or between a line and a baseline).
Args:
x: X values (shared by both bounds).
y1: Lower bound Y values, **or** the line when ``y2`` is a scalar.
y2: Upper bound Y values. Pass a scalar (e.g. ``0``) to shade
between ``y1`` and a horizontal baseline.
color: Fill and line color (default: ``"#1f77b4"``).
alpha: Fill opacity 0–1 (default: ``0.25``).
line_width: Width of the boundary lines in pixels. ``0`` hides them.
label: Legend label.
line_color: Color for boundary lines. Defaults to ``color``.
"""
def __init__(
self,
x,
y1,
y2,
color: str = "#1f77b4",
alpha: float = 0.25,
line_width: int = 1,
label: str | None = None,
line_color: str | None = None,
) -> None:
self.x1 = list(x)
self.y1 = list(y1)
# y2 can be a scalar baseline (e.g. 0) or a full array
if np.isscalar(y2):
self.y2 = [float(y2)] * len(self.y1)
else:
self.y2 = list(y2)
self.alpha = float(alpha)
self.line_width = int(line_width)
self.line_color = line_color or color
self.css_class = f"series-{id(self) % 100000}"
# Domain: x spans full range; y spans both bounds
all_y = self.y1 + self.y2
super().__init__(
x = self.x1,
y = all_y,
color = color,
label = label,
)
[docs]
def to_svg(self, ax: object, use_y2: bool = False) -> str:
scale_y = ax.scale_y2 if use_y2 else ax.scale_y # type: ignore[union-attr]
x_vals = getattr(self, "_numeric_x", self.x1)
# Build a closed polygon: trace y2 forward, then y1 backward
upper_pts = [f"{ax.scale_x(x)},{scale_y(y)}" for x, y in zip(x_vals, self.y2)] # type: ignore[union-attr]
lower_pts = [f"{ax.scale_x(x)},{scale_y(y)}" for x, y in reversed(list(zip(x_vals, self.y1)))] # type: ignore[union-attr]
polygon_points = " ".join(upper_pts + lower_pts)
elements = [
f'<polygon class="{self.css_class}" '
f'points="{polygon_points}" '
f'fill="{self.color}" fill-opacity="{self.alpha}" stroke="none"/>'
]
# Optional boundary lines
if self.line_width > 0:
for y_vals, label_sfx in [(self.y2, "-upper"), (self.y1, "-lower")]:
pts = " ".join(
f"{ax.scale_x(x)},{scale_y(y)}" # type: ignore[union-attr]
for x, y in zip(x_vals, y_vals)
)
elements.append(
f'<polyline class="{self.css_class}" fill="none" '
f'stroke="{self.line_color}" '
f'stroke-width="{self.line_width}" '
f'points="{pts}"/>'
)
return "\n".join(elements)