Skip to content

Commit 752c0f5

Browse files
update(band-basic): altair — comprehensive quality review (#4353)
## Summary Updated **altair** implementation for **band-basic**. **Changes:** Comprehensive quality review — improved data context, axis labels, visual design. ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 005ec4a commit 752c0f5

2 files changed

Lines changed: 227 additions & 128 deletions

File tree

plots/band-basic/implementations/altair.py

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,114 @@
11
""" pyplots.ai
22
band-basic: Basic Band Plot
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: altair 6.0.0 | Python 3.14
4+
Quality: 94/100 | Updated: 2026-02-23
55
"""
66

77
import altair as alt
88
import numpy as np
99
import pandas as pd
1010

1111

12-
# Data - Time series with 95% confidence interval
12+
# Data - Oscilloscope voltage measurement with growing uncertainty
1313
np.random.seed(42)
1414
x = np.linspace(0, 10, 100)
1515
y_center = 2 * np.sin(x) + 0.5 * x # Central trend (sinusoidal + linear growth)
1616

17-
# Confidence band widens over time (realistic uncertainty growth)
17+
# Confidence band widens over time (realistic sensor drift uncertainty)
1818
uncertainty = 0.5 + 0.15 * x
1919
y_lower = y_center - 1.96 * uncertainty
2020
y_upper = y_center + 1.96 * uncertainty
21+
y_inner_lower = y_center - 0.674 * uncertainty # 50% CI inner band
22+
y_inner_upper = y_center + 0.674 * uncertainty
2123

22-
df = pd.DataFrame({"x": x, "y_center": y_center, "y_lower": y_lower, "y_upper": y_upper})
24+
df = pd.DataFrame(
25+
{
26+
"x": x,
27+
"y_center": y_center,
28+
"y_lower": y_lower,
29+
"y_upper": y_upper,
30+
"y_inner_lower": y_inner_lower,
31+
"y_inner_upper": y_inner_upper,
32+
}
33+
)
34+
35+
# Annotation data — callout at x≈9.0 s where uncertainty is wide and clearly visible
36+
ann_row = df.iloc[90] # x ≈ 9.09
37+
ann_df = pd.DataFrame(
38+
{
39+
"x": [ann_row["x"]],
40+
"y_upper": [ann_row["y_upper"]],
41+
"y_lower": [ann_row["y_lower"]],
42+
"y_mid": [(ann_row["y_upper"] + ann_row["y_lower"]) / 2],
43+
"label": [f"95% CI: ±{(ann_row['y_upper'] - ann_row['y_lower']) / 2:.1f} mV"],
44+
}
45+
)
2346

24-
# Band (area between y_lower and y_upper)
25-
band = (
47+
# Nearest-point selection for interactive HTML export
48+
nearest = alt.selection_point(nearest=True, on="pointerover", fields=["x"], empty=False)
49+
50+
# 95% confidence band (outer) — teal-shifted blue for visual depth against inner band
51+
band_outer = (
2652
alt.Chart(df)
27-
.mark_area(opacity=0.3, color="#306998")
28-
.encode(x=alt.X("x:Q", title="Time (s)"), y=alt.Y("y_lower:Q", title="Signal Amplitude"), y2=alt.Y2("y_upper:Q"))
53+
.mark_area(opacity=0.25, color="#4a8db7", interpolate="monotone")
54+
.encode(
55+
x=alt.X("x:Q", title="Time (s)"), y=alt.Y("y_lower:Q", title="Oscilloscope Signal (mV)"), y2=alt.Y2("y_upper:Q")
56+
)
2957
)
3058

31-
# Central trend line
32-
line = alt.Chart(df).mark_line(strokeWidth=4, color="#306998").encode(x="x:Q", y="y_center:Q")
59+
# 50% confidence band (inner) — deeper saturated blue for layered contrast
60+
band_inner = (
61+
alt.Chart(df)
62+
.mark_area(opacity=0.3, color="#306998", interpolate="monotone")
63+
.encode(x="x:Q", y="y_inner_lower:Q", y2="y_inner_upper:Q")
64+
)
65+
66+
# Central trend line (dark navy for strong focal contrast)
67+
line = alt.Chart(df).mark_line(strokeWidth=2.5, color="#1a3a5c", interpolate="monotone").encode(x="x:Q", y="y_center:Q")
68+
69+
# Annotation: vertical bracket showing uncertainty span
70+
ann_rule = (
71+
alt.Chart(ann_df)
72+
.mark_rule(color="#c0392b", strokeWidth=1.5, strokeDash=[6, 3])
73+
.encode(x="x:Q", y="y_lower:Q", y2="y_upper:Q")
74+
)
75+
76+
ann_text = (
77+
alt.Chart(ann_df)
78+
.mark_text(align="left", dx=10, fontSize=16, fontWeight="bold", color="#c0392b")
79+
.encode(x="x:Q", y="y_mid:Q", text="label:N")
80+
)
81+
82+
# Interactive tooltip points (visible only on hover in HTML)
83+
tooltip_points = (
84+
alt.Chart(df)
85+
.mark_point(color="#1a3a5c", size=80)
86+
.encode(
87+
x="x:Q",
88+
y="y_center:Q",
89+
opacity=alt.condition(nearest, alt.value(1), alt.value(0)),
90+
tooltip=[
91+
alt.Tooltip("x:Q", title="Time (s)", format=".1f"),
92+
alt.Tooltip("y_center:Q", title="Signal (mV)", format=".2f"),
93+
alt.Tooltip("y_lower:Q", title="95% CI Lower", format=".2f"),
94+
alt.Tooltip("y_upper:Q", title="95% CI Upper", format=".2f"),
95+
],
96+
)
97+
.add_params(nearest)
98+
)
99+
100+
# Vertical guide rule (visible only on hover in HTML)
101+
guide_rule = (
102+
alt.Chart(df)
103+
.mark_rule(color="#999999", strokeDash=[4, 4])
104+
.encode(x="x:Q", opacity=alt.condition(nearest, alt.value(0.5), alt.value(0)))
105+
)
33106

34-
# Combine band and line
107+
# Combine layers
35108
chart = (
36-
(band + line)
109+
(band_outer + band_inner + line + ann_rule + ann_text + tooltip_points + guide_rule)
37110
.properties(width=1600, height=900, title=alt.Title("band-basic · altair · pyplots.ai", fontSize=28))
38-
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
111+
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.15, gridColor="#cccccc")
39112
.configure_view(strokeWidth=0)
40113
)
41114

0 commit comments

Comments
 (0)