Skip to content

Commit b758d79

Browse files
feat(matplotlib): implement gauge-basic (#5391)
## Implementation: `gauge-basic` - python/matplotlib Implements the **python/matplotlib** version of `gauge-basic`. **File:** `plots/gauge-basic/implementations/python/matplotlib.py` **Parent Issue:** #857 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24930860636)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 15156a2 commit b758d79

2 files changed

Lines changed: 227 additions & 164 deletions

File tree

Lines changed: 66 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,51 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
gauge-basic: Basic Gauge Chart
3-
Library: matplotlib 3.10.8 | Python 3.13.11
4-
Quality: 95/100 | Created: 2025-12-23
3+
Library: matplotlib 3.10.9 | Python 3.14.4
4+
Quality: 91/100 | Updated: 2026-04-25
55
"""
66

7+
import os
8+
79
import matplotlib.patches as mpatches
810
import matplotlib.pyplot as plt
911
import numpy as np
1012

1113

14+
# Theme tokens
15+
THEME = os.getenv("ANYPLOT_THEME", "light")
16+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
17+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
18+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
19+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
20+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
21+
22+
# Okabe-Ito zone colors (colorblind-safe red/yellow/green)
23+
ZONE_BAD = "#D55E00" # vermillion
24+
ZONE_WARN = "#E69F00" # orange
25+
ZONE_GOOD = "#009E73" # bluish green (brand)
26+
1227
# Data
1328
value = 72 # Current sales value
1429
min_value = 0
1530
max_value = 100
16-
thresholds = [30, 70] # Boundaries for red/yellow/green zones
31+
thresholds = [30, 70] # Boundaries for bad/warn/good zones
1732

18-
# Calculate angles (gauge spans from 180° to 0°, i.e., left to right)
19-
angle_range = 180 # Semi-circular gauge
33+
# Geometry: gauge spans from 180° (left) to 0° (right)
34+
angle_range = 180
2035
value_normalized = (value - min_value) / (max_value - min_value)
21-
needle_angle = 180 - value_normalized * angle_range # Convert to degrees (180=left, 0=right)
36+
needle_angle = 180 - value_normalized * angle_range
2237

23-
# Create plot (4800x2700 px)
24-
fig, ax = plt.subplots(figsize=(16, 9))
38+
# Plot
39+
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
40+
ax.set_facecolor(PAGE_BG)
2541

26-
# Draw gauge background zones (wedges)
27-
zone_colors = ["#E74C3C", "#F1C40F", "#2ECC71"] # Red, Yellow, Green
42+
# Background zone wedges
43+
zone_colors = [ZONE_BAD, ZONE_WARN, ZONE_GOOD]
2844
zone_boundaries = [min_value] + thresholds + [max_value]
2945

3046
for i in range(len(zone_colors)):
3147
start_norm = (zone_boundaries[i] - min_value) / (max_value - min_value)
3248
end_norm = (zone_boundaries[i + 1] - min_value) / (max_value - min_value)
33-
# Convert to angles (180° to 0°)
3449
theta1 = 180 - end_norm * angle_range
3550
theta2 = 180 - start_norm * angle_range
3651
wedge = mpatches.Wedge(
@@ -40,47 +55,43 @@
4055
theta2=theta2,
4156
width=0.3,
4257
facecolor=zone_colors[i],
43-
edgecolor="white",
58+
edgecolor=PAGE_BG,
4459
linewidth=2,
4560
)
4661
ax.add_patch(wedge)
4762

48-
# Draw inner arc (white background for cleaner look)
49-
inner_circle = mpatches.Wedge(center=(0, 0), r=0.65, theta1=0, theta2=180, facecolor="white", edgecolor="none")
63+
# Inner cutout to clean the dial center (matches page background)
64+
inner_circle = mpatches.Wedge(center=(0, 0), r=0.65, theta1=0, theta2=180, facecolor=PAGE_BG, edgecolor="none")
5065
ax.add_patch(inner_circle)
5166

52-
# Draw the needle
53-
needle_rad = np.radians(needle_angle)
54-
needle_length = 0.75
55-
needle_x = needle_length * np.cos(needle_rad)
56-
needle_y = needle_length * np.sin(needle_rad)
57-
58-
# Needle line
59-
ax.plot([0, needle_x], [0, needle_y], color="#2C3E50", linewidth=6, solid_capstyle="round")
60-
61-
# Needle center cap
62-
center_circle = plt.Circle((0, 0), 0.08, color="#2C3E50", zorder=10)
63-
ax.add_patch(center_circle)
67+
# Tick marks: major (with labels) and minor (between)
68+
major_ticks = [0, 25, 50, 75, 100]
69+
minor_ticks = [t for t in range(0, 101, 5) if t not in major_ticks]
6470

65-
# Add tick marks and labels around the gauge
66-
tick_values = [0, 25, 50, 75, 100]
67-
for tick in tick_values:
71+
for tick in minor_ticks:
6872
tick_norm = (tick - min_value) / (max_value - min_value)
6973
tick_angle = 180 - tick_norm * angle_range
7074
tick_rad = np.radians(tick_angle)
75+
inner_r, outer_r = 1.02, 1.05
76+
ax.plot(
77+
[inner_r * np.cos(tick_rad), outer_r * np.cos(tick_rad)],
78+
[inner_r * np.sin(tick_rad), outer_r * np.sin(tick_rad)],
79+
color=INK_SOFT,
80+
linewidth=1.5,
81+
)
7182

72-
# Tick mark
73-
inner_r = 1.02
74-
outer_r = 1.08
83+
for tick in major_ticks:
84+
tick_norm = (tick - min_value) / (max_value - min_value)
85+
tick_angle = 180 - tick_norm * angle_range
86+
tick_rad = np.radians(tick_angle)
87+
inner_r, outer_r = 1.02, 1.09
7588
ax.plot(
7689
[inner_r * np.cos(tick_rad), outer_r * np.cos(tick_rad)],
7790
[inner_r * np.sin(tick_rad), outer_r * np.sin(tick_rad)],
78-
color="#333333",
91+
color=INK,
7992
linewidth=3,
8093
)
81-
82-
# Tick label
83-
label_r = 1.18
94+
label_r = 1.19
8495
ax.text(
8596
label_r * np.cos(tick_rad),
8697
label_r * np.sin(tick_rad),
@@ -89,23 +100,32 @@
89100
va="center",
90101
fontsize=18,
91102
fontweight="bold",
92-
color="#333333",
103+
color=INK_SOFT,
93104
)
94105

95-
# Display the current value prominently below the gauge
96-
ax.text(0, -0.25, f"{value}", ha="center", va="center", fontsize=48, fontweight="bold", color="#306998")
106+
# Needle
107+
needle_rad = np.radians(needle_angle)
108+
needle_length = 0.78
109+
needle_x = needle_length * np.cos(needle_rad)
110+
needle_y = needle_length * np.sin(needle_rad)
111+
ax.plot([0, needle_x], [0, needle_y], color=INK, linewidth=6, solid_capstyle="round", zorder=9)
112+
113+
# Center cap (two-tone for definition)
114+
ax.add_patch(plt.Circle((0, 0), 0.09, facecolor=INK, edgecolor="none", zorder=10))
115+
ax.add_patch(plt.Circle((0, 0), 0.035, facecolor=PAGE_BG, edgecolor="none", zorder=11))
97116

98-
# Add "Current Sales" label below value
99-
ax.text(0, -0.45, "Current Sales", ha="center", va="center", fontsize=20, color="#666666")
117+
# Value label and context
118+
ax.text(0, -0.25, f"{value}", ha="center", va="center", fontsize=56, fontweight="bold", color=ZONE_GOOD)
119+
ax.text(0, -0.47, "Current Sales", ha="center", va="center", fontsize=20, color=INK_MUTED)
100120

101-
# Add title
102-
ax.set_title("gauge-basic · matplotlib · pyplots.ai", fontsize=24, pad=20)
121+
# Title
122+
ax.set_title("gauge-basic · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK, pad=20)
103123

104-
# Set equal aspect ratio and limits
124+
# Frame
105125
ax.set_aspect("equal")
106126
ax.set_xlim(-1.5, 1.5)
107127
ax.set_ylim(-0.7, 1.5)
108128
ax.axis("off")
109129

110130
plt.tight_layout()
111-
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
131+
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)

0 commit comments

Comments
 (0)