|
1 | 1 | """ anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import os |
|
19 | 19 | ggplot, |
20 | 20 | ggsize, |
21 | 21 | labs, |
| 22 | + layer_tooltips, |
22 | 23 | scale_color_manual, |
23 | 24 | scale_x_continuous, |
24 | 25 | scale_y_continuous, |
|
36 | 37 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
37 | 38 | INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
38 | 39 | 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)" |
40 | 41 |
|
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 |
45 | 46 |
|
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 |
48 | 48 | categories = [ |
49 | 49 | "Engineering", |
50 | 50 | "Marketing", |
|
65 | 65 | df = df.sort_values("diff", ascending=True).reset_index(drop=True) |
66 | 66 | df["y_pos"] = range(len(df)) |
67 | 67 |
|
| 68 | +df_improved = df[df["diff"] > 0] |
| 69 | +df_declined = df[df["diff"] <= 0] |
| 70 | + |
68 | 71 | df_points = pd.concat( |
69 | 72 | [ |
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 | + ), |
72 | 91 | ] |
73 | 92 | ) |
74 | 93 |
|
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 |
76 | 100 | plot = ( |
77 | 101 | 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") |
81 | 123 | + scale_x_continuous(limits=[50, 95]) |
82 | 124 | + 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) |
87 | 127 | + theme_minimal() |
88 | 128 | + theme( |
89 | 129 | plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
90 | 130 | 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(), |
94 | 134 | panel_grid_major_y=element_blank(), |
95 | 135 | panel_grid_minor_y=element_blank(), |
96 | 136 | axis_line=element_line(color=INK_SOFT), |
97 | 137 | 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], |
104 | 144 | legend_justification=[1, 0], |
105 | 145 | ) |
106 | 146 | ) |
107 | 147 |
|
108 | 148 | # Save |
109 | | -ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3) |
| 149 | +ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=4) |
110 | 150 | ggsave(plot, filename=f"plot-{THEME}.html", path=".") |
0 commit comments