|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | bullet-basic: Basic Bullet Chart |
3 | | -Library: plotnine 0.15.2 | Python 3.13.11 |
4 | | -Quality: 94/100 | Created: 2025-12-23 |
| 3 | +Library: plotnine 0.15.3 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-22 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import pandas as pd |
8 | 8 | from plotnine import ( |
9 | 9 | aes, |
10 | 10 | element_blank, |
| 11 | + element_line, |
11 | 12 | element_text, |
12 | 13 | geom_rect, |
13 | 14 | geom_segment, |
|
51 | 52 | ) |
52 | 53 | range_data.append({"y": y_pos, "xmin": (m["ranges"][1] / max_val) * 100, "xmax": 100, "band": "Good"}) |
53 | 54 |
|
54 | | - # Actual value bar |
| 55 | + # Actual value bar - format label as integer when possible |
55 | 56 | actual_pct = (m["actual"] / max_val) * 100 |
56 | | - actual_data.append({"y": y_pos, "xmin": 0, "xmax": actual_pct, "label": m["label"], "actual": m["actual"]}) |
| 57 | + val = m["actual"] |
| 58 | + val_str = str(int(val)) if val == int(val) else str(val) |
| 59 | + actual_data.append({"y": y_pos, "xmin": 0, "xmax": actual_pct, "label": m["label"], "actual": val_str}) |
57 | 60 |
|
58 | 61 | # Target marker |
59 | 62 | target_pct = (m["target"] / max_val) * 100 |
|
63 | 66 | df_actual = pd.DataFrame(actual_data) |
64 | 67 | df_target = pd.DataFrame(target_data) |
65 | 68 |
|
66 | | -# Grayscale colors for qualitative bands (good=light, satisfactory=medium, poor=dark) |
| 69 | +# Grayscale colors for qualitative bands (poor=dark, satisfactory=medium, good=light) |
67 | 70 | band_colors = {"Poor": "#707070", "Satisfactory": "#A0A0A0", "Good": "#D0D0D0"} |
68 | 71 |
|
69 | 72 | # Bar height parameters |
70 | | -range_height = 0.65 |
71 | | -actual_height = 0.28 |
| 73 | +range_height = 0.6 |
| 74 | +actual_height = 0.25 |
| 75 | + |
| 76 | +# Band legend labels centered in each zone of the bottom metric (Satisfaction: 50%, 70%, 100%) |
| 77 | +sat = metrics[-1] |
| 78 | +sat_max = sat["ranges"][-1] |
| 79 | +poor_mid = (sat["ranges"][0] / sat_max) * 50 |
| 80 | +satis_mid = ((sat["ranges"][0] + sat["ranges"][1]) / (2 * sat_max)) * 100 |
| 81 | +good_mid = ((sat["ranges"][1] + sat_max) / (2 * sat_max)) * 100 |
| 82 | +legend_labels = pd.DataFrame( |
| 83 | + [ |
| 84 | + {"x": poor_mid, "y": -0.52, "text": "Poor"}, |
| 85 | + {"x": satis_mid, "y": -0.52, "text": "Satisfactory"}, |
| 86 | + {"x": good_mid, "y": -0.52, "text": "Good"}, |
| 87 | + ] |
| 88 | +) |
72 | 89 |
|
73 | 90 | # Plot - horizontal bullet charts (x is performance, y is metric category) |
74 | 91 | plot = ( |
|
89 | 106 | color="#1a1a1a", |
90 | 107 | size=2.5, |
91 | 108 | ) |
92 | | - # Actual value labels at end of bars |
| 109 | + # Actual value labels above each bar |
93 | 110 | + geom_text( |
94 | | - df_actual, aes(x="xmax + 3", y="y", label="actual"), ha="left", size=12, color="#306998", fontweight="bold" |
| 111 | + df_actual, |
| 112 | + aes(x="xmax", y="y + range_height/2 + 0.05", label="actual"), |
| 113 | + ha="right", |
| 114 | + va="bottom", |
| 115 | + size=10, |
| 116 | + color="#306998", |
| 117 | + fontweight="bold", |
95 | 118 | ) |
| 119 | + # Band meaning labels below the bottom metric |
| 120 | + + geom_text(legend_labels, aes(x="x", y="y", label="text"), size=8, color="#555555", va="top") |
96 | 121 | # Scale and labels |
97 | | - + scale_x_continuous(limits=(0, 115), breaks=[0, 25, 50, 75, 100], expand=(0, 0)) |
| 122 | + + scale_x_continuous(limits=(0, 100), breaks=[0, 25, 50, 75, 100], expand=(0, 0.02)) |
98 | 123 | + scale_y_continuous( |
99 | | - breaks=list(range(len(metrics))), labels=[m["label"] for m in reversed(metrics)], expand=(0.15, 0.15) |
| 124 | + breaks=list(range(len(metrics))), labels=[m["label"] for m in reversed(metrics)], expand=(0.25, 0.12) |
100 | 125 | ) |
101 | 126 | + labs(title="bullet-basic · plotnine · pyplots.ai", x="Performance (%)", y="") |
102 | 127 | # Theme |
|
110 | 135 | axis_text_y=element_text(size=18, ha="right"), |
111 | 136 | panel_grid_major_y=element_blank(), |
112 | 137 | panel_grid_minor=element_blank(), |
113 | | - panel_grid_major_x=element_blank(), |
114 | | - legend_position="none", |
| 138 | + panel_grid_major_x=element_line(color="#e0e0e0", size=0.3), |
115 | 139 | ) |
116 | 140 | ) |
117 | 141 |
|
|
0 commit comments