Skip to content

Commit 0786958

Browse files
claude[bot]github-actions[bot]MarkusNeusinger
authored
feat(bokeh): implement line-basic (#374)
## Summary Implements `line-basic` for **bokeh** library. **Parent Issue:** #205 **Sub-Issue:** #232 **Base Branch:** `plot/line-basic` ## Implementation - `plots/bokeh/line/line-basic/default.py` ## Changes - KISS style: simple sequential script (no functions, no classes) - Uses ColumnDataSource for data management - Output: 4800x2700 PNG with proper styling (20pt title/labels, 16pt ticks) - Uses Python Blue (#306998) color from style guide - Includes line with markers at data points ## Latest Fix (afa562e) - **Removed webdriver-manager dependency**: Uses system chromedriver directly instead of webdriver-manager - **Added better headless flags**: `--headless=new` and `--disable-gpu` for improved compatibility - This approach is more reliable in CI environments where Chrome/Chromium and chromedriver are pre-installed --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 001ecc1 commit 0786958

2 files changed

Lines changed: 1561 additions & 1661 deletions

File tree

Lines changed: 33 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,50 @@
11
"""
22
line-basic: Basic Line Plot
3-
Implementation for: bokeh
4-
Variant: default
5-
Python: 3.10+
3+
Library: bokeh
64
"""
75

8-
from typing import TYPE_CHECKING, Optional
9-
10-
import numpy as np
116
import pandas as pd
7+
from bokeh.io import export_png
128
from bokeh.models import ColumnDataSource
139
from bokeh.plotting import figure
10+
from selenium import webdriver
11+
from selenium.webdriver.chrome.options import Options
12+
from selenium.webdriver.chrome.service import Service
1413

1514

16-
if TYPE_CHECKING:
17-
from bokeh.plotting import Figure
18-
19-
20-
def create_plot(
21-
data: pd.DataFrame,
22-
x: str,
23-
y: str,
24-
title: Optional[str] = None,
25-
xlabel: Optional[str] = None,
26-
ylabel: Optional[str] = None,
27-
color: str = "steelblue",
28-
line_width: float = 2.0,
29-
marker: Optional[str] = None,
30-
marker_size: float = 8,
31-
alpha: float = 1.0,
32-
width: int = 1600,
33-
height: int = 900,
34-
**kwargs,
35-
) -> "Figure":
36-
"""
37-
Create a basic line plot visualizing trends over continuous or sequential data.
38-
39-
Args:
40-
data: Input DataFrame with required columns
41-
x: Column name for x-axis values (numeric or datetime)
42-
y: Column name for y-axis values (numeric)
43-
title: Plot title (optional)
44-
xlabel: Custom x-axis label (optional, defaults to column name)
45-
ylabel: Custom y-axis label (optional, defaults to column name)
46-
color: Line color (default: "steelblue")
47-
line_width: Width of the line (default: 2.0)
48-
marker: Marker style for data points (optional, e.g., "circle", "square")
49-
marker_size: Size of markers if enabled (default: 8)
50-
alpha: Line transparency (default: 1.0)
51-
width: Figure width in pixels (default: 1600)
52-
height: Figure height in pixels (default: 900)
53-
**kwargs: Additional parameters
54-
55-
Returns:
56-
Bokeh Figure object
57-
58-
Raises:
59-
ValueError: If data is empty
60-
KeyError: If required columns not found
61-
TypeError: If y column contains non-numeric data
62-
63-
Example:
64-
>>> data = pd.DataFrame({'x': [1, 2, 3, 4, 5], 'y': [2, 4, 3, 5, 6]})
65-
>>> fig = create_plot(data, 'x', 'y')
66-
"""
67-
# Input validation
68-
if data.empty:
69-
raise ValueError("Data cannot be empty")
70-
71-
# Check required columns
72-
for col in [x, y]:
73-
if col not in data.columns:
74-
available = ", ".join(data.columns)
75-
raise KeyError(f"Column '{col}' not found. Available columns: {available}")
76-
77-
# Check if y column is numeric
78-
if not pd.api.types.is_numeric_dtype(data[y]):
79-
raise TypeError(f"Column '{y}' must contain numeric data")
80-
81-
# Sort data by x to ensure proper line connection
82-
plot_data = data[[x, y]].dropna().sort_values(by=x)
83-
84-
# Determine x-axis type
85-
x_axis_type = "datetime" if pd.api.types.is_datetime64_any_dtype(plot_data[x]) else "auto"
86-
87-
# Create ColumnDataSource
88-
source = ColumnDataSource(data={"x": plot_data[x], "y": plot_data[y]})
89-
90-
# Create figure
91-
p = figure(
92-
width=width,
93-
height=height,
94-
title=title or "Line Plot",
95-
x_axis_type=x_axis_type,
96-
toolbar_location="above",
97-
tools="pan,wheel_zoom,box_zoom,reset,save",
98-
)
99-
100-
# Plot line
101-
p.line(x="x", y="y", source=source, line_color=color, line_width=line_width, line_alpha=alpha)
102-
103-
# Add markers if specified
104-
if marker:
105-
p.scatter(x="x", y="y", source=source, size=marker_size, color=color, alpha=alpha, marker=marker)
106-
107-
# Labels
108-
p.xaxis.axis_label = xlabel or x
109-
p.yaxis.axis_label = ylabel or y
110-
111-
# Styling
112-
p.title.text_font_size = "14pt"
113-
p.title.align = "center"
114-
p.xaxis.axis_label_text_font_size = "12pt"
115-
p.yaxis.axis_label_text_font_size = "12pt"
116-
p.xgrid.grid_line_alpha = 0.3
117-
p.ygrid.grid_line_alpha = 0.3
118-
p.xgrid.grid_line_dash = [6, 4]
119-
p.ygrid.grid_line_dash = [6, 4]
15+
# Data
16+
data = pd.DataFrame({"time": [1, 2, 3, 4, 5, 6, 7], "value": [10, 15, 13, 18, 22, 19, 25]})
12017

121-
return p
18+
source = ColumnDataSource(data={"x": data["time"], "y": data["value"]})
12219

20+
# Create figure
21+
p = figure(width=4800, height=2700, title="Basic Line Plot", x_axis_label="Time", y_axis_label="Value")
12322

124-
if __name__ == "__main__":
125-
from bokeh.io import export_png
23+
# Plot line
24+
p.line(x="x", y="y", source=source, line_width=2, line_color="#306998")
12625

127-
# Sample data for testing - simulating time series data
128-
np.random.seed(42)
129-
n_points = 50
26+
# Add markers at data points
27+
p.scatter(x="x", y="y", source=source, size=8, color="#306998")
13028

131-
# Create sequential x values
132-
x_values = np.arange(n_points)
133-
# Create y values with a trend and some noise
134-
y_values = 10 + 0.5 * x_values + np.random.randn(n_points) * 2
29+
# Styling
30+
p.title.text_font_size = "20pt"
31+
p.xaxis.axis_label_text_font_size = "20pt"
32+
p.yaxis.axis_label_text_font_size = "20pt"
33+
p.xaxis.major_label_text_font_size = "16pt"
34+
p.yaxis.major_label_text_font_size = "16pt"
35+
p.grid.grid_line_alpha = 0.3
13536

136-
data = pd.DataFrame({"Time": x_values, "Value": y_values})
37+
# Setup Chrome/Chromium webdriver for PNG export
38+
chrome_options = Options()
39+
chrome_options.add_argument("--headless=new")
40+
chrome_options.add_argument("--no-sandbox")
41+
chrome_options.add_argument("--disable-dev-shm-usage")
42+
chrome_options.add_argument("--disable-gpu")
13743

138-
# Create plot
139-
fig = create_plot(
140-
data,
141-
x="Time",
142-
y="Value",
143-
title="Basic Line Plot Example",
144-
xlabel="Time (units)",
145-
ylabel="Measurement Value",
146-
color="steelblue",
147-
line_width=2.5,
148-
marker="circle",
149-
marker_size=6,
150-
)
44+
# Use system chromedriver (pre-installed on GitHub Actions runners)
45+
service = Service()
46+
driver = webdriver.Chrome(service=service, options=chrome_options)
15147

152-
# Save as PNG
153-
export_png(fig, filename="plot.png")
154-
print("Plot saved to plot.png")
48+
# Save
49+
export_png(p, filename="plot.png", webdriver=driver)
50+
driver.quit()

0 commit comments

Comments
 (0)