Skip to content

Commit 06f4917

Browse files
feat(letsplot): implement dumbbell-basic (#9569)
## Implementation: `dumbbell-basic` - python/letsplot Implements the **python/letsplot** version of `dumbbell-basic`. **File:** `plots/dumbbell-basic/implementations/python/letsplot.py` **Parent Issue:** #945 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28480996002)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent f32ca35 commit 06f4917

2 files changed

Lines changed: 176 additions & 135 deletions

File tree

plots/dumbbell-basic/implementations/python/letsplot.py

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" anyplot.ai
22
dumbbell-basic: Basic Dumbbell Chart
3-
Library: letsplot 4.9.0 | Python 3.14.4
4-
Quality: 86/100 | Updated: 2026-04-26
3+
Library: letsplot 4.11.0 | Python 3.13.14
4+
Quality: 91/100 | Updated: 2026-06-30
55
"""
66

77
import os
@@ -19,6 +19,7 @@
1919
ggplot,
2020
ggsize,
2121
labs,
22+
layer_tooltips,
2223
scale_color_manual,
2324
scale_x_continuous,
2425
scale_y_continuous,
@@ -36,15 +37,14 @@
3637
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
3738
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
3839
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
39-
GRID = "#1A1A17" if THEME == "light" else "#F0EFE8"
40+
RULE = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)"
4041

41-
# Okabe-Ito palette — "After" comes first alphabetically → brand green
42-
BRAND = "#009E73"
43-
ACCENT = "#C475FD"
44-
SEGMENT = INK_SOFT
42+
# Imprint palette — position 1 = After (hero), position 2 = Before, position 5 = semantic decline
43+
BRAND = "#009E73" # position 1 — After scores / positive change direction
44+
LAVENDER = "#C475FD" # position 2 — Before scores
45+
DECLINE = "#AE3030" # position 5 — semantic red for regressions
4546

46-
# Data — Employee satisfaction scores before and after policy changes.
47-
# Mix of strong gains, modest shifts, and a regression to show full plot capability.
47+
# Data — Employee satisfaction scores before and after policy changes
4848
categories = [
4949
"Engineering",
5050
"Marketing",
@@ -65,46 +65,86 @@
6565
df = df.sort_values("diff", ascending=True).reset_index(drop=True)
6666
df["y_pos"] = range(len(df))
6767

68+
df_improved = df[df["diff"] > 0]
69+
df_declined = df[df["diff"] <= 0]
70+
6871
df_points = pd.concat(
6972
[
70-
pd.DataFrame({"y_pos": df["y_pos"], "value": df["before"], "period": "Before"}),
71-
pd.DataFrame({"y_pos": df["y_pos"], "value": df["after"], "period": "After"}),
73+
pd.DataFrame(
74+
{
75+
"y_pos": df["y_pos"],
76+
"value": df["before"],
77+
"period": "Before",
78+
"category": df["category"].values,
79+
"diff": df["diff"].values,
80+
}
81+
),
82+
pd.DataFrame(
83+
{
84+
"y_pos": df["y_pos"],
85+
"value": df["after"],
86+
"period": "After",
87+
"category": df["category"].values,
88+
"diff": df["diff"].values,
89+
}
90+
),
7291
]
7392
)
7493

75-
# Plot — horizontal dumbbell
94+
# Scale title fontsize for longer-than-baseline title (floor: 11px)
95+
title = "Employee Satisfaction · dumbbell-basic · python · letsplot · anyplot.ai"
96+
n = len(title)
97+
title_size = max(11, round(16 * 67 / n)) if n > 67 else 16
98+
99+
# Plot — horizontal dumbbell; segments color-coded by change direction
76100
plot = (
77101
ggplot()
78-
+ geom_segment(data=df, mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), size=1.5, color=SEGMENT)
79-
+ geom_point(data=df_points, mapping=aes(x="value", y="y_pos", color="period"), size=8)
80-
+ scale_color_manual(values=[BRAND, ACCENT], name="Period")
102+
+ geom_segment(
103+
data=df_improved,
104+
mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"),
105+
color=BRAND,
106+
size=1.2,
107+
alpha=0.65,
108+
)
109+
+ geom_segment(
110+
data=df_declined,
111+
mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"),
112+
color=DECLINE,
113+
size=1.2,
114+
alpha=0.65,
115+
)
116+
+ geom_point(
117+
data=df_points,
118+
mapping=aes(x="value", y="y_pos", color="period"),
119+
size=5,
120+
tooltips=layer_tooltips().line("@category").line("@period: @value").line("Change: @diff"),
121+
)
122+
+ scale_color_manual(values=[BRAND, LAVENDER], name="Period")
81123
+ scale_x_continuous(limits=[50, 95])
82124
+ scale_y_continuous(breaks=list(range(len(df))), labels=df["category"].tolist())
83-
+ labs(
84-
x="Satisfaction Score", y="Department", title="Employee Satisfaction · dumbbell-basic · letsplot · anyplot.ai"
85-
)
86-
+ ggsize(1600, 900)
125+
+ labs(x="Satisfaction Score", y="Department", title=title)
126+
+ ggsize(800, 450)
87127
+ theme_minimal()
88128
+ theme(
89129
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
90130
panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
91-
legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),
92-
panel_grid_major_x=element_line(color=GRID, size=0.3),
93-
panel_grid_minor_x=element_line(color=GRID, size=0.2),
131+
legend_background=element_rect(fill=ELEVATED_BG, color="transparent"),
132+
panel_grid_major_x=element_line(color=RULE, size=0.3),
133+
panel_grid_minor_x=element_blank(),
94134
panel_grid_major_y=element_blank(),
95135
panel_grid_minor_y=element_blank(),
96136
axis_line=element_line(color=INK_SOFT),
97137
axis_ticks=element_blank(),
98-
axis_title=element_text(size=20, color=INK),
99-
axis_text=element_text(size=16, color=INK_SOFT),
100-
plot_title=element_text(size=24, color=INK, hjust=0.5),
101-
legend_title=element_text(size=18, color=INK),
102-
legend_text=element_text(size=16, color=INK_SOFT),
103-
legend_position=[0.88, 0.18],
138+
axis_title=element_text(size=12, color=INK),
139+
axis_text=element_text(size=10, color=INK_SOFT),
140+
plot_title=element_text(size=title_size, color=INK, hjust=0.5),
141+
legend_title=element_text(size=10, color=INK),
142+
legend_text=element_text(size=10, color=INK_SOFT),
143+
legend_position=[0.92, 0.2],
104144
legend_justification=[1, 0],
105145
)
106146
)
107147

108148
# Save
109-
ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3)
149+
ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=4)
110150
ggsave(plot, filename=f"plot-{THEME}.html", path=".")

0 commit comments

Comments
 (0)