Skip to content

Commit 5425a66

Browse files
feat(plotly): implement bullet-basic (#1039)
## Implementation: `bullet-basic` - plotly Implements the **plotly** version of `bullet-basic`. **File:** `plots/bullet-basic/implementations/plotly.py` **Parent Issue:** #999 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20257947593)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 77c4dc0 commit 5425a66

2 files changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
bullet-basic: Basic Bullet Chart
3+
Library: plotly
4+
"""
5+
6+
import plotly.graph_objects as go
7+
from plotly.subplots import make_subplots
8+
9+
10+
# Data - Multiple KPIs with different performance levels
11+
metrics = [
12+
{"label": "Revenue ($K)", "actual": 275, "target": 250, "ranges": [150, 200, 300]},
13+
{"label": "Profit ($K)", "actual": 85, "target": 100, "ranges": [50, 75, 125]},
14+
{"label": "Customers", "actual": 320, "target": 400, "ranges": [200, 350, 500]},
15+
{"label": "Satisfaction", "actual": 4.2, "target": 4.5, "ranges": [3.0, 4.0, 5.0]},
16+
]
17+
18+
# Grayscale colors for qualitative ranges (poor -> satisfactory -> good)
19+
range_colors = ["#D9D9D9", "#BFBFBF", "#A6A6A6"]
20+
21+
# Create subplots - one row per metric for proper scaling
22+
fig = make_subplots(
23+
rows=len(metrics), cols=1, shared_xaxes=False, vertical_spacing=0.12, subplot_titles=[m["label"] for m in metrics]
24+
)
25+
26+
# Create each bullet chart in its own subplot
27+
for i, m in enumerate(metrics):
28+
row = i + 1
29+
30+
# Add qualitative range bands (background, plotted in reverse order)
31+
for j, r in enumerate(reversed(m["ranges"])):
32+
fig.add_trace(
33+
go.Bar(
34+
x=[r],
35+
y=[""],
36+
orientation="h",
37+
marker=dict(color=range_colors[len(m["ranges"]) - 1 - j]),
38+
width=0.6,
39+
showlegend=False,
40+
hoverinfo="skip",
41+
),
42+
row=row,
43+
col=1,
44+
)
45+
46+
# Add actual value bar (primary measure)
47+
fig.add_trace(
48+
go.Bar(
49+
x=[m["actual"]],
50+
y=[""],
51+
orientation="h",
52+
marker=dict(color="#306998"),
53+
width=0.25,
54+
showlegend=False,
55+
name=m["label"],
56+
hovertemplate=f"{m['label']}: {m['actual']}<extra></extra>",
57+
),
58+
row=row,
59+
col=1,
60+
)
61+
62+
# Add target marker line
63+
fig.add_shape(
64+
type="line",
65+
x0=m["target"],
66+
x1=m["target"],
67+
y0=-0.4,
68+
y1=0.4,
69+
line=dict(color="#1A1A1A", width=5),
70+
row=row,
71+
col=1,
72+
)
73+
74+
# Add actual value annotation
75+
max_range = m["ranges"][-1]
76+
fig.add_annotation(
77+
x=max_range * 1.02,
78+
y=0,
79+
text=f"<b>{m['actual']}</b>",
80+
showarrow=False,
81+
font=dict(size=20, color="#306998"),
82+
xanchor="left",
83+
row=row,
84+
col=1,
85+
)
86+
87+
# Update x-axis range for each subplot
88+
fig.update_xaxes(
89+
range=[0, max_range * 1.15], tickfont=dict(size=16), showgrid=True, gridcolor="rgba(0,0,0,0.1)", row=row, col=1
90+
)
91+
92+
fig.update_yaxes(showticklabels=False, row=row, col=1)
93+
94+
# Layout
95+
fig.update_layout(
96+
title=dict(text="bullet-basic · plotly · pyplots.ai", font=dict(size=32), x=0.5, xanchor="center"),
97+
barmode="overlay",
98+
template="plotly_white",
99+
margin=dict(l=80, r=100, t=120, b=60),
100+
showlegend=False,
101+
height=900,
102+
width=1600,
103+
)
104+
105+
# Update subplot titles font size
106+
for annotation in fig["layout"]["annotations"]:
107+
if "text" in annotation and annotation["text"] in [m["label"] for m in metrics]:
108+
annotation["font"] = dict(size=22)
109+
110+
# Save
111+
fig.write_image("plot.png", width=1600, height=900, scale=3)
112+
fig.write_html("plot.html", include_plotlyjs="cdn")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Per-library metadata for plotly implementation of bullet-basic
2+
# Auto-generated by impl-generate.yml
3+
4+
library: plotly
5+
specification_id: bullet-basic
6+
7+
# Preview URLs (filled by workflow)
8+
preview_url: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/plotly/plot.png
9+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/plotly/plot_thumb.png
10+
preview_html: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/plotly/plot.html
11+
12+
current:
13+
version: 0
14+
generated_at: 2025-12-16T05:52:30Z
15+
generated_by: claude-opus-4-5-20251101
16+
workflow_run: 20257947593
17+
issue: 999
18+
quality_score: 93
19+
# Version info (filled by workflow)
20+
python_version: "3.13.11"
21+
library_version: "unknown"
22+
23+
history: []

0 commit comments

Comments
 (0)