|
1 | 1 | """ pyplots.ai |
2 | 2 | density-basic: Basic Density 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.14 |
| 4 | +Quality: /100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
11 | 11 |
|
12 | 12 | # Data - simulated test scores showing slightly left-skewed distribution |
13 | 13 | np.random.seed(42) |
14 | | -values = np.concatenate( |
| 14 | +scores = np.concatenate( |
15 | 15 | [ |
16 | 16 | np.random.normal(75, 8, 200), # Main cluster around 75 |
17 | | - np.random.normal(60, 5, 50), # Smaller cluster showing slight left skew |
| 17 | + np.random.normal(58, 5, 50), # Smaller cluster showing left skew |
18 | 18 | ] |
19 | 19 | ) |
20 | 20 |
|
21 | | - |
22 | | -# Compute KDE using Gaussian kernel |
23 | | -x_range = np.linspace(values.min() - 10, values.max() + 10, 200) |
24 | | -n = len(values) |
25 | | -bandwidth = n ** (-1 / 5) * np.std(values) # Scott's rule |
| 21 | +# Compute KDE using Gaussian kernel with Scott's rule bandwidth |
| 22 | +x_range = np.linspace(scores.min() - 10, scores.max() + 10, 300) |
| 23 | +n = len(scores) |
| 24 | +bandwidth = n ** (-1 / 5) * np.std(scores) |
26 | 25 | density = np.zeros_like(x_range) |
27 | | -for xi in values: |
| 26 | +for xi in scores: |
28 | 27 | density += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2) |
29 | 28 | density /= n * bandwidth * np.sqrt(2 * np.pi) |
30 | 29 |
|
31 | | -# Custom style for 4800x2700 px (scaled 3x from template for large canvas) |
| 30 | +# Custom style — clean white, refined typography for 4800x2700 canvas |
32 | 31 | custom_style = Style( |
33 | 32 | background="white", |
34 | 33 | plot_background="white", |
35 | | - foreground="#333", |
36 | | - foreground_strong="#333", |
37 | | - foreground_subtle="#666", |
38 | | - colors=("#306998",), |
39 | | - title_font_size=56, |
40 | | - label_font_size=42, |
41 | | - major_label_font_size=36, |
42 | | - legend_font_size=36, |
| 34 | + foreground="#333333", |
| 35 | + foreground_strong="#333333", |
| 36 | + foreground_subtle="#e0e0e0", |
| 37 | + colors=("#306998", "#1a4971"), |
| 38 | + title_font_size=72, |
| 39 | + label_font_size=44, |
| 40 | + major_label_font_size=40, |
| 41 | + legend_font_size=40, |
43 | 42 | value_font_size=32, |
| 43 | + stroke_width=5, |
| 44 | + opacity=0.70, |
| 45 | + opacity_hover=0.85, |
44 | 46 | ) |
45 | 47 |
|
46 | 48 | # Create XY chart for continuous density curve |
|
50 | 52 | style=custom_style, |
51 | 53 | title="density-basic · pygal · pyplots.ai", |
52 | 54 | x_title="Test Score (points)", |
53 | | - y_title="Probability Density", |
| 55 | + y_title="Density", |
54 | 56 | show_dots=False, |
55 | | - stroke_style={"width": 3}, |
56 | 57 | fill=True, |
57 | 58 | show_legend=False, |
58 | 59 | show_y_guides=True, |
59 | 60 | show_x_guides=False, |
| 61 | + stroke_style={"width": 5, "linecap": "round"}, |
| 62 | + truncate_label=-1, |
| 63 | + css=[ |
| 64 | + "file://style.css", |
| 65 | + "inline:.plot .background {fill: white; stroke: none !important;}", |
| 66 | + "inline:.axis .guides .line {stroke: #e8e8e8 !important; stroke-width: 1px;}", |
| 67 | + "inline:.axis .line {stroke: #cccccc !important; stroke-width: 1.5px;}", |
| 68 | + ], |
| 69 | + js=[], |
60 | 70 | ) |
61 | 71 |
|
62 | | -# Add density curve data as XY points |
| 72 | +# Add density curve as XY points |
63 | 73 | xy_data = [(float(x), float(y)) for x, y in zip(x_range, density, strict=True)] |
64 | 74 | chart.add("Density", xy_data) |
65 | 75 |
|
66 | | -# Add rug plot as small vertical marks along x-axis (sampled for clarity) |
67 | | -rug_height = max(density) * 0.02 # Small height for rug marks |
68 | | -rug_sample = values[::5] # Sample every 5th point to avoid clutter |
| 76 | +# Add rug plot as individual vertical marks along x-axis |
| 77 | +rug_height = max(density) * 0.06 |
| 78 | +rug_sample = scores[::3] # Sample every 3rd point for good coverage |
69 | 79 | rug_data = [] |
70 | | -for xi in rug_sample: |
71 | | - rug_data.append((float(xi), 0)) |
| 80 | +for xi in sorted(rug_sample): |
| 81 | + rug_data.append((float(xi), 0.0)) |
72 | 82 | rug_data.append((float(xi), float(rug_height))) |
73 | | - rug_data.append((float(xi), 0)) |
| 83 | + rug_data.append((float(xi), 0.0)) |
74 | 84 |
|
75 | | -# Add rug marks as a separate series with thinner stroke |
76 | | -chart.add("Observations", rug_data, stroke_style={"width": 1}, show_dots=False, fill=False) |
| 85 | +chart.add("Observations", rug_data, stroke_style={"width": 2.5, "linecap": "round"}, show_dots=False, fill=False) |
77 | 86 |
|
78 | | -# Save as PNG and HTML |
| 87 | +# Save outputs |
79 | 88 | chart.render_to_png("plot.png") |
80 | 89 | chart.render_to_file("plot.html") |
0 commit comments