|
1 | 1 | """ pyplots.ai |
2 | 2 | box-basic: Basic Box 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: 90/100 | Created: 2025-12-23 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import re |
| 8 | + |
| 9 | +import cairosvg |
7 | 10 | import numpy as np |
8 | 11 | import pygal |
9 | 12 | from pygal.style import Style |
|
20 | 23 | "HR": np.random.normal(58000, 8000, 100), |
21 | 24 | } |
22 | 25 |
|
23 | | -# Add some outliers to demonstrate box plot features |
| 26 | +# Add outliers to demonstrate box plot features |
24 | 27 | data["Engineering"] = np.append(data["Engineering"], [130000, 135000, 40000]) |
25 | 28 | data["Sales"] = np.append(data["Sales"], [120000, 25000]) |
26 | 29 |
|
27 | | -# Custom style using PyPlots colors |
| 30 | +# Compute medians for annotation storytelling |
| 31 | +medians = {cat: float(np.median(data[cat])) for cat in categories} |
| 32 | +highest_dept = max(medians, key=medians.get) |
| 33 | +lowest_dept = min(medians, key=medians.get) |
| 34 | + |
| 35 | +# Custom style for 4800x2700 canvas — strong, vivid colors |
28 | 36 | custom_style = Style( |
29 | 37 | background="white", |
30 | | - plot_background="white", |
| 38 | + plot_background="#FAFAFA", |
31 | 39 | foreground="#333333", |
32 | | - foreground_strong="#333333", |
33 | | - foreground_subtle="#666666", |
34 | | - colors=("#306998", "#FFD43B", "#4CAF50", "#FF5722", "#9C27B0"), |
35 | | - title_font_size=60, |
36 | | - label_font_size=40, |
37 | | - major_label_font_size=36, |
38 | | - legend_font_size=36, |
39 | | - value_font_size=32, |
| 40 | + foreground_strong="#222222", |
| 41 | + foreground_subtle="#E0E0E0", |
| 42 | + colors=("#306998", "#E69F00", "#009E73", "#D55E00", "#7B68EE"), |
| 43 | + title_font_size=72, |
| 44 | + label_font_size=48, |
| 45 | + major_label_font_size=44, |
| 46 | + legend_font_size=44, |
| 47 | + value_font_size=36, |
| 48 | + opacity=1.0, |
| 49 | + opacity_hover=1.0, |
40 | 50 | ) |
41 | 51 |
|
42 | | -# Create box chart |
| 52 | +# Create box chart — set y range to focus on actual data, not starting at 0 |
43 | 53 | chart = pygal.Box( |
44 | 54 | width=4800, |
45 | 55 | height=2700, |
|
49 | 59 | y_title="Salary ($)", |
50 | 60 | show_legend=True, |
51 | 61 | legend_at_bottom=True, |
52 | | - legend_box_size=24, |
| 62 | + legend_at_bottom_columns=5, |
| 63 | + legend_box_size=36, |
| 64 | + truncate_legend=-1, |
| 65 | + truncate_label=-1, |
53 | 66 | show_y_guides=True, |
54 | 67 | show_x_guides=False, |
55 | | - margin=50, |
| 68 | + margin=80, |
| 69 | + spacing=40, |
56 | 70 | box_mode="tukey", |
| 71 | + range=(5000, 145000), |
| 72 | + y_labels=[20000, 40000, 60000, 80000, 100000, 120000, 140000], |
57 | 73 | ) |
58 | 74 |
|
59 | 75 | # Add data for each category |
60 | 76 | for category in categories: |
61 | 77 | chart.add(category, data[category].tolist()) |
62 | 78 |
|
| 79 | +# Render SVG, then post-process for visual improvements |
| 80 | +svg_string = chart.render().decode("utf-8") |
| 81 | + |
| 82 | +# Fix 1: Increase box fill opacity from 0.2 to 0.7 (pygal hardcodes subtle-fill) |
| 83 | +svg_string = svg_string.replace(".subtle-fill{fill-opacity:.2}", ".subtle-fill{fill-opacity:.7}") |
| 84 | + |
| 85 | +# Fix 2: Enlarge outlier dots (pygal hardcodes r=3 for box outliers) |
| 86 | +svg_string = re.sub(r'(<circle[^>]*) r="3" (class="subtle-fill)', r'\1 r="10" \2', svg_string) |
| 87 | + |
| 88 | +# Fix 3: Add storytelling annotation as subtitle |
| 89 | +annotation_svg = ( |
| 90 | + f'<text x="2400" y="200" text-anchor="middle" ' |
| 91 | + f'font-size="38" fill="#555555" font-family="sans-serif" font-style="italic">' |
| 92 | + f"Highest median: {highest_dept} (${medians[highest_dept]:,.0f})" |
| 93 | + f" \u00b7 Lowest median: {lowest_dept} (${medians[lowest_dept]:,.0f})" |
| 94 | + f" \u00b7 Gap: ${medians[highest_dept] - medians[lowest_dept]:,.0f}" |
| 95 | + f"</text>" |
| 96 | +) |
| 97 | +svg_string = svg_string.replace("</svg>", f"{annotation_svg}</svg>") |
| 98 | + |
63 | 99 | # Save outputs |
64 | | -chart.render_to_file("plot.html") |
65 | | -chart.render_to_png("plot.png") |
| 100 | +with open("plot.html", "w") as f: |
| 101 | + f.write(svg_string) |
| 102 | + |
| 103 | +cairosvg.svg2png(bytestring=svg_string.encode("utf-8"), write_to="plot.png") |
0 commit comments