Skip to content

Commit dfa5f42

Browse files
feat(letsplot): implement box-grouped (#5996)
## Implementation: `box-grouped` - python/letsplot Implements the **python/letsplot** version of `box-grouped`. **File:** `plots/box-grouped/implementations/python/letsplot.py` **Parent Issue:** #2017 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25535090344)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 309acb5 commit dfa5f42

2 files changed

Lines changed: 184 additions & 155 deletions

File tree

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
box-grouped: Grouped Box Plot
3-
Library: letsplot 4.8.2 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-25
3+
Library: letsplot 4.9.0 | Python 3.13.13
4+
Quality: 91/100 | Updated: 2026-05-08
55
"""
66

7+
import os
8+
import shutil
9+
710
import numpy as np
811
import pandas as pd
912
from lets_plot import (
1013
LetsPlot,
1114
aes,
15+
element_line,
16+
element_rect,
1217
element_text,
1318
geom_boxplot,
1419
ggplot,
@@ -23,6 +28,15 @@
2328

2429
LetsPlot.setup_html()
2530

31+
# Theme tokens
32+
THEME = os.getenv("ANYPLOT_THEME", "light")
33+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
34+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
35+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
36+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
37+
38+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"]
39+
2640
# Data: Employee performance scores by department and experience level
2741
np.random.seed(42)
2842

@@ -33,22 +47,19 @@
3347
for dept in departments:
3448
for exp in experience_levels:
3549
n = 40
36-
# Create realistic distributions with variation
3750
if exp == "Junior":
38-
base = 60 + np.random.choice([0, 5, 10])
51+
base = 50 + np.random.choice([0, 5, 10])
3952
spread = 12
4053
elif exp == "Mid-Level":
41-
base = 72 + np.random.choice([0, 3, 6])
54+
base = 60 + np.random.choice([0, 3, 6])
4255
spread = 10
43-
else: # Senior
44-
base = 82 + np.random.choice([0, 2, 4])
56+
else:
57+
base = 72 + np.random.choice([0, 2, 4])
4558
spread = 8
4659

47-
# Add department-specific variation
4860
dept_offset = {"Engineering": 3, "Marketing": 0, "Sales": -2, "Operations": 1}[dept]
4961
values = np.random.normal(base + dept_offset, spread, n)
5062

51-
# Add some outliers for boxplot visualization
5263
if np.random.random() > 0.5:
5364
outlier_low = base - 3 * spread + np.random.randn(2) * 2
5465
outlier_high = base + 3 * spread + np.random.randn(2) * 2
@@ -58,34 +69,35 @@
5869
data.append({"Department": dept, "Experience": exp, "Performance Score": v})
5970

6071
df = pd.DataFrame(data)
61-
62-
# Ensure proper ordering
6372
df["Experience"] = pd.Categorical(df["Experience"], categories=experience_levels, ordered=True)
6473
df["Department"] = pd.Categorical(df["Department"], categories=departments, ordered=True)
6574

6675
# Plot
6776
plot = (
6877
ggplot(df, aes(x="Department", y="Performance Score", fill="Experience"))
6978
+ geom_boxplot(alpha=0.85, outlier_size=3, outlier_alpha=0.7, width=0.7)
70-
+ scale_fill_manual(values=["#306998", "#FFD43B", "#4CAF50"])
71-
+ labs(
72-
title="box-grouped · letsplot · pyplots.ai", x="Department", y="Performance Score (%)", fill="Experience Level"
73-
)
79+
+ scale_fill_manual(values=OKABE_ITO)
80+
+ labs(title="box-grouped · letsplot · anyplot.ai", x="Department", y="Performance Score", fill="Experience Level")
7481
+ theme_minimal()
7582
+ theme(
76-
plot_title=element_text(size=28, face="bold"),
77-
axis_title=element_text(size=22),
78-
axis_text=element_text(size=18),
79-
axis_text_x=element_text(size=18),
80-
legend_title=element_text(size=20),
81-
legend_text=element_text(size=18),
83+
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
84+
panel_background=element_rect(fill=PAGE_BG),
85+
panel_grid_major=element_line(color=INK_SOFT, size=0.3),
86+
plot_title=element_text(size=24, face="bold", color=INK),
87+
axis_title=element_text(size=20, color=INK),
88+
axis_text=element_text(size=16, color=INK_SOFT),
89+
legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
90+
legend_title=element_text(size=16, color=INK),
91+
legend_text=element_text(size=16, color=INK_SOFT),
8292
legend_position="right",
8393
)
8494
+ ggsize(1600, 900)
8595
)
8696

87-
# Save as PNG (scale 3x for 4800x2700 px)
88-
ggsave(plot, "plot.png", path=".", scale=3)
97+
# Save
98+
ggsave(plot, f"plot-{THEME}.png", scale=3)
99+
ggsave(plot, f"plot-{THEME}.html")
89100

90-
# Save as HTML for interactivity
91-
ggsave(plot, "plot.html", path=".")
101+
# Move files from lets-plot-images to current directory
102+
shutil.move(f"lets-plot-images/plot-{THEME}.png", f"plot-{THEME}.png")
103+
shutil.move(f"lets-plot-images/plot-{THEME}.html", f"plot-{THEME}.html")

0 commit comments

Comments
 (0)