Skip to content

Commit f7d239a

Browse files
feat(altair): implement subplot-mosaic (#3026)
## Implementation: `subplot-mosaic` - altair Implements the **altair** version of `subplot-mosaic`. **File:** `plots/subplot-mosaic/implementations/altair.py` **Parent Issue:** #3002 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20617510407)* --------- 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 20d9d76 commit f7d239a

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
""" pyplots.ai
2+
subplot-mosaic: Mosaic Subplot Layout with Varying Sizes
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data - Create diverse datasets for dashboard-style mosaic layout
13+
np.random.seed(42)
14+
15+
# Panel A: Wide time series (top, spanning 2 columns)
16+
dates = pd.date_range("2024-01-01", periods=100, freq="D")
17+
df_timeseries = pd.DataFrame(
18+
{"date": dates, "value": np.cumsum(np.random.randn(100)) + 50, "category": "Revenue Trend"}
19+
)
20+
21+
# Panel B: Small metric (top right corner)
22+
df_gauge = pd.DataFrame({"metric": ["Current"], "value": [78], "max_value": [100]})
23+
24+
# Panel C: Bar chart (middle left)
25+
df_bars = pd.DataFrame({"region": ["North", "South", "East", "West", "Central"], "sales": [45, 38, 52, 29, 41]})
26+
27+
# Panel D: Scatter plot (middle right, spanning 2 rows)
28+
n_points = 80
29+
df_scatter = pd.DataFrame(
30+
{
31+
"efficiency": np.random.uniform(60, 95, n_points),
32+
"output": np.random.uniform(100, 500, n_points) + np.random.uniform(60, 95, n_points) * 3,
33+
"size": np.random.uniform(20, 100, n_points),
34+
}
35+
)
36+
37+
# Panel E: Small bar chart (bottom left)
38+
df_categories = pd.DataFrame({"type": ["Type A", "Type B", "Type C"], "count": [24, 18, 31]})
39+
40+
# Panel F: Small area chart (bottom middle)
41+
df_area = pd.DataFrame(
42+
{
43+
"hour": list(range(24)),
44+
"traffic": [10, 8, 5, 4, 6, 15, 35, 55, 48, 42, 38, 45, 50, 48, 52, 60, 65, 55, 40, 30, 25, 20, 15, 12],
45+
}
46+
)
47+
48+
# Note: Data is small enough to not need max_rows configuration
49+
50+
# Create individual charts with proper styling
51+
52+
# Chart A: Wide time series (spans 2 columns at top)
53+
chart_a = (
54+
alt.Chart(df_timeseries)
55+
.mark_line(strokeWidth=3, color="#306998")
56+
.encode(
57+
x=alt.X("date:T", title="Date", axis=alt.Axis(labelFontSize=14, titleFontSize=16)),
58+
y=alt.Y("value:Q", title="Revenue ($K)", axis=alt.Axis(labelFontSize=14, titleFontSize=16)),
59+
)
60+
.properties(width=700, height=200, title=alt.Title("Monthly Revenue Overview", fontSize=20))
61+
)
62+
63+
# Chart B: Gauge-style metric (small, top right)
64+
chart_b_bg = (
65+
alt.Chart(df_gauge).mark_arc(innerRadius=50, outerRadius=80, theta=3.14159, theta2=0, color="#E0E0E0").encode()
66+
)
67+
68+
chart_b_value = (
69+
alt.Chart(df_gauge)
70+
.mark_arc(
71+
innerRadius=50,
72+
outerRadius=80,
73+
theta=3.14159,
74+
theta2=alt.expr("3.14159 - (datum.value / datum.max_value) * 3.14159"),
75+
color="#306998",
76+
)
77+
.encode()
78+
)
79+
80+
chart_b_text = (
81+
alt.Chart(df_gauge)
82+
.mark_text(fontSize=28, fontWeight="bold", color="#306998")
83+
.encode(text=alt.Text("value:Q", format=".0f"))
84+
)
85+
86+
chart_b = alt.layer(chart_b_bg, chart_b_value, chart_b_text).properties(
87+
width=200, height=200, title=alt.Title("Performance Score", fontSize=18)
88+
)
89+
90+
# Chart C: Bar chart (middle left)
91+
chart_c = (
92+
alt.Chart(df_bars)
93+
.mark_bar(color="#306998", cornerRadiusTopLeft=4, cornerRadiusTopRight=4)
94+
.encode(
95+
x=alt.X("region:N", title="Region", axis=alt.Axis(labelFontSize=14, titleFontSize=16, labelAngle=0)),
96+
y=alt.Y("sales:Q", title="Sales ($K)", axis=alt.Axis(labelFontSize=14, titleFontSize=16)),
97+
)
98+
.properties(width=280, height=180, title=alt.Title("Sales by Region", fontSize=18))
99+
)
100+
101+
# Chart D: Scatter plot (spans 2 rows on right side)
102+
chart_d = (
103+
alt.Chart(df_scatter)
104+
.mark_circle(opacity=0.7)
105+
.encode(
106+
x=alt.X(
107+
"efficiency:Q",
108+
title="Efficiency (%)",
109+
scale=alt.Scale(domain=[55, 100]),
110+
axis=alt.Axis(labelFontSize=14, titleFontSize=16),
111+
),
112+
y=alt.Y("output:Q", title="Output (units)", axis=alt.Axis(labelFontSize=14, titleFontSize=16)),
113+
size=alt.Size("size:Q", scale=alt.Scale(range=[50, 300]), legend=None),
114+
color=alt.Color("efficiency:Q", scale=alt.Scale(scheme="blues"), legend=None),
115+
)
116+
.properties(width=320, height=400, title=alt.Title("Efficiency vs Output", fontSize=18))
117+
)
118+
119+
# Chart E: Small bar chart (bottom left)
120+
chart_e = (
121+
alt.Chart(df_categories)
122+
.mark_bar(color="#FFD43B")
123+
.encode(
124+
x=alt.X("type:N", title=None, axis=alt.Axis(labelFontSize=12, labelAngle=0)),
125+
y=alt.Y("count:Q", title="Count", axis=alt.Axis(labelFontSize=12, titleFontSize=14)),
126+
)
127+
.properties(width=180, height=160, title=alt.Title("By Category", fontSize=16))
128+
)
129+
130+
# Chart F: Area chart (bottom middle)
131+
chart_f = (
132+
alt.Chart(df_area)
133+
.mark_area(color="#306998", opacity=0.7, line={"color": "#306998", "strokeWidth": 2})
134+
.encode(
135+
x=alt.X("hour:Q", title="Hour", axis=alt.Axis(labelFontSize=12, titleFontSize=14)),
136+
y=alt.Y("traffic:Q", title="Traffic", axis=alt.Axis(labelFontSize=12, titleFontSize=14)),
137+
)
138+
.properties(width=200, height=160, title=alt.Title("Daily Traffic Pattern", fontSize=16))
139+
)
140+
141+
# Build mosaic layout using Altair's concatenation
142+
# Layout pattern: "AAB"
143+
# "CDD"
144+
# "EFD"
145+
146+
# Top row: time series (wide) + gauge
147+
top_row = alt.hconcat(chart_a, chart_b, spacing=20)
148+
149+
# Middle row: bars + scatter (scatter spans into bottom row via vconcat)
150+
# Bottom row: categories + area (scatter continues from middle)
151+
152+
# Left column for middle and bottom rows
153+
left_middle = chart_c
154+
left_bottom = alt.hconcat(chart_e, chart_f, spacing=15)
155+
left_column = alt.vconcat(left_middle, left_bottom, spacing=15)
156+
157+
# Right side is the tall scatter plot
158+
middle_bottom_row = alt.hconcat(left_column, chart_d, spacing=20)
159+
160+
# Combine all rows
161+
mosaic = (
162+
alt.vconcat(top_row, middle_bottom_row, spacing=25)
163+
.properties(title=alt.Title("subplot-mosaic · altair · pyplots.ai", fontSize=28, anchor="middle", offset=20))
164+
.configure(background="white")
165+
.configure_view(strokeWidth=0)
166+
)
167+
168+
# Save outputs
169+
mosaic.save("plot.png", scale_factor=3.0)
170+
mosaic.save("plot.html")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: altair
2+
specification_id: subplot-mosaic
3+
created: '2025-12-31T10:56:12Z'
4+
updated: '2025-12-31T11:07:48Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20617510407
7+
issue: 3002
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/subplot-mosaic/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/subplot-mosaic/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/subplot-mosaic/altair/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent demonstration of mosaic layout concept using Altair concatenation operators
17+
(hconcat, vconcat)
18+
- Clean, professional visual design with consistent color scheme (#306998 blue theme)
19+
- Good variety of chart types (line, bar, scatter, area, gauge) showing layout flexibility
20+
- Descriptive axis labels with appropriate units throughout
21+
- Realistic business dashboard scenario with plausible data
22+
weaknesses:
23+
- No demonstration of empty cells using placeholder characters (e.g., "." for gaps)
24+
as mentioned in spec
25+
- Scatter plot lacks legend for the color encoding (efficiency gradient)
26+
- HTML version could benefit from interactive features (tooltips, selection) which
27+
Altair excels at

0 commit comments

Comments
 (0)