Skip to content

Commit f6d3ac5

Browse files
feat(letsplot): implement dumbbell-basic (#5421)
## 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/24945502773)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 42ad90a commit f6d3ac5

2 files changed

Lines changed: 230 additions & 153 deletions

File tree

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
dumbbell-basic: Basic Dumbbell Chart
3-
Library: letsplot 4.8.2 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: letsplot 4.9.0 | Python 3.14.4
4+
Quality: 86/100 | Updated: 2026-04-26
55
"""
66

7+
import os
8+
79
import pandas as pd
810
from lets_plot import (
911
LetsPlot,
1012
aes,
1113
element_blank,
14+
element_line,
15+
element_rect,
1216
element_text,
1317
geom_point,
1418
geom_segment,
@@ -26,7 +30,21 @@
2630

2731
LetsPlot.setup_html()
2832

29-
# Data - Employee satisfaction scores before and after policy changes
33+
# Theme tokens
34+
THEME = os.getenv("ANYPLOT_THEME", "light")
35+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
36+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
37+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
38+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
39+
GRID = "#1A1A17" if THEME == "light" else "#F0EFE8"
40+
41+
# Okabe-Ito palette — "After" comes first alphabetically → brand green
42+
BRAND = "#009E73"
43+
ACCENT = "#D55E00"
44+
SEGMENT = INK_SOFT
45+
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.
3048
categories = [
3149
"Engineering",
3250
"Marketing",
@@ -39,52 +57,54 @@
3957
"Legal",
4058
"R&D",
4159
]
42-
before_scores = [62, 58, 71, 55, 68, 64, 59, 73, 66, 61]
43-
after_scores = [78, 72, 85, 74, 81, 76, 71, 88, 79, 75]
60+
before_scores = [62, 58, 71, 55, 68, 64, 72, 73, 66, 61]
61+
after_scores = [78, 72, 85, 80, 81, 70, 65, 88, 67, 75]
4462

4563
df = pd.DataFrame({"category": categories, "before": before_scores, "after": after_scores})
46-
47-
# Calculate difference for sorting - sort by improvement (largest first)
4864
df["diff"] = df["after"] - df["before"]
4965
df = df.sort_values("diff", ascending=True).reset_index(drop=True)
50-
51-
# Create numeric y positions for categories (horizontal orientation)
5266
df["y_pos"] = range(len(df))
5367

54-
# Create long-format data for points (to get legend)
5568
df_points = pd.concat(
5669
[
5770
pd.DataFrame({"y_pos": df["y_pos"], "value": df["before"], "period": "Before"}),
5871
pd.DataFrame({"y_pos": df["y_pos"], "value": df["after"], "period": "After"}),
5972
]
6073
)
6174

62-
# Plot - Horizontal dumbbell chart
75+
# Plot — horizontal dumbbell
6376
plot = (
6477
ggplot()
65-
# Connecting lines - thin and subtle
66-
+ geom_segment(data=df, mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), size=1.5, color="#888888")
67-
# Points with color mapping for legend
78+
+ geom_segment(data=df, mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), size=1.5, color=SEGMENT)
6879
+ geom_point(data=df_points, mapping=aes(x="value", y="y_pos", color="period"), size=8)
69-
+ scale_color_manual(values=["#FFD43B", "#306998"], name="Period")
70-
+ labs(
71-
x="Satisfaction Score", y="Department", title="Employee Satisfaction · dumbbell-basic · letsplot · pyplots.ai"
72-
)
80+
+ scale_color_manual(values=[BRAND, ACCENT], name="Period")
7381
+ scale_x_continuous(limits=[50, 95])
7482
+ 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+
)
7586
+ ggsize(1600, 900)
7687
+ theme_minimal()
7788
+ theme(
78-
axis_text_x=element_text(size=16),
79-
axis_text_y=element_text(size=16),
80-
axis_title=element_text(size=20),
81-
plot_title=element_text(size=24, hjust=0.5),
82-
legend_title=element_text(size=18),
83-
legend_text=element_text(size=16),
89+
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
90+
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),
8494
panel_grid_major_y=element_blank(),
95+
panel_grid_minor_y=element_blank(),
96+
axis_line=element_line(color=INK_SOFT),
97+
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],
104+
legend_justification=[1, 0],
85105
)
86106
)
87107

88108
# Save
89-
ggsave(plot, filename="plot.png", path=".", scale=3)
90-
ggsave(plot, filename="plot.html", path=".")
109+
ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3)
110+
ggsave(plot, filename=f"plot-{THEME}.html", path=".")

0 commit comments

Comments
 (0)