|
| 1 | +""" pyplots.ai |
| 2 | +subplot-grid: Subplot Grid Layout |
| 3 | +Library: plotly 6.5.0 | Python 3.13.11 |
| 4 | +Quality: 92/100 | Created: 2025-12-30 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +import pandas as pd |
| 9 | +import plotly.graph_objects as go |
| 10 | +from plotly.subplots import make_subplots |
| 11 | + |
| 12 | + |
| 13 | +# Data - Financial dashboard example |
| 14 | +np.random.seed(42) |
| 15 | + |
| 16 | +# Generate 60 days of stock data |
| 17 | +days = 60 |
| 18 | +dates = pd.date_range("2024-01-01", periods=days, freq="D") |
| 19 | + |
| 20 | +# Stock price with realistic walk |
| 21 | +returns = np.random.normal(0.001, 0.02, days) |
| 22 | +price = 100 * np.cumprod(1 + returns) |
| 23 | + |
| 24 | +# Volume data (correlated with absolute price movement) |
| 25 | +base_volume = 1000000 |
| 26 | +volume = base_volume + np.abs(returns) * 50000000 + np.random.normal(0, 200000, days) |
| 27 | +volume = np.clip(volume, 500000, 3000000) |
| 28 | + |
| 29 | +# Daily returns for histogram |
| 30 | +daily_returns = np.diff(price) / price[:-1] * 100 |
| 31 | + |
| 32 | +# Moving averages |
| 33 | +ma_20 = pd.Series(price).rolling(20).mean().values |
| 34 | + |
| 35 | +# Create 2x2 subplot grid |
| 36 | +fig = make_subplots( |
| 37 | + rows=2, |
| 38 | + cols=2, |
| 39 | + subplot_titles=("Stock Price & Moving Average", "Trading Volume", "Daily Returns Distribution", "Price vs Volume"), |
| 40 | + horizontal_spacing=0.1, |
| 41 | + vertical_spacing=0.12, |
| 42 | + specs=[[{"type": "scatter"}, {"type": "bar"}], [{"type": "histogram"}, {"type": "scatter"}]], |
| 43 | +) |
| 44 | + |
| 45 | +# Colors |
| 46 | +python_blue = "#306998" |
| 47 | +python_yellow = "#FFD43B" |
| 48 | +accent_green = "#2E7D32" |
| 49 | +accent_red = "#C62828" |
| 50 | + |
| 51 | +# Subplot 1: Line chart - Stock price with moving average |
| 52 | +fig.add_trace( |
| 53 | + go.Scatter(x=dates, y=price, mode="lines", name="Price", line={"color": python_blue, "width": 3}), row=1, col=1 |
| 54 | +) |
| 55 | +fig.add_trace( |
| 56 | + go.Scatter( |
| 57 | + x=dates, y=ma_20, mode="lines", name="20-day MA", line={"color": python_yellow, "width": 2, "dash": "dash"} |
| 58 | + ), |
| 59 | + row=1, |
| 60 | + col=1, |
| 61 | +) |
| 62 | + |
| 63 | +# Subplot 2: Bar chart - Volume |
| 64 | +volume_colors = [accent_green if r >= 0 else accent_red for r in returns] |
| 65 | +fig.add_trace(go.Bar(x=dates, y=volume, name="Volume", marker={"color": volume_colors, "opacity": 0.7}), row=1, col=2) |
| 66 | + |
| 67 | +# Subplot 3: Histogram - Daily returns distribution |
| 68 | +fig.add_trace( |
| 69 | + go.Histogram( |
| 70 | + x=daily_returns, |
| 71 | + nbinsx=20, |
| 72 | + name="Returns", |
| 73 | + marker={"color": python_blue, "opacity": 0.7, "line": {"color": "white", "width": 1}}, |
| 74 | + ), |
| 75 | + row=2, |
| 76 | + col=1, |
| 77 | +) |
| 78 | + |
| 79 | +# Subplot 4: Scatter - Price vs Volume relationship |
| 80 | +fig.add_trace( |
| 81 | + go.Scatter( |
| 82 | + x=volume, |
| 83 | + y=price, |
| 84 | + mode="markers", |
| 85 | + name="Price-Volume", |
| 86 | + marker={"color": python_blue, "size": 10, "opacity": 0.6, "line": {"color": "white", "width": 1}}, |
| 87 | + ), |
| 88 | + row=2, |
| 89 | + col=2, |
| 90 | +) |
| 91 | + |
| 92 | +# Update layout |
| 93 | +fig.update_layout( |
| 94 | + title={"text": "subplot-grid \u00b7 plotly \u00b7 pyplots.ai", "font": {"size": 32}, "x": 0.5, "xanchor": "center"}, |
| 95 | + showlegend=True, |
| 96 | + legend={"font": {"size": 16}, "x": 1.02, "y": 1, "xanchor": "left", "yanchor": "top"}, |
| 97 | + template="plotly_white", |
| 98 | + margin={"l": 80, "r": 150, "t": 120, "b": 80}, |
| 99 | +) |
| 100 | + |
| 101 | +# Update all axes fonts |
| 102 | +fig.update_xaxes(tickfont={"size": 14}, title_font={"size": 18}) |
| 103 | +fig.update_yaxes(tickfont={"size": 14}, title_font={"size": 18}) |
| 104 | + |
| 105 | +# Specific axis labels |
| 106 | +fig.update_xaxes(title_text="Date", row=1, col=1) |
| 107 | +fig.update_yaxes(title_text="Price ($)", row=1, col=1) |
| 108 | +fig.update_xaxes(title_text="Date", row=1, col=2) |
| 109 | +fig.update_yaxes(title_text="Volume", row=1, col=2) |
| 110 | +fig.update_xaxes(title_text="Daily Return (%)", row=2, col=1) |
| 111 | +fig.update_yaxes(title_text="Frequency", row=2, col=1) |
| 112 | +fig.update_xaxes(title_text="Volume", row=2, col=2) |
| 113 | +fig.update_yaxes(title_text="Price ($)", row=2, col=2) |
| 114 | + |
| 115 | +# Update subplot titles font size |
| 116 | +for annotation in fig["layout"]["annotations"]: |
| 117 | + annotation["font"] = {"size": 20} |
| 118 | + |
| 119 | +# Save as PNG (4800x2700 via scale=3) |
| 120 | +fig.write_image("plot.png", width=1600, height=900, scale=3) |
| 121 | + |
| 122 | +# Save interactive HTML |
| 123 | +fig.write_html("plot.html", include_plotlyjs=True) |
0 commit comments