From e183fa835c0d406e2f20b61e54d2ab5e34bc52f0 Mon Sep 17 00:00:00 2001
From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com>
Date: Sun, 7 Dec 2025 00:48:42 +0000
Subject: [PATCH] feat(highcharts): implement box-basic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Implements basic box plot for highcharts library following KISS style:
- Uses pandas for data preparation
- Calculates quartiles, whiskers, and outliers manually
- Uses color palette from style guide (#306998, #FFD43B, #059669, #8B5CF6)
- Includes outliers as separate scatter series
- Target dimensions: 4800x2700px
- Downloads Highcharts JS inline for headless Chrome compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
plots/highcharts/boxplot/box-basic/default.py | 415 +++++++-----------
1 file changed, 160 insertions(+), 255 deletions(-)
diff --git a/plots/highcharts/boxplot/box-basic/default.py b/plots/highcharts/boxplot/box-basic/default.py
index 78baa718d0..b964ce3563 100644
--- a/plots/highcharts/boxplot/box-basic/default.py
+++ b/plots/highcharts/boxplot/box-basic/default.py
@@ -1,257 +1,163 @@
"""
box-basic: Basic Box Plot
-Implementation for: highcharts
-Variant: default
-Python: 3.10+
+Library: highcharts
Note: Highcharts requires a license for commercial use.
"""
-from typing import Optional
+import tempfile
+import time
+import urllib.request
+from pathlib import Path
import numpy as np
import pandas as pd
from highcharts_core.chart import Chart
from highcharts_core.options import HighchartsOptions
from highcharts_core.options.series.boxplot import BoxPlotSeries
-
-
-def create_plot(
- data: pd.DataFrame,
- values: str,
- groups: str,
- title: Optional[str] = None,
- xlabel: Optional[str] = None,
- ylabel: Optional[str] = None,
- colors: Optional[list] = None,
- width: int = 1600,
- height: int = 900,
- **kwargs,
-) -> Chart:
- """
- Create a basic box plot showing statistical distribution of multiple groups using Highcharts.
-
- Args:
- data: Input DataFrame with required columns
- values: Column name containing numeric values
- groups: Column name containing group categories
- title: Plot title (optional)
- xlabel: Custom x-axis label (optional, defaults to groups column name)
- ylabel: Custom y-axis label (optional, defaults to values column name)
- colors: List of colors for each box (optional)
- width: Figure width in pixels (default: 1600)
- height: Figure height in pixels (default: 900)
- **kwargs: Additional parameters for Highcharts configuration
-
- Returns:
- Highcharts Chart object
-
- Raises:
- ValueError: If data is empty
- KeyError: If required columns not found
-
- Example:
- >>> data = pd.DataFrame({
- ... 'Group': ['A', 'A', 'B', 'B', 'C', 'C'],
- ... 'Value': [1, 2, 2, 3, 3, 4]
- ... })
- >>> chart = create_plot(data, values='Value', groups='Group')
- """
- # Input validation
- if data.empty:
- raise ValueError("Data cannot be empty")
-
- # Check required columns
- for col in [values, groups]:
- if col not in data.columns:
- available = ", ".join(data.columns)
- raise KeyError(f"Column '{col}' not found. Available columns: {available}")
-
- # Prepare box plot data
- group_names = sorted(data[groups].unique())
- box_data = []
- outliers_data = []
-
- for i, group in enumerate(group_names):
- group_data = data[data[groups] == group][values].dropna()
-
- # Calculate statistics
- q1 = float(group_data.quantile(0.25))
- median = float(group_data.quantile(0.5))
- q3 = float(group_data.quantile(0.75))
- iqr = q3 - q1
- lower_whisker = max(float(group_data.min()), q1 - 1.5 * iqr)
- upper_whisker = min(float(group_data.max()), q3 + 1.5 * iqr)
-
- # Box plot data: [low, q1, median, q3, high]
- box_data.append([lower_whisker, q1, median, q3, upper_whisker])
-
- # Find outliers
- outliers = group_data[(group_data < lower_whisker) | (group_data > upper_whisker)]
- for outlier in outliers:
- outliers_data.append([i, float(outlier)])
-
- # Create chart with container ID for rendering
- chart = Chart(container="container")
-
- # Configure chart options
- chart.options = HighchartsOptions()
-
- # Title
- chart.options.title = {
- "text": title or "Box Plot Distribution",
- "style": {"fontSize": "16px", "fontWeight": "bold"},
- }
-
- # X-axis
- chart.options.x_axis = {"categories": list(group_names), "title": {"text": xlabel or groups}}
-
- # Y-axis
- chart.options.y_axis = {
- "title": {"text": ylabel or values},
- "gridLineWidth": 1,
- "gridLineDashStyle": "Dot",
- "gridLineColor": "#e0e0e0",
- }
-
- # Colors
- if colors:
- chart.options.colors = colors
- else:
- chart.options.colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854"]
-
- # Plot options
- chart.options.plot_options = {
- "boxplot": {
- "fillColor": None,
- "lineWidth": 2,
- "medianWidth": 3,
- "medianColor": "#FF0000",
- "stemWidth": 1,
- "whiskerWidth": 2,
- "whiskerLength": "50%",
- }
- }
-
- # Tooltip
- chart.options.tooltip = {
- "shared": False,
- "useHTML": True,
- "headerFormat": "{point.key}
",
- "pointFormat": (
- "Max: {point.high}
"
- "Q3: {point.q3}
"
- 'Median: {point.median}
'
- "Q1: {point.q1}
"
- "Min: {point.low}
"
+from highcharts_core.options.series.scatter import ScatterSeries
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+
+
+# Data
+np.random.seed(42)
+data = pd.DataFrame(
+ {
+ "group": ["A"] * 50 + ["B"] * 50 + ["C"] * 50 + ["D"] * 50,
+ "value": np.concatenate(
+ [
+ np.random.normal(50, 10, 50),
+ np.random.normal(60, 15, 50),
+ np.random.normal(45, 8, 50),
+ np.random.normal(70, 20, 50),
+ ]
),
}
-
- # Chart dimensions
- chart.options.chart = {"type": "boxplot", "width": width, "height": height, "backgroundColor": "white"}
-
- # Add box plot series
- box_series = BoxPlotSeries()
- box_series.data = box_data
- box_series.name = "Distribution"
- box_series.color_by_point = True
- chart.add_series(box_series)
-
- # Add outliers as scatter series if any exist
- if outliers_data:
- from highcharts_core.options.series.scatter import ScatterSeries
-
- scatter_series = ScatterSeries()
- scatter_series.data = outliers_data
- scatter_series.name = "Outliers"
- scatter_series.color = "rgba(255, 0, 0, 0.5)"
- scatter_series.marker = {
- "fillColor": "rgba(255, 0, 0, 0.5)",
- "lineWidth": 1,
- "lineColor": "#000000",
- "radius": 4,
- }
- scatter_series.tooltip = {"pointFormat": "Outlier: {point.y}"}
- chart.add_series(scatter_series)
-
- # Legend
- chart.options.legend = {
- "enabled": False # Hide legend for cleaner look
+)
+
+# Calculate box plot statistics for each group
+groups = sorted(data["group"].unique())
+box_data = []
+outliers_data = []
+
+for i, group in enumerate(groups):
+ group_values = data[data["group"] == group]["value"].dropna()
+
+ q1 = float(group_values.quantile(0.25))
+ median = float(group_values.quantile(0.5))
+ q3 = float(group_values.quantile(0.75))
+ iqr = q3 - q1
+ lower_whisker = max(float(group_values.min()), q1 - 1.5 * iqr)
+ upper_whisker = min(float(group_values.max()), q3 + 1.5 * iqr)
+
+ # Box plot data format: [low, q1, median, q3, high]
+ box_data.append([lower_whisker, q1, median, q3, upper_whisker])
+
+ # Find outliers
+ outliers = group_values[(group_values < lower_whisker) | (group_values > upper_whisker)]
+ for outlier in outliers:
+ outliers_data.append([i, float(outlier)])
+
+# Create chart
+chart = Chart(container="container")
+chart.options = HighchartsOptions()
+
+# Chart configuration
+chart.options.chart = {"type": "boxplot", "width": 4800, "height": 2700, "backgroundColor": "#ffffff"}
+
+# Title
+chart.options.title = {"text": "Basic Box Plot", "style": {"fontSize": "60px", "fontWeight": "bold"}}
+
+# X-axis
+chart.options.x_axis = {
+ "categories": groups,
+ "title": {"text": "Group", "style": {"fontSize": "60px"}},
+ "labels": {"style": {"fontSize": "48px"}},
+}
+
+# Y-axis
+chart.options.y_axis = {
+ "title": {"text": "Value", "style": {"fontSize": "60px"}},
+ "labels": {"style": {"fontSize": "48px"}},
+ "gridLineWidth": 1,
+ "gridLineDashStyle": "Dot",
+ "gridLineColor": "rgba(0, 0, 0, 0.3)",
+}
+
+# Colors (using palette from style guide)
+colors = ["#306998", "#FFD43B", "#059669", "#8B5CF6"]
+
+# Plot options
+chart.options.plot_options = {
+ "boxplot": {
+ "fillColor": None,
+ "lineWidth": 4,
+ "medianWidth": 6,
+ "medianColor": "#DC2626",
+ "stemWidth": 2,
+ "whiskerWidth": 4,
+ "whiskerLength": "50%",
+ "colorByPoint": True,
}
+}
+
+# Tooltip
+chart.options.tooltip = {
+ "shared": False,
+ "useHTML": True,
+ "headerFormat": "{point.key}
",
+ "pointFormat": (
+ "Max: {point.high:.1f}
"
+ "Q3: {point.q3:.1f}
"
+ 'Median: {point.median:.1f}
'
+ "Q1: {point.q1:.1f}
"
+ "Min: {point.low:.1f}
"
+ ),
+ "style": {"fontSize": "36px"},
+}
+
+# Add box plot series
+box_series = BoxPlotSeries()
+box_series.data = box_data
+box_series.name = "Distribution"
+chart.options.colors = colors
+chart.add_series(box_series)
+
+# Add outliers as scatter series if any exist
+if outliers_data:
+ scatter_series = ScatterSeries()
+ scatter_series.data = outliers_data
+ scatter_series.name = "Outliers"
+ scatter_series.color = "#DC2626"
+ scatter_series.marker = {
+ "fillColor": "#DC2626",
+ "lineWidth": 2,
+ "lineColor": "#000000",
+ "radius": 8,
+ "symbol": "circle",
+ }
+ scatter_series.tooltip = {"pointFormat": "Outlier: {point.y:.1f}"}
+ chart.add_series(scatter_series)
- # Credits
- chart.options.credits = {"enabled": False}
-
- return chart
-
-
-if __name__ == "__main__":
- # Sample data for testing with different distributions per group
- np.random.seed(42) # For reproducibility
-
- # Generate sample data with 4 groups
- data_dict = {"Group": [], "Value": []}
-
- # Group A: Normal distribution, mean=50, std=10
- group_a_data = np.random.normal(50, 10, 40)
- # Add some outliers
- group_a_data = np.append(group_a_data, [80, 85, 15])
-
- # Group B: Normal distribution, mean=60, std=15
- group_b_data = np.random.normal(60, 15, 35)
- # Add outliers
- group_b_data = np.append(group_b_data, [100, 10])
-
- # Group C: Normal distribution, mean=45, std=8
- group_c_data = np.random.normal(45, 8, 45)
-
- # Group D: Skewed distribution
- group_d_data = np.random.gamma(2, 2, 30) + 40
- # Add outliers
- group_d_data = np.append(group_d_data, [75, 78, 20])
-
- # Combine all data
- for group, values in zip(
- ["Group A", "Group B", "Group C", "Group D"],
- [group_a_data, group_b_data, group_c_data, group_d_data],
- strict=False,
- ):
- data_dict["Group"].extend([group] * len(values))
- data_dict["Value"].extend(values)
-
- data = pd.DataFrame(data_dict)
-
- # Create plot
- chart = create_plot(
- data,
- values="Value",
- groups="Group",
- title="Statistical Distribution Comparison Across Groups",
- ylabel="Measurement Value",
- xlabel="Categories",
- )
-
- # Export to PNG via Selenium screenshot
- import tempfile
- import time
- import urllib.request
- from pathlib import Path
+# Legend
+chart.options.legend = {"enabled": False}
- from selenium import webdriver
- from selenium.webdriver.chrome.options import Options
+# Credits
+chart.options.credits = {"enabled": False}
- # Download Highcharts JS (required for headless Chrome which can't load CDN)
- highcharts_url = "https://code.highcharts.com/highcharts.js"
- highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
+# Download Highcharts JS (required for headless Chrome)
+highcharts_url = "https://code.highcharts.com/highcharts.js"
+highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
- with urllib.request.urlopen(highcharts_url, timeout=30) as response:
- highcharts_js = response.read().decode("utf-8")
- with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
- highcharts_more_js = response.read().decode("utf-8")
+with urllib.request.urlopen(highcharts_url, timeout=30) as response:
+ highcharts_js = response.read().decode("utf-8")
+with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
+ highcharts_more_js = response.read().decode("utf-8")
- # Generate HTML content with inline scripts
- html_str = chart.to_js_literal()
- html_content = f"""
+# Generate HTML with inline scripts
+html_str = chart.to_js_literal()
+html_content = f"""