Skip to content

Commit cb5e728

Browse files
chore: merge feature branch
2 parents 880cf37 + ddf9a64 commit cb5e728

4 files changed

Lines changed: 1577 additions & 1682 deletions

File tree

.github/workflows/ci-plottest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ jobs:
6666
echo "📦 Installing: uv sync $EXTRAS"
6767
uv sync $EXTRAS
6868
69-
- name: Setup Chrome for Highcharts
70-
if: steps.detect_libs.outputs.has_plots == 'true' && contains(steps.detect_libs.outputs.libraries, 'highcharts')
69+
- name: Setup Chrome for Selenium-based libraries
70+
if: steps.detect_libs.outputs.has_plots == 'true' && (contains(steps.detect_libs.outputs.libraries, 'highcharts') || contains(steps.detect_libs.outputs.libraries, 'bokeh'))
7171
uses: browser-actions/setup-chrome@v1
7272
with:
7373
chrome-version: stable
Lines changed: 46 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,55 @@
11
"""
22
area-basic: Basic Area Chart
3-
Implementation for: bokeh
4-
Variant: default
5-
Python: 3.10+
3+
Library: bokeh
64
"""
75

8-
from typing import TYPE_CHECKING, Optional
9-
106
import pandas as pd
7+
from bokeh.io import export_png
118
from bokeh.models import ColumnDataSource
129
from bokeh.plotting import figure
1310

1411

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")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ plotting = [
7575
lib-matplotlib = ["matplotlib>=3.9.0", "numpy>=1.26.0", "pandas>=2.2.0"]
7676
lib-seaborn = ["seaborn>=0.13.0", "matplotlib>=3.9.0", "numpy>=1.26.0", "pandas>=2.2.0"]
7777
lib-plotly = ["plotly>=5.18.0", "kaleido>=0.2.1", "numpy>=1.26.0", "pandas>=2.2.0"]
78-
lib-bokeh = ["bokeh>=3.4.0", "numpy>=1.26.0", "pandas>=2.2.0"]
78+
lib-bokeh = ["bokeh>=3.4.0", "numpy>=1.26.0", "pandas>=2.2.0", "selenium>=4.15.0", "webdriver-manager>=4.0.0"]
7979
lib-altair = ["altair>=5.2.0", "vl-convert-python>=1.3.0", "numpy>=1.26.0", "pandas>=2.2.0"]
8080
lib-plotnine = ["plotnine>=0.13.0", "numpy>=1.26.0", "pandas>=2.2.0"]
8181
lib-pygal = ["pygal>=3.0.0", "cairosvg>=2.7.0", "pandas>=2.2.0"]

0 commit comments

Comments
 (0)