|
| 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") |
0 commit comments