|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import random |
| 7 | +import os |
8 | 8 |
|
| 9 | +import numpy as np |
9 | 10 | import pygal |
10 | 11 | from pygal.style import Style |
11 | 12 |
|
12 | 13 |
|
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" |
15 | 19 |
|
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) |
17 | 24 | categories = ["Engineering", "Marketing", "Sales", "Support"] |
18 | 25 | n_per_category = 40 |
19 | 26 |
|
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), |
26 | 32 | } |
27 | 33 |
|
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 |
33 | 35 | 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, |
40 | 42 | title_font_size=72, |
41 | 43 | label_font_size=48, |
42 | 44 | major_label_font_size=42, |
43 | 45 | legend_font_size=42, |
44 | 46 | value_font_size=36, |
45 | | - opacity=0.65, |
| 47 | + opacity=0.60, |
46 | 48 | stroke_width=0, |
47 | 49 | ) |
48 | 50 |
|
49 | | -# Create XY chart for strip plot |
| 51 | +# Chart |
50 | 52 | chart = pygal.XY( |
51 | 53 | width=4800, |
52 | 54 | height=2700, |
53 | 55 | style=custom_style, |
54 | | - title="strip-basic · pygal · pyplots.ai", |
| 56 | + title="strip-basic · pygal · anyplot.ai", |
55 | 57 | x_title="Department", |
56 | | - y_title="Survey Score", |
| 58 | + y_title="Satisfaction Score (1–10)", |
57 | 59 | show_legend=True, |
58 | 60 | legend_at_bottom=True, |
59 | | - legend_at_bottom_columns=4, |
| 61 | + legend_at_bottom_columns=5, |
60 | 62 | show_x_guides=False, |
61 | 63 | show_y_guides=True, |
62 | 64 | stroke=False, |
63 | | - dots_size=12, |
| 65 | + dots_size=17, |
64 | 66 | x_label_rotation=0, |
65 | 67 | ) |
66 | 68 |
|
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 |
68 | 73 | chart.x_labels = ["", "Engineering", "Marketing", "Sales", "Support", ""] |
69 | 74 | chart.xrange = (0, 5) |
70 | 75 |
|
71 | | -# Add jittered points for each category |
| 76 | +# Add jittered points per category; per-point dicts enrich HTML tooltips |
72 | 77 | 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 | + ] |
75 | 83 | chart.add(cat, points) |
76 | 84 |
|
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