Skip to content

Commit 3bb19ec

Browse files
feat(highcharts): implement bar-basic (#133)
## Summary Implements `bar-basic` for **highcharts** library. **Parent Issue:** #117 **Sub-Issue:** #125 **Base Branch:** `plot/bar-basic` **Attempt:** 1/3 ## Implementation - `plots/highcharts/bar/bar-basic/default.py` ## Features - Uses `ColumnSeries` for vertical bars (Highcharts terminology for bar charts) - X-axis with category labels - Y-axis starting at zero with subtle dotted grid lines (alpha ~0.15) - Customizable colors, opacity, rotation of labels - Input validation for empty data and missing columns - Google-style docstrings with Args, Returns, Raises, Example sections - Selenium-based PNG export for testing ## Quality Criteria Met - [x] X-axis displays category labels clearly - [x] Y-axis shows numeric scale with appropriate tick marks - [x] Both axes are labeled (custom or column names) - [x] Grid lines visible but subtle on y-axis only - [x] Bars have consistent width and spacing - [x] Bar colors distinguishable from background - [x] Title present when specified - [x] Y-axis starts at zero Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 89e5d35 commit 3bb19ec

1 file changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""
2+
bar-basic: Basic Bar Chart
3+
Library: highcharts
4+
5+
A fundamental vertical bar chart that visualizes categorical data with numeric values.
6+
7+
Note: Highcharts requires a license for commercial use.
8+
"""
9+
10+
from typing import Optional
11+
12+
import pandas as pd
13+
from highcharts_core.chart import Chart
14+
from highcharts_core.options import HighchartsOptions
15+
from highcharts_core.options.series.bar import ColumnSeries
16+
17+
18+
def create_plot(
19+
data: pd.DataFrame,
20+
category: str,
21+
value: str,
22+
figsize: tuple[int, int] = (10, 6),
23+
color: str = "steelblue",
24+
edgecolor: str = "black",
25+
alpha: float = 0.8,
26+
title: Optional[str] = None,
27+
xlabel: Optional[str] = None,
28+
ylabel: Optional[str] = None,
29+
rotation: int = 0,
30+
width: int = 1600,
31+
height: int = 900,
32+
**kwargs,
33+
) -> Chart:
34+
"""
35+
Create a basic bar chart from DataFrame.
36+
37+
Args:
38+
data: Input DataFrame with categorical and numeric data
39+
category: Column name for category labels (x-axis)
40+
value: Column name for numeric values (bar heights)
41+
figsize: Figure size as (width, height) in inches (legacy, use width/height instead)
42+
color: Bar fill color
43+
edgecolor: Bar edge color
44+
alpha: Transparency level for bars (0.0 to 1.0)
45+
title: Plot title
46+
xlabel: X-axis label (defaults to column name if None)
47+
ylabel: Y-axis label (defaults to column name if None)
48+
rotation: Rotation angle for x-axis labels
49+
width: Figure width in pixels (default: 1600)
50+
height: Figure height in pixels (default: 900)
51+
**kwargs: Additional parameters passed to chart options
52+
53+
Returns:
54+
Highcharts Chart object
55+
56+
Raises:
57+
ValueError: If data is empty
58+
KeyError: If required columns are not found in data
59+
60+
Example:
61+
>>> data = pd.DataFrame({
62+
... 'category': ['A', 'B', 'C'],
63+
... 'value': [10, 20, 30]
64+
... })
65+
>>> chart = create_plot(data, 'category', 'value', title='My Chart')
66+
"""
67+
# Input validation
68+
if data.empty:
69+
raise ValueError("Data cannot be empty")
70+
71+
for col in [category, value]:
72+
if col not in data.columns:
73+
available = ", ".join(data.columns.tolist())
74+
raise KeyError(f"Column '{col}' not found. Available: {available}")
75+
76+
# Create chart
77+
chart = Chart()
78+
chart.options = HighchartsOptions()
79+
80+
# Chart configuration
81+
chart.options.chart = {"type": "column", "width": width, "height": height, "backgroundColor": "#ffffff"}
82+
83+
# Title
84+
if title:
85+
chart.options.title = {"text": title, "style": {"fontSize": "16px", "fontWeight": "bold"}}
86+
else:
87+
chart.options.title = {"text": None}
88+
89+
# X-axis configuration
90+
categories = data[category].tolist()
91+
x_label = xlabel if xlabel is not None else category
92+
chart.options.x_axis = {
93+
"categories": categories,
94+
"title": {"text": x_label, "style": {"fontSize": "12px"}},
95+
"labels": {"rotation": -rotation if rotation else 0, "style": {"fontSize": "10px"}},
96+
}
97+
98+
# Y-axis configuration with subtle grid (y-axis only per spec)
99+
y_label = ylabel if ylabel is not None else value
100+
chart.options.y_axis = {
101+
"title": {"text": y_label, "style": {"fontSize": "12px"}},
102+
"min": 0,
103+
"gridLineWidth": 1,
104+
"gridLineDashStyle": "Dot",
105+
"gridLineColor": "rgba(0, 0, 0, 0.15)",
106+
"labels": {"style": {"fontSize": "10px"}},
107+
}
108+
109+
# Create series with column type (vertical bars in Highcharts)
110+
series = ColumnSeries()
111+
series.data = data[value].tolist()
112+
series.name = y_label
113+
series.color = color
114+
series.border_color = edgecolor
115+
series.border_width = 1
116+
117+
# Set opacity via plot options
118+
chart.options.plot_options = {
119+
"column": {"opacity": alpha, "pointPadding": 0.1, "groupPadding": 0.1, "borderWidth": 1, "colorByPoint": False}
120+
}
121+
122+
chart.add_series(series)
123+
124+
# Legend (single series, so hide legend)
125+
chart.options.legend = {"enabled": False}
126+
127+
# Credits
128+
chart.options.credits = {"enabled": False}
129+
130+
return chart
131+
132+
133+
if __name__ == "__main__":
134+
import tempfile
135+
import time
136+
from pathlib import Path
137+
138+
from selenium import webdriver
139+
from selenium.webdriver.chrome.options import Options
140+
141+
# Sample data for testing
142+
sample_data = pd.DataFrame(
143+
{"category": ["Product A", "Product B", "Product C", "Product D", "Product E"], "value": [45, 78, 52, 91, 63]}
144+
)
145+
146+
# Create plot
147+
chart = create_plot(
148+
sample_data, "category", "value", title="Sales by Product", xlabel="Product Category", ylabel="Sales ($)"
149+
)
150+
151+
# Export to PNG via Selenium screenshot
152+
html_str = chart.to_js_literal()
153+
html_content = f"""<!DOCTYPE html>
154+
<html>
155+
<head>
156+
<meta charset="utf-8">
157+
<script src="https://code.highcharts.com/highcharts.js"></script>
158+
</head>
159+
<body style="margin:0;">
160+
<div id="container" style="width: 1600px; height: 900px;"></div>
161+
<script>{html_str}</script>
162+
</body>
163+
</html>"""
164+
165+
# Write temp HTML and take screenshot
166+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False) as f:
167+
f.write(html_content)
168+
temp_path = f.name
169+
170+
chrome_options = Options()
171+
chrome_options.add_argument("--headless")
172+
chrome_options.add_argument("--no-sandbox")
173+
chrome_options.add_argument("--disable-dev-shm-usage")
174+
chrome_options.add_argument("--window-size=1600,900")
175+
176+
driver = webdriver.Chrome(options=chrome_options)
177+
driver.get(f"file://{temp_path}")
178+
time.sleep(1) # Wait for chart to render
179+
driver.save_screenshot("plot.png")
180+
driver.quit()
181+
182+
Path(temp_path).unlink() # Clean up temp file
183+
print("Plot saved to plot.png")

0 commit comments

Comments
 (0)