|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | bullet-basic: Basic Bullet Chart |
3 | | -Library: seaborn 0.13.2 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: seaborn 0.13.2 | Python 3.14.3 |
| 4 | +Quality: /100 | Updated: 2026-02-22 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import matplotlib.patches as mpatches |
8 | 8 | import matplotlib.pyplot as plt |
9 | | -import numpy as np |
10 | 9 | import pandas as pd |
11 | 10 | import seaborn as sns |
12 | 11 |
|
13 | 12 |
|
14 | 13 | # Data - Multiple KPIs with actual values, targets, and qualitative ranges |
15 | | -np.random.seed(42) |
16 | | - |
17 | 14 | metrics = ["Revenue", "Customer\nSatisfaction", "Efficiency", "Quality\nScore"] |
18 | | -actuals = [78, 85, 62, 91] |
| 15 | +actuals = [78, 85, 35, 91] |
19 | 16 | targets = [90, 80, 75, 85] |
20 | 17 | # Ranges define thresholds for qualitative bands (poor/satisfactory/good) |
21 | 18 | ranges_list = [ |
|
25 | 22 | [70, 85, 100], # Quality Score |
26 | 23 | ] |
27 | 24 |
|
28 | | -# Create figure |
29 | | -fig, ax = plt.subplots(figsize=(16, 9)) |
30 | | -sns.set_style("whitegrid") |
31 | | - |
32 | | -# Qualitative range colors (grayscale as per specification - dark to light for poor to good) |
33 | | -range_colors = ["#e0e0e0", "#bdbdbd", "#8c8c8c"] |
34 | | - |
35 | | -# Bar dimensions |
36 | | -range_height = 0.7 |
37 | | -actual_height = 0.35 |
38 | | -n_metrics = len(metrics) |
39 | | -y_positions = np.arange(n_metrics) |
40 | | - |
41 | | -# Draw qualitative ranges as background bands for each metric |
42 | | -for y_pos, ranges in zip(y_positions, ranges_list, strict=True): |
| 25 | +# Build a long-form DataFrame for qualitative range bands |
| 26 | +range_labels = ["Good", "Satisfactory", "Poor"] |
| 27 | +range_records = [] |
| 28 | +for metric, ranges in zip(metrics, ranges_list, strict=True): |
43 | 29 | prev = 0 |
44 | | - for end, color in zip(ranges, range_colors, strict=True): |
45 | | - width = end - prev |
46 | | - ax.barh(y_pos, width, left=prev, height=range_height, color=color, edgecolor="none", zorder=1) |
| 30 | + for end, label in zip(ranges, range_labels[::-1], strict=True): |
| 31 | + range_records.append({"Metric": metric, "Range": label, "Start": prev, "Width": end - prev}) |
47 | 32 | prev = end |
| 33 | +df_ranges = pd.DataFrame(range_records) |
| 34 | + |
| 35 | +# Seaborn grayscale palette for qualitative bands (dark=poor, medium=satisfactory, light=good) |
| 36 | +band_palette = dict(zip(range_labels, sns.light_palette("#555555", n_colors=4, reverse=True)[1:], strict=True)) |
| 37 | + |
| 38 | +# Create figure with seaborn theme |
| 39 | +sns.set_theme(style="whitegrid", rc={"axes.spines.top": False, "axes.spines.right": False, "axes.spines.left": False}) |
| 40 | +fig, ax = plt.subplots(figsize=(16, 9)) |
48 | 41 |
|
49 | | -# Create DataFrame for seaborn barplot |
50 | | -df = pd.DataFrame({"Metric": metrics, "Actual": actuals}) |
| 42 | +# Draw qualitative range bands using seaborn barplot layering |
| 43 | +range_height = 0.7 |
| 44 | +for label in range_labels: |
| 45 | + subset = df_ranges[df_ranges["Range"] == label] |
| 46 | + sns.barplot( |
| 47 | + data=subset, |
| 48 | + x="Width", |
| 49 | + y="Metric", |
| 50 | + color=band_palette[label], |
| 51 | + left=subset["Start"].values, |
| 52 | + width=range_height, |
| 53 | + edgecolor="none", |
| 54 | + ax=ax, |
| 55 | + zorder=1, |
| 56 | + ) |
51 | 57 |
|
52 | | -# Draw actual value bars using seaborn |
| 58 | +# Actual value bars using seaborn barplot |
| 59 | +df_actual = pd.DataFrame({"Metric": metrics, "Actual": actuals}) |
53 | 60 | sns.barplot( |
54 | | - data=df, |
| 61 | + data=df_actual, |
55 | 62 | x="Actual", |
56 | 63 | y="Metric", |
57 | | - color="#306998", # Python Blue |
58 | | - width=actual_height, |
59 | | - ax=ax, |
60 | | - zorder=3, |
| 64 | + color="#306998", |
| 65 | + width=0.35, |
61 | 66 | edgecolor="#1e4d6b", |
62 | 67 | linewidth=1.5, |
| 68 | + ax=ax, |
| 69 | + zorder=3, |
63 | 70 | ) |
64 | 71 |
|
65 | 72 | # Draw target markers as vertical lines |
|
92 | 99 |
|
93 | 100 | # Create legend |
94 | 101 | legend_elements = [ |
95 | | - mpatches.Patch(facecolor=range_colors[0], label="Poor"), |
96 | | - mpatches.Patch(facecolor=range_colors[1], label="Satisfactory"), |
97 | | - mpatches.Patch(facecolor=range_colors[2], label="Good"), |
| 102 | + mpatches.Patch(facecolor=band_palette["Poor"], label="Poor"), |
| 103 | + mpatches.Patch(facecolor=band_palette["Satisfactory"], label="Satisfactory"), |
| 104 | + mpatches.Patch(facecolor=band_palette["Good"], label="Good"), |
98 | 105 | mpatches.Patch(facecolor="#306998", edgecolor="#1e4d6b", linewidth=1.5, label="Actual"), |
99 | 106 | plt.Line2D([0], [0], color="#1a1a1a", linewidth=5, label="Target"), |
100 | 107 | ] |
101 | | -ax.legend(handles=legend_elements, loc="upper right", fontsize=14, framealpha=0.95) |
102 | | - |
103 | | -# Remove spines for cleaner look |
104 | | -ax.spines["top"].set_visible(False) |
105 | | -ax.spines["right"].set_visible(False) |
106 | | -ax.spines["left"].set_visible(False) |
| 108 | +ax.legend(handles=legend_elements, loc="upper right", fontsize=13, framealpha=0.95) |
107 | 109 |
|
108 | 110 | plt.tight_layout() |
109 | 111 | plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments