Skip to content

Commit fd200ac

Browse files
feat(altair): implement line-confidence (#2253)
## Implementation: `line-confidence` - altair Implements the **altair** version of `line-confidence`. **File:** `plots/line-confidence/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20525434309)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0a70bc3 commit fd200ac

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
""" pyplots.ai
2+
line-confidence: Line Plot with Confidence Interval
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 78/100 | Created: 2025-12-26
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data: Simulating a time series forecast with 95% confidence interval
13+
np.random.seed(42)
14+
15+
# Generate time points (50 days of daily data)
16+
n_points = 50
17+
days = np.arange(n_points)
18+
19+
# Create a trend with some curvature (e.g., a model prediction)
20+
trend = 100 + 0.5 * days + 0.02 * days**2 + np.sin(days / 5) * 5
21+
22+
# Add noise for the central line
23+
noise = np.random.normal(0, 3, n_points)
24+
y_mean = trend + noise
25+
26+
# Confidence interval that widens over time (typical for forecasts)
27+
uncertainty = 5 + 0.15 * days # Uncertainty grows with time
28+
y_lower = y_mean - 1.96 * uncertainty / 2
29+
y_upper = y_mean + 1.96 * uncertainty / 2
30+
31+
# Create DataFrame
32+
df = pd.DataFrame({"Day": days, "Value": y_mean, "Lower": y_lower, "Upper": y_upper})
33+
34+
# Calculate appropriate y-axis domain with padding (avoid unnecessary whitespace from 0)
35+
y_min = df["Lower"].min()
36+
y_max = df["Upper"].max()
37+
y_padding = (y_max - y_min) * 0.1
38+
y_domain = [y_min - y_padding, y_max + y_padding]
39+
40+
# Create shared y-scale for consistent rendering across all layers
41+
y_scale = alt.Scale(domain=y_domain)
42+
43+
# Create the confidence band (area) with fill encoding for legend
44+
band = (
45+
alt.Chart(df)
46+
.transform_calculate(legend_label='"95% Confidence Interval"')
47+
.mark_area(opacity=0.3)
48+
.encode(
49+
x=alt.X("Day:Q", title="Day", axis=alt.Axis(values=list(range(0, 51, 5)))),
50+
y=alt.Y("Lower:Q", title="Predicted Value", scale=y_scale),
51+
y2="Upper:Q",
52+
fill=alt.Fill(
53+
"legend_label:N",
54+
scale=alt.Scale(domain=["95% Confidence Interval"], range=["#306998"]),
55+
legend=alt.Legend(
56+
title="Legend",
57+
orient="right",
58+
labelFontSize=16,
59+
titleFontSize=18,
60+
symbolType="square",
61+
symbolSize=300,
62+
symbolOpacity=0.3,
63+
),
64+
),
65+
)
66+
)
67+
68+
# Create the central line with stroke encoding for legend
69+
line = (
70+
alt.Chart(df)
71+
.transform_calculate(legend_label='"Predicted Mean"')
72+
.mark_line(strokeWidth=4)
73+
.encode(
74+
x="Day:Q",
75+
y=alt.Y("Value:Q", scale=y_scale),
76+
stroke=alt.Stroke(
77+
"legend_label:N",
78+
scale=alt.Scale(domain=["Predicted Mean"], range=["#306998"]),
79+
legend=alt.Legend(
80+
title="Legend", orient="right", labelFontSize=16, titleFontSize=18, symbolStrokeWidth=4, symbolSize=300
81+
),
82+
),
83+
)
84+
)
85+
86+
# Add point markers on the line for clarity (increased size for better visibility)
87+
points = (
88+
alt.Chart(df)
89+
.mark_point(size=100, filled=True, color="#306998")
90+
.encode(x="Day:Q", y=alt.Y("Value:Q", scale=y_scale))
91+
)
92+
93+
# Combine band, line, and points with resolved legends
94+
chart = (
95+
alt.layer(band, line, points)
96+
.resolve_legend(fill="independent", stroke="independent")
97+
.properties(
98+
width=1600, height=900, title=alt.Title("line-confidence · altair · pyplots.ai", fontSize=28, anchor="middle")
99+
)
100+
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
101+
.configure_view(strokeWidth=0)
102+
)
103+
104+
# Save as PNG (scale_factor=3 gives 4800x2700)
105+
chart.save("plot.png", scale_factor=3.0)
106+
107+
# Save as HTML for interactivity
108+
interactive_band = (
109+
alt.Chart(df)
110+
.mark_area(opacity=0.3, color="#306998")
111+
.encode(
112+
x=alt.X("Day:Q", title="Day"),
113+
y=alt.Y("Lower:Q", title="Predicted Value", scale=y_scale),
114+
y2="Upper:Q",
115+
tooltip=[
116+
alt.Tooltip("Day:Q", title="Day"),
117+
alt.Tooltip("Lower:Q", title="Lower Bound", format=".1f"),
118+
alt.Tooltip("Upper:Q", title="Upper Bound", format=".1f"),
119+
],
120+
)
121+
)
122+
123+
interactive_line = (
124+
alt.Chart(df).mark_line(strokeWidth=3, color="#306998").encode(x="Day:Q", y=alt.Y("Value:Q", scale=y_scale))
125+
)
126+
127+
interactive_points = (
128+
alt.Chart(df)
129+
.mark_point(size=60, filled=True, color="#306998")
130+
.encode(
131+
x="Day:Q",
132+
y=alt.Y("Value:Q", scale=y_scale),
133+
tooltip=[alt.Tooltip("Day:Q", title="Day"), alt.Tooltip("Value:Q", title="Predicted Value", format=".1f")],
134+
)
135+
)
136+
137+
interactive_chart = (
138+
alt.layer(interactive_band, interactive_line, interactive_points)
139+
.properties(
140+
width=800, height=450, title=alt.Title("line-confidence · altair · pyplots.ai", fontSize=20, anchor="middle")
141+
)
142+
.configure_axis(labelFontSize=14, titleFontSize=16, gridOpacity=0.3)
143+
.configure_view(strokeWidth=0)
144+
.interactive()
145+
)
146+
147+
interactive_chart.save("plot.html")
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: altair
2+
specification_id: line-confidence
3+
created: '2025-12-26T16:14:06Z'
4+
updated: '2025-12-26T16:33:33Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20525434309
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/line-confidence/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/line-confidence/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/line-confidence/altair/plot.html
13+
quality_score: 78
14+
review:
15+
strengths:
16+
- Clean code structure following KISS principles with clear data generation logic
17+
- Confidence band appropriately widens over time showing realistic forecast uncertainty
18+
behavior
19+
- Uses Altair layered chart composition effectively (band + line + points)
20+
- Interactive HTML export includes useful tooltips for exploration
21+
- Consistent color scheme with good contrast between line and band
22+
weaknesses:
23+
- Legend is not visible in the rendered PNG despite code attempting to create one
24+
using resolve_legend - this violates spec requirement for legend that clearly
25+
identifies both the central line and confidence band
26+
- Y-axis starts at 0 but data ranges ~90-180, wasting nearly half the vertical canvas
27+
space (the code calculates y_domain but Altair appears to ignore it or it is not
28+
applied correctly to all layers)

0 commit comments

Comments
 (0)