Skip to content

Commit 30fd977

Browse files
feat(pygal): implement strip-basic (#5673)
## Implementation: `strip-basic` - python/pygal Implements the **python/pygal** version of `strip-basic`. **File:** `plots/strip-basic/implementations/python/pygal.py` **Parent Issue:** #975 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25342549595)* --------- 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 035ef11 commit 30fd977

2 files changed

Lines changed: 216 additions & 167 deletions

File tree

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,96 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
strip-basic: Basic Strip Plot
3-
Library: pygal 3.1.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: pygal 3.1.0 | Python 3.13.13
4+
Quality: 87/100 | Updated: 2026-05-04
55
"""
66

7-
import random
7+
import os
88

9+
import numpy as np
910
import pygal
1011
from pygal.style import Style
1112

1213

13-
# Set seed for reproducibility
14-
random.seed(42)
14+
# Theme tokens
15+
THEME = os.getenv("ANYPLOT_THEME", "light")
16+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
17+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
18+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
1519

16-
# Data - Survey response scores by department
20+
OKABE_ITO = ("#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442")
21+
22+
# Data — employee satisfaction scores by department (1–10 scale)
23+
np.random.seed(42)
1724
categories = ["Engineering", "Marketing", "Sales", "Support"]
1825
n_per_category = 40
1926

20-
# Generate realistic survey scores (1-10 scale) with different distributions
21-
data = {
22-
"Engineering": [random.gauss(7.5, 1.2) for _ in range(n_per_category)],
23-
"Marketing": [random.gauss(6.8, 1.5) for _ in range(n_per_category)],
24-
"Sales": [random.gauss(7.2, 1.0) for _ in range(n_per_category)],
25-
"Support": [random.gauss(6.5, 1.8) for _ in range(n_per_category)],
27+
scores = {
28+
"Engineering": np.clip(np.random.normal(7.5, 1.2, n_per_category), 1, 10),
29+
"Marketing": np.clip(np.random.normal(6.8, 1.5, n_per_category), 1, 10),
30+
"Sales": np.clip(np.random.normal(7.2, 1.0, n_per_category), 1, 10),
31+
"Support": np.clip(np.random.normal(6.5, 1.8, n_per_category), 1, 10),
2632
}
2733

28-
# Clip values to realistic 1-10 range
29-
for cat in data:
30-
data[cat] = [max(1, min(10, v)) for v in data[cat]]
31-
32-
# Custom style for 4800x2700 output
34+
# Style
3335
custom_style = Style(
34-
background="white",
35-
plot_background="white",
36-
foreground="#333333",
37-
foreground_strong="#333333",
38-
foreground_subtle="#666666",
39-
colors=("#306998", "#FFD43B", "#4B8BBE", "#FFE873"),
36+
background=PAGE_BG,
37+
plot_background=PAGE_BG,
38+
foreground=INK,
39+
foreground_strong=INK,
40+
foreground_subtle=INK_MUTED,
41+
colors=OKABE_ITO,
4042
title_font_size=72,
4143
label_font_size=48,
4244
major_label_font_size=42,
4345
legend_font_size=42,
4446
value_font_size=36,
45-
opacity=0.65,
47+
opacity=0.60,
4648
stroke_width=0,
4749
)
4850

49-
# Create XY chart for strip plot
51+
# Chart
5052
chart = pygal.XY(
5153
width=4800,
5254
height=2700,
5355
style=custom_style,
54-
title="strip-basic · pygal · pyplots.ai",
56+
title="strip-basic · pygal · anyplot.ai",
5557
x_title="Department",
56-
y_title="Survey Score",
58+
y_title="Satisfaction Score (1–10)",
5759
show_legend=True,
5860
legend_at_bottom=True,
59-
legend_at_bottom_columns=4,
61+
legend_at_bottom_columns=5,
6062
show_x_guides=False,
6163
show_y_guides=True,
6264
stroke=False,
63-
dots_size=12,
65+
dots_size=17,
6466
x_label_rotation=0,
6567
)
6668

67-
# Set x-axis range and labels
69+
# Pygal-native tooltip formatting: value_formatter applies to all hover labels
70+
chart.value_formatter = lambda y: f"{y:.1f}"
71+
72+
# X-axis labels aligned to integer positions
6873
chart.x_labels = ["", "Engineering", "Marketing", "Sales", "Support", ""]
6974
chart.xrange = (0, 5)
7075

71-
# Add jittered points for each category
76+
# Add jittered points per category; per-point dicts enrich HTML tooltips
7277
for i, cat in enumerate(categories, start=1):
73-
# Create jittered x-positions around the category index
74-
points = [(i + random.uniform(-0.25, 0.25), val) for val in data[cat]]
78+
jitter = np.random.uniform(-0.25, 0.25, n_per_category)
79+
points = [
80+
{"value": (float(i + j), float(v)), "label": f"{cat}: {v:.1f}"}
81+
for j, v in zip(jitter, scores[cat], strict=True)
82+
]
7583
chart.add(cat, points)
7684

77-
# Save outputs
78-
chart.render_to_file("plot.html")
79-
chart.render_to_png("plot.png")
85+
# Mean reference markers — one dot per category at the mean position (5th Okabe color: #E69F00)
86+
# Positioned at exact integer x (no jitter) so they stand apart from the scattered data cloud
87+
mean_points = [
88+
{"value": (float(i), float(np.mean(scores[cat]))), "label": f"Mean {cat}: {np.mean(scores[cat]):.2f}"}
89+
for i, cat in enumerate(categories, start=1)
90+
]
91+
chart.add("─ Mean", mean_points)
92+
93+
# Save
94+
chart.render_to_png(f"plot-{THEME}.png")
95+
with open(f"plot-{THEME}.html", "wb") as f:
96+
f.write(chart.render())

0 commit comments

Comments
 (0)