|
1 | 1 | """ |
2 | 2 | area-basic: Basic Area Chart |
3 | | -Implementation for: bokeh |
4 | | -Variant: default |
5 | | -Python: 3.10+ |
| 3 | +Library: bokeh |
6 | 4 | """ |
7 | 5 |
|
8 | | -from typing import TYPE_CHECKING, Optional |
9 | | - |
10 | 6 | import pandas as pd |
| 7 | +from bokeh.io import export_png |
11 | 8 | from bokeh.models import ColumnDataSource |
12 | 9 | from bokeh.plotting import figure |
13 | 10 |
|
14 | 11 |
|
15 | | -if TYPE_CHECKING: |
16 | | - from bokeh.plotting import Figure |
17 | | - |
18 | | - |
19 | | -def create_plot( |
20 | | - data: pd.DataFrame, |
21 | | - x: str, |
22 | | - y: str, |
23 | | - fill_alpha: float = 0.5, |
24 | | - line_color: Optional[str] = None, |
25 | | - title: Optional[str] = None, |
26 | | - x_label: Optional[str] = None, |
27 | | - y_label: Optional[str] = None, |
28 | | - width: int = 1600, |
29 | | - height: int = 900, |
30 | | - **kwargs, |
31 | | -) -> "Figure": |
32 | | - """ |
33 | | - Create a basic filled area chart using bokeh. |
34 | | -
|
35 | | - A simple filled area chart showing a single data series over time or |
36 | | - sequential x-values. The area between the data line and the baseline |
37 | | - (zero) is filled with a semi-transparent color. |
38 | | -
|
39 | | - Args: |
40 | | - data: Input DataFrame with x and y columns |
41 | | - x: Column name for x-axis values |
42 | | - y: Column name for y-axis values |
43 | | - fill_alpha: Transparency of the filled area (default: 0.5) |
44 | | - line_color: Color of the line and fill (default: bokeh blue) |
45 | | - title: Chart title (optional) |
46 | | - x_label: Label for x-axis (optional, defaults to column name) |
47 | | - y_label: Label for y-axis (optional, defaults to column name) |
48 | | - width: Figure width in pixels (default: 1600) |
49 | | - height: Figure height in pixels (default: 900) |
50 | | - **kwargs: Additional parameters passed to figure |
51 | | -
|
52 | | - Returns: |
53 | | - Bokeh Figure object |
54 | | -
|
55 | | - Raises: |
56 | | - ValueError: If data is empty or fill_alpha is out of range |
57 | | - KeyError: If required columns not found |
58 | | -
|
59 | | - Example: |
60 | | - >>> data = pd.DataFrame({ |
61 | | - ... 'Month': [1, 2, 3, 4, 5, 6], |
62 | | - ... 'Sales': [100, 120, 90, 140, 160, 130] |
63 | | - ... }) |
64 | | - >>> fig = create_plot(data, x='Month', y='Sales', title='Monthly Sales') |
65 | | - """ |
66 | | - # Input validation |
67 | | - if data.empty: |
68 | | - raise ValueError("Data cannot be empty") |
69 | | - |
70 | | - for col in [x, y]: |
71 | | - if col not in data.columns: |
72 | | - available = ", ".join(data.columns) |
73 | | - raise KeyError(f"Column '{col}' not found. Available columns: {available}") |
74 | | - |
75 | | - if not 0 <= fill_alpha <= 1: |
76 | | - raise ValueError(f"fill_alpha must be between 0 and 1, got {fill_alpha}") |
77 | | - |
78 | | - # Set default color (bokeh blue) |
79 | | - color = line_color or "#1f77b4" |
80 | | - |
81 | | - # Sort data by x to ensure proper area rendering |
82 | | - plot_data = data[[x, y]].dropna().sort_values(by=x).reset_index(drop=True) |
83 | | - |
84 | | - # Create ColumnDataSource |
85 | | - source = ColumnDataSource(data={"x": plot_data[x], "y": plot_data[y], "y0": [0] * len(plot_data)}) |
86 | | - |
87 | | - # Create figure |
88 | | - p = figure( |
89 | | - width=width, |
90 | | - height=height, |
91 | | - title=title or "Area Chart", |
92 | | - x_axis_label=x_label or x, |
93 | | - y_axis_label=y_label or y, |
94 | | - toolbar_location="above", |
95 | | - tools="pan,wheel_zoom,box_zoom,reset,save", |
96 | | - **kwargs, |
97 | | - ) |
98 | | - |
99 | | - # Draw the filled area from baseline (0) to y values |
100 | | - p.varea(x="x", y1="y0", y2="y", source=source, fill_color=color, fill_alpha=fill_alpha) |
101 | | - |
102 | | - # Draw line on top for better visibility |
103 | | - p.line(x="x", y="y", source=source, line_color=color, line_width=2) |
104 | | - |
105 | | - # Styling |
106 | | - p.title.text_font_size = "14pt" |
107 | | - p.title.align = "center" |
108 | | - |
109 | | - # Grid styling - subtle |
110 | | - p.xgrid.grid_line_alpha = 0.3 |
111 | | - p.ygrid.grid_line_alpha = 0.3 |
112 | | - p.xgrid.grid_line_dash = [6, 4] |
113 | | - p.ygrid.grid_line_dash = [6, 4] |
114 | | - |
115 | | - # Axis styling |
116 | | - p.xaxis.axis_label_text_font_size = "12pt" |
117 | | - p.yaxis.axis_label_text_font_size = "12pt" |
118 | | - p.xaxis.major_label_text_font_size = "10pt" |
119 | | - p.yaxis.major_label_text_font_size = "10pt" |
120 | | - |
121 | | - return p |
122 | | - |
123 | | - |
124 | | -if __name__ == "__main__": |
125 | | - import numpy as np |
126 | | - |
127 | | - # Sample data: Monthly website traffic over a year |
128 | | - np.random.seed(42) |
129 | | - months = list(range(1, 13)) |
130 | | - base_traffic = [1000, 1100, 1050, 1200, 1400, 1600, 1500, 1550, 1700, 1650, 1800, 2000] |
131 | | - noise = np.random.normal(0, 50, 12) |
132 | | - traffic = [max(0, int(b + n)) for b, n in zip(base_traffic, noise, strict=False)] |
133 | | - |
134 | | - data = pd.DataFrame({"Month": months, "Visitors": traffic}) |
135 | | - |
136 | | - # Create plot |
137 | | - fig = create_plot( |
138 | | - data, |
139 | | - x="Month", |
140 | | - y="Visitors", |
141 | | - title="Monthly Website Traffic", |
142 | | - x_label="Month", |
143 | | - y_label="Visitors (thousands)", |
144 | | - fill_alpha=0.5, |
145 | | - ) |
146 | | - |
147 | | - # Save as PNG using webdriver-manager for automatic chromedriver |
148 | | - from bokeh.io import export_png |
149 | | - from selenium import webdriver |
150 | | - from selenium.webdriver.chrome.options import Options |
151 | | - from selenium.webdriver.chrome.service import Service |
152 | | - from webdriver_manager.chrome import ChromeDriverManager |
153 | | - |
154 | | - chrome_options = Options() |
155 | | - chrome_options.add_argument("--headless") |
156 | | - chrome_options.add_argument("--no-sandbox") |
157 | | - chrome_options.add_argument("--disable-dev-shm-usage") |
158 | | - |
159 | | - service = Service(ChromeDriverManager().install()) |
160 | | - driver = webdriver.Chrome(service=service, options=chrome_options) |
161 | | - |
162 | | - export_png(fig, filename="plot.png", webdriver=driver) |
163 | | - driver.quit() |
164 | | - print("Plot saved to plot.png") |
| 12 | +# Data |
| 13 | +data = pd.DataFrame( |
| 14 | + { |
| 15 | + "month": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], |
| 16 | + "sales": [120, 135, 148, 162, 175, 195, 210, 198, 185, 170, 158, 190], |
| 17 | + } |
| 18 | +) |
| 19 | + |
| 20 | +# Create x positions for categorical months |
| 21 | +x = list(range(len(data["month"]))) |
| 22 | +y = data["sales"].tolist() |
| 23 | + |
| 24 | +source = ColumnDataSource(data={"x": x, "y": y, "month": data["month"]}) |
| 25 | + |
| 26 | +# Create figure |
| 27 | +p = figure( |
| 28 | + width=4800, |
| 29 | + height=2700, |
| 30 | + title="Monthly Sales Volume", |
| 31 | + x_axis_label="Month", |
| 32 | + y_axis_label="Sales ($)", |
| 33 | + x_range=(-0.5, len(x) - 0.5), |
| 34 | +) |
| 35 | + |
| 36 | +# Plot area (varea fills from y1 to y2) |
| 37 | +p.varea(x="x", y1=0, y2="y", source=source, fill_alpha=0.7, fill_color="#306998") |
| 38 | + |
| 39 | +# Add line on top for clearer boundary |
| 40 | +p.line(x="x", y="y", source=source, line_width=2, line_color="#306998") |
| 41 | + |
| 42 | +# Configure x-axis ticks to show month labels |
| 43 | +p.xaxis.ticker = x |
| 44 | +p.xaxis.major_label_overrides = dict(enumerate(data["month"])) |
| 45 | + |
| 46 | +# Styling |
| 47 | +p.title.text_font_size = "20pt" |
| 48 | +p.xaxis.axis_label_text_font_size = "20pt" |
| 49 | +p.yaxis.axis_label_text_font_size = "20pt" |
| 50 | +p.xaxis.major_label_text_font_size = "16pt" |
| 51 | +p.yaxis.major_label_text_font_size = "16pt" |
| 52 | +p.grid.grid_line_alpha = 0.3 |
| 53 | + |
| 54 | +# Save |
| 55 | +export_png(p, filename="plot.png") |
0 commit comments