Skip to content

Commit 3d67d5f

Browse files
feat(seaborn): implement gauge-basic (#5392)
## Implementation: `gauge-basic` - python/seaborn Implements the **python/seaborn** version of `gauge-basic`. **File:** `plots/gauge-basic/implementations/python/seaborn.py` **Parent Issue:** #857 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24930951204)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 33f55ae commit 3d67d5f

2 files changed

Lines changed: 270 additions & 258 deletions

File tree

Lines changed: 96 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,136 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
gauge-basic: Basic Gauge Chart
3-
Library: seaborn 0.13.2 | Python 3.13.11
4-
Quality: 90/100 | Created: 2025-12-23
3+
Library: seaborn 0.13.2 | Python 3.14.4
4+
Quality: 89/100 | Updated: 2026-04-25
55
"""
66

7+
import os
8+
9+
import matplotlib.patheffects as pe
710
import matplotlib.pyplot as plt
811
import numpy as np
9-
import pandas as pd
1012
import seaborn as sns
13+
from matplotlib.patches import Circle, Wedge
14+
15+
16+
# Theme tokens
17+
THEME = os.getenv("ANYPLOT_THEME", "light")
18+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
19+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
20+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
1121

22+
# Okabe-Ito zone palette (semantic gauge mapping: vermillion / orange / brand green)
23+
ZONE_LOW, ZONE_MED, ZONE_HIGH = sns.color_palette(["#D55E00", "#E69F00", "#009E73"])
1224

13-
# Data - Sales performance gauge
14-
value = 72 # Current sales performance
25+
# Data — sales performance gauge
26+
value = 72
1527
min_value = 0
1628
max_value = 100
17-
thresholds = [30, 70] # Zone boundaries
29+
thresholds = [30, 70]
1830

19-
# Create figure with larger gauge
20-
fig, ax = plt.subplots(figsize=(16, 9))
21-
sns.set_theme(style="white")
22-
23-
# Gauge parameters - larger for better canvas utilization
24-
center = (0.5, 0.30)
25-
radius = 0.42
26-
width = 0.18
31+
# Gauge geometry
32+
center = (0.5, 0.45)
33+
radius = 0.40
34+
width = 0.16
2735
start_angle = 180
2836
end_angle = 0
2937
angle_range = start_angle - end_angle
3038
value_range = max_value - min_value
3139

32-
# Colorblind-safe palette using seaborn's colorblind palette
33-
cb_palette = sns.color_palette("colorblind", 3)
34-
zone_colors = [cb_palette[2], cb_palette[1], cb_palette[0]] # Blue-ish, Orange, Green-ish order for low-med-high
35-
36-
# Draw gauge arc segments using seaborn scatterplot for the zone markers
37-
# Create data for zone indicator points along the arc
38-
n_points_per_zone = 50
3940
zone_boundaries = [min_value] + thresholds + [max_value]
40-
zone_data = []
41+
zone_names = ["Low", "Medium", "High"]
42+
zone_colors = [ZONE_LOW, ZONE_MED, ZONE_HIGH]
43+
44+
# Plot
45+
sns.set_theme(
46+
style="white",
47+
rc={
48+
"figure.facecolor": PAGE_BG,
49+
"axes.facecolor": PAGE_BG,
50+
"text.color": INK,
51+
"axes.labelcolor": INK,
52+
"xtick.color": INK_SOFT,
53+
"ytick.color": INK_SOFT,
54+
},
55+
)
56+
fig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG)
57+
ax.set_facecolor(PAGE_BG)
4158

59+
# Arc — three coloured zones drawn as smooth Wedge patches
4260
for i in range(len(zone_boundaries) - 1):
43-
zone_start = zone_boundaries[i]
44-
zone_end = zone_boundaries[i + 1]
45-
zone_name = ["Low", "Medium", "High"][i]
46-
47-
for v in np.linspace(zone_start, zone_end, n_points_per_zone, endpoint=(i == len(zone_boundaries) - 2)):
48-
angle = start_angle - (v - min_value) / value_range * angle_range
49-
rad = np.radians(angle)
50-
# Points along the middle of the arc width
51-
for r_offset in np.linspace(-width / 2 + 0.01, width / 2 - 0.01, 8):
52-
r = radius - width / 2 + r_offset + width / 2
53-
x = center[0] + r * np.cos(rad)
54-
y = center[1] + r * np.sin(rad)
55-
zone_data.append({"x": x, "y": y, "zone": zone_name, "value": v})
56-
57-
df_zones = pd.DataFrame(zone_data)
58-
59-
# Use seaborn scatterplot to draw the gauge arc zones with denser points for smooth appearance
60-
sns.scatterplot(
61-
data=df_zones,
62-
x="x",
63-
y="y",
64-
hue="zone",
65-
hue_order=["Low", "Medium", "High"],
66-
palette=[zone_colors[0], zone_colors[1], zone_colors[2]],
67-
s=180,
68-
marker="o",
69-
edgecolor="none",
70-
alpha=1.0,
71-
legend=False,
72-
ax=ax,
73-
)
61+
z_start_angle = start_angle - (zone_boundaries[i] - min_value) / value_range * angle_range
62+
z_end_angle = start_angle - (zone_boundaries[i + 1] - min_value) / value_range * angle_range
63+
ax.add_patch(
64+
Wedge(
65+
center=center,
66+
r=radius + width / 2,
67+
theta1=z_end_angle,
68+
theta2=z_start_angle,
69+
width=width,
70+
facecolor=zone_colors[i],
71+
edgecolor="none",
72+
linewidth=0,
73+
zorder=2,
74+
)
75+
)
7476

75-
# Add thin white arc lines at zone boundaries using seaborn for visual separation
77+
# Boundary cuts between zones — short radial line, theme-visible color
7678
for threshold in thresholds:
7779
boundary_angle = start_angle - (threshold - min_value) / value_range * angle_range
7880
rad = np.radians(boundary_angle)
79-
boundary_line_data = []
80-
for r in np.linspace(radius - width, radius, 10):
81-
boundary_line_data.append({"x": center[0] + r * np.cos(rad), "y": center[1] + r * np.sin(rad)})
82-
boundary_df = pd.DataFrame(boundary_line_data)
83-
sns.lineplot(data=boundary_df, x="x", y="y", color="white", linewidth=3, ax=ax, legend=False)
84-
85-
# Create needle indicator data point using seaborn
81+
rs = (radius - width / 2, radius + width / 2)
82+
ax.plot(
83+
[center[0] + r * np.cos(rad) for r in rs],
84+
[center[1] + r * np.sin(rad) for r in rs],
85+
color=INK_SOFT,
86+
linewidth=3,
87+
zorder=3,
88+
solid_capstyle="butt",
89+
)
90+
91+
# Needle
8692
needle_angle = start_angle - (value - min_value) / value_range * angle_range
8793
needle_rad = np.radians(needle_angle)
88-
needle_length = radius - width / 2
89-
90-
# Draw needle line
94+
needle_length = radius + width / 2 - 0.015
9195
needle_tip_x = center[0] + needle_length * np.cos(needle_rad)
9296
needle_tip_y = center[1] + needle_length * np.sin(needle_rad)
97+
ax.plot([center[0], needle_tip_x], [center[1], needle_tip_y], color=INK, linewidth=6, zorder=12, solid_capstyle="round")
9398

94-
# Use seaborn lineplot for the needle
95-
needle_df = pd.DataFrame({"x": [center[0], needle_tip_x], "y": [center[1], needle_tip_y]})
96-
sns.lineplot(data=needle_df, x="x", y="y", color="#2C3E50", linewidth=6, ax=ax)
97-
98-
# Draw needle tip marker using seaborn scatterplot - larger and more prominent
99-
tip_df = pd.DataFrame({"x": [needle_tip_x], "y": [needle_tip_y]})
100-
# White outline for contrast
101-
sns.scatterplot(data=tip_df, x="x", y="y", s=900, color="white", marker="v", ax=ax, zorder=9)
102-
# Main tip marker
103-
sns.scatterplot(data=tip_df, x="x", y="y", s=700, color="#E74C3C", marker="v", ax=ax, zorder=10)
104-
105-
# Draw center hub using seaborn scatterplot
106-
hub_df = pd.DataFrame({"x": [center[0]], "y": [center[1]]})
107-
sns.scatterplot(data=hub_df, x="x", y="y", s=800, color="#2C3E50", marker="o", ax=ax, zorder=11)
99+
# Hub — outer ring (page bg) + inner disc (ink) for clean contrast in both themes
100+
ax.add_patch(Circle(center, radius=0.045, facecolor=PAGE_BG, edgecolor="none", zorder=13))
101+
ax.add_patch(Circle(center, radius=0.035, facecolor=INK, edgecolor="none", zorder=14))
108102

109103
# Value display
110-
ax.text(
111-
center[0], center[1] - 0.22, f"{value}%", ha="center", va="center", fontsize=52, fontweight="bold", color="#2C3E50"
112-
)
113-
114-
# Min and max labels
115-
ax.text(
116-
center[0] - radius + width / 2,
117-
center[1] - 0.10,
118-
f"{min_value}",
119-
ha="center",
120-
va="top",
121-
fontsize=22,
122-
color="#555555",
123-
)
124-
ax.text(
125-
center[0] + radius - width / 2,
126-
center[1] - 0.10,
127-
f"{max_value}",
128-
ha="center",
129-
va="top",
130-
fontsize=22,
131-
color="#555555",
132-
)
133-
134-
# Zone labels on the arc
135-
zone_label_angles = [
136-
start_angle - (15 / value_range) * angle_range,
137-
start_angle - (50 / value_range) * angle_range,
138-
start_angle - (85 / value_range) * angle_range,
104+
ax.text(center[0], center[1] - 0.20, f"{value}%", ha="center", va="center", fontsize=56, fontweight="bold", color=INK)
105+
106+
# Min / max endpoint labels
107+
for vlabel, x_off in [(min_value, -1), (max_value, 1)]:
108+
ax.text(
109+
center[0] + x_off * radius, center[1] - 0.06, f"{vlabel}", ha="center", va="top", fontsize=20, color=INK_SOFT
110+
)
111+
112+
# Zone labels on the arc — white text with dark stroke for legibility on every zone in both themes
113+
zone_label_centers = [
114+
(thresholds[0] - min_value) / 2 + min_value,
115+
(thresholds[0] + thresholds[1]) / 2,
116+
(thresholds[1] + max_value) / 2,
139117
]
140-
zone_label_names = ["Low", "Medium", "High"]
141-
label_radius = radius - width / 2
142-
143-
for angle, label in zip(zone_label_angles, zone_label_names, strict=True):
118+
for v_center, label in zip(zone_label_centers, zone_names, strict=True):
119+
angle = start_angle - (v_center - min_value) / value_range * angle_range
144120
rad = np.radians(angle)
145-
x = center[0] + label_radius * np.cos(rad)
146-
y = center[1] + label_radius * np.sin(rad)
147-
# Text shadow/outline for better contrast on colored backgrounds
148-
for dx, dy in [(-1, -1), (-1, 1), (1, -1), (1, 1), (-1, 0), (1, 0), (0, -1), (0, 1)]:
149-
ax.text(
150-
x + dx * 0.003,
151-
y + dy * 0.003,
152-
label,
153-
ha="center",
154-
va="center",
155-
fontsize=18,
156-
color="#1a1a1a",
157-
fontweight="bold",
158-
)
159-
ax.text(x, y, label, ha="center", va="center", fontsize=18, color="white", fontweight="bold")
121+
lx = center[0] + radius * np.cos(rad)
122+
ly = center[1] + radius * np.sin(rad)
123+
txt = ax.text(lx, ly, label, ha="center", va="center", fontsize=22, color="white", fontweight="bold", zorder=5)
124+
txt.set_path_effects([pe.withStroke(linewidth=3, foreground="#1A1A17")])
160125

161-
# Title and subtitle
162-
ax.set_title("gauge-basic · seaborn · pyplots.ai", fontsize=28, fontweight="bold", pad=20, color="#333333")
163-
ax.text(center[0], 0.95, "Sales Performance", ha="center", va="top", fontsize=24, color="#555555")
126+
# Title
127+
ax.set_title("gauge-basic · seaborn · anyplot.ai", fontsize=24, fontweight="medium", pad=20, color=INK)
164128

165-
# Axis settings
129+
# Axis settings — purely a canvas
166130
ax.set_xlim(0, 1)
167131
ax.set_ylim(0, 1)
168132
ax.set_aspect("equal")
169133
ax.axis("off")
170134

171135
plt.tight_layout()
172-
plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white")
136+
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)

0 commit comments

Comments
 (0)