|
1 | 1 | """ pyplots.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import altair as alt |
8 | 8 | import numpy as np |
9 | 9 | import pandas as pd |
10 | 10 |
|
11 | 11 |
|
12 | | -# Data - Time series with 95% confidence interval |
| 12 | +# Data - Oscilloscope voltage measurement with growing uncertainty |
13 | 13 | np.random.seed(42) |
14 | 14 | x = np.linspace(0, 10, 100) |
15 | 15 | y_center = 2 * np.sin(x) + 0.5 * x # Central trend (sinusoidal + linear growth) |
16 | 16 |
|
17 | | -# Confidence band widens over time (realistic uncertainty growth) |
| 17 | +# Confidence band widens over time (realistic sensor drift uncertainty) |
18 | 18 | uncertainty = 0.5 + 0.15 * x |
19 | 19 | y_lower = y_center - 1.96 * uncertainty |
20 | 20 | 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 |
21 | 23 |
|
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 | +) |
23 | 46 |
|
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 = ( |
26 | 52 | 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 | + ) |
29 | 57 | ) |
30 | 58 |
|
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 | +) |
33 | 106 |
|
34 | | -# Combine band and line |
| 107 | +# Combine layers |
35 | 108 | chart = ( |
36 | | - (band + line) |
| 109 | + (band_outer + band_inner + line + ann_rule + ann_text + tooltip_points + guide_rule) |
37 | 110 | .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") |
39 | 112 | .configure_view(strokeWidth=0) |
40 | 113 | ) |
41 | 114 |
|
|
0 commit comments