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