Skip to content

Commit 4174937

Browse files
feat(bokeh): implement subplot-grid (#2826)
## Implementation: `subplot-grid` - bokeh Implements the **bokeh** version of `subplot-grid`. **File:** `plots/subplot-grid/implementations/bokeh.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20602449203)* --------- 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 2874980 commit 4174937

2 files changed

Lines changed: 195 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
""" pyplots.ai
2+
subplot-grid: Subplot Grid Layout
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from bokeh.io import export_png
10+
from bokeh.layouts import gridplot
11+
from bokeh.models import ColumnDataSource, Title
12+
from bokeh.plotting import figure, output_file, save
13+
14+
15+
# Data - Financial dashboard with multiple metrics
16+
np.random.seed(42)
17+
18+
# Time series data for price and volume (trading days)
19+
n_days = 60
20+
dates = pd.date_range("2024-01-01", periods=n_days, freq="B")
21+
date_strings = [d.strftime("%b %d") for d in dates]
22+
23+
# Price data (cumulative returns creating realistic price movement)
24+
returns = np.random.normal(0.001, 0.02, n_days)
25+
price = 100 * np.cumprod(1 + returns)
26+
27+
# Volume data (with some correlation to price movement)
28+
base_volume = np.random.uniform(0.8, 1.2, n_days) * 1_000_000
29+
volume = base_volume * (1 + np.abs(returns) * 10)
30+
31+
# Scatter data for risk vs return
32+
n_assets = 40
33+
asset_returns = np.random.normal(8, 4, n_assets)
34+
asset_risk = np.abs(asset_returns) * 0.3 + np.random.uniform(2, 8, n_assets)
35+
36+
# Histogram data - daily returns distribution
37+
daily_returns = np.random.normal(0.1, 2.5, 200)
38+
39+
# Colors
40+
python_blue = "#306998"
41+
python_yellow = "#FFD43B"
42+
accent_green = "#2E7D32"
43+
accent_red = "#C62828"
44+
45+
# ========== SUBPLOT 1: Price Line Chart (top-left) ==========
46+
source_price = ColumnDataSource(data={"x": list(range(n_days)), "y": price, "date": date_strings})
47+
48+
p1 = figure(
49+
width=2400,
50+
height=1350,
51+
title="Stock Price Over Time",
52+
x_axis_label="Trading Day",
53+
y_axis_label="Price ($)",
54+
tools="",
55+
toolbar_location=None,
56+
)
57+
p1.line("x", "y", source=source_price, line_width=4, color=python_blue, alpha=0.9)
58+
p1.scatter("x", "y", source=source_price, size=8, color=python_blue, alpha=0.6)
59+
60+
# Styling for p1
61+
p1.title.text_font_size = "24pt"
62+
p1.xaxis.axis_label_text_font_size = "20pt"
63+
p1.yaxis.axis_label_text_font_size = "20pt"
64+
p1.xaxis.major_label_text_font_size = "16pt"
65+
p1.yaxis.major_label_text_font_size = "16pt"
66+
p1.xaxis.major_label_orientation = 0.7
67+
p1.grid.grid_line_alpha = 0.3
68+
p1.grid.grid_line_dash = "dashed"
69+
70+
# ========== SUBPLOT 2: Volume Bar Chart (top-right) ==========
71+
source_volume = ColumnDataSource(data={"x": list(range(n_days)), "y": volume / 1_000_000, "date": date_strings})
72+
73+
p2 = figure(
74+
width=2400,
75+
height=1350,
76+
title="Daily Trading Volume",
77+
x_axis_label="Trading Day",
78+
y_axis_label="Volume (Millions)",
79+
tools="",
80+
toolbar_location=None,
81+
)
82+
p2.vbar(x="x", top="y", source=source_volume, width=0.7, color=python_yellow, alpha=0.8)
83+
84+
# Styling for p2
85+
p2.title.text_font_size = "24pt"
86+
p2.xaxis.axis_label_text_font_size = "20pt"
87+
p2.yaxis.axis_label_text_font_size = "20pt"
88+
p2.xaxis.major_label_text_font_size = "16pt"
89+
p2.yaxis.major_label_text_font_size = "16pt"
90+
p2.xaxis.major_label_orientation = 0.7
91+
p2.grid.grid_line_alpha = 0.3
92+
p2.grid.grid_line_dash = "dashed"
93+
94+
# ========== SUBPLOT 3: Risk vs Return Scatter (bottom-left) ==========
95+
# Color by performance (positive vs negative returns)
96+
colors = [accent_green if r > 8 else (accent_red if r < 5 else python_blue) for r in asset_returns]
97+
98+
source_scatter = ColumnDataSource(data={"x": asset_risk, "y": asset_returns, "color": colors})
99+
100+
p3 = figure(
101+
width=2400,
102+
height=1350,
103+
title="Risk vs Return Analysis",
104+
x_axis_label="Risk (Volatility %)",
105+
y_axis_label="Annual Return (%)",
106+
tools="",
107+
toolbar_location=None,
108+
)
109+
p3.scatter("x", "y", source=source_scatter, size=18, color="color", alpha=0.7)
110+
111+
# Styling for p3
112+
p3.title.text_font_size = "24pt"
113+
p3.xaxis.axis_label_text_font_size = "20pt"
114+
p3.yaxis.axis_label_text_font_size = "20pt"
115+
p3.xaxis.major_label_text_font_size = "16pt"
116+
p3.yaxis.major_label_text_font_size = "16pt"
117+
p3.grid.grid_line_alpha = 0.3
118+
p3.grid.grid_line_dash = "dashed"
119+
120+
# ========== SUBPLOT 4: Returns Distribution Histogram (bottom-right) ==========
121+
# Create histogram bins
122+
hist, edges = np.histogram(daily_returns, bins=25)
123+
124+
source_hist = ColumnDataSource(data={"top": hist, "left": edges[:-1], "right": edges[1:]})
125+
126+
p4 = figure(
127+
width=2400,
128+
height=1350,
129+
title="Daily Returns Distribution",
130+
x_axis_label="Daily Return (%)",
131+
y_axis_label="Frequency",
132+
tools="",
133+
toolbar_location=None,
134+
)
135+
p4.quad(
136+
top="top",
137+
bottom=0,
138+
left="left",
139+
right="right",
140+
source=source_hist,
141+
fill_color=python_blue,
142+
line_color="white",
143+
alpha=0.8,
144+
line_width=2,
145+
)
146+
147+
# Styling for p4
148+
p4.title.text_font_size = "24pt"
149+
p4.xaxis.axis_label_text_font_size = "20pt"
150+
p4.yaxis.axis_label_text_font_size = "20pt"
151+
p4.xaxis.major_label_text_font_size = "16pt"
152+
p4.yaxis.major_label_text_font_size = "16pt"
153+
p4.grid.grid_line_alpha = 0.3
154+
p4.grid.grid_line_dash = "dashed"
155+
156+
# ========== CREATE GRID LAYOUT ==========
157+
grid = gridplot([[p1, p2], [p3, p4]], merge_tools=False, toolbar_location=None)
158+
159+
# Add main title using the first plot's add_layout
160+
main_title = Title(text="subplot-grid · bokeh · pyplots.ai", text_font_size="32pt", align="center")
161+
p1.add_layout(main_title, "above")
162+
163+
# Save as PNG
164+
export_png(grid, filename="plot.png")
165+
166+
# Save as HTML for interactive viewing
167+
output_file("plot.html", title="subplot-grid · bokeh · pyplots.ai")
168+
save(grid)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: bokeh
2+
specification_id: subplot-grid
3+
created: '2025-12-30T17:50:32Z'
4+
updated: '2025-12-30T18:03:48Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20602449203
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/subplot-grid/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/subplot-grid/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/subplot-grid/bokeh/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of a cohesive financial dashboard demonstrating diverse
17+
plot types in a grid
18+
- Clean code structure with good use of ColumnDataSource for all subplots
19+
- Effective color scheme using Python blue/yellow with meaningful semantic colors
20+
for risk categories
21+
- Proper dual output (PNG and HTML) for both static and interactive viewing
22+
- Well-organized data generation with realistic financial relationships
23+
weaknesses:
24+
- Main title placement is unconventional (added to first subplot rather than as
25+
true grid title) causing slight visual asymmetry
26+
- Individual subplot dimensions (2400x1350 each) result in a total grid size that
27+
may not match the target 4800x2700 exactly

0 commit comments

Comments
 (0)