Skip to content

Commit a6cd33e

Browse files
update(bullet-basic): bokeh — comprehensive quality review
Comprehensive quality review improving rendering, band styling, and library features.
1 parent 1e072e3 commit a6cd33e

2 files changed

Lines changed: 81 additions & 49 deletions

File tree

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
bullet-basic: Basic Bullet Chart
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: bokeh 3.8.2 | Python 3.14.3
4+
Quality: /100 | Updated: 2026-02-22
55
"""
66

77
from bokeh.io import export_png, output_file, save
8-
from bokeh.models import Label
8+
from bokeh.models import Label, Range1d
99
from bokeh.plotting import figure
1010

1111

1212
# Data - Sales performance metrics with targets
1313
metrics = [
14-
{"label": "Revenue", "actual": 275, "target": 250, "ranges": [150, 225, 300]},
15-
{"label": "Profit", "actual": 85, "target": 100, "ranges": [50, 75, 100]},
16-
{"label": "Orders", "actual": 320, "target": 350, "ranges": [200, 300, 400]},
17-
{"label": "Customers", "actual": 1450, "target": 1200, "ranges": [800, 1100, 1500]},
18-
{"label": "Satisfaction", "actual": 4.2, "target": 4.5, "ranges": [3.0, 4.0, 5.0]},
14+
{"label": "Revenue", "unit": "$K", "actual": 275, "target": 250, "ranges": [150, 225, 300]},
15+
{"label": "Profit", "unit": "$K", "actual": 85, "target": 100, "ranges": [50, 75, 100]},
16+
{"label": "Orders", "unit": "", "actual": 320, "target": 350, "ranges": [200, 300, 400]},
17+
{"label": "Customers", "unit": "", "actual": 1450, "target": 1200, "ranges": [800, 1100, 1500]},
18+
{"label": "Satisfaction", "unit": "/5", "actual": 4.2, "target": 4.5, "ranges": [3.0, 4.0, 5.0]},
1919
]
2020

2121
# Configuration
2222
num_metrics = len(metrics)
23-
bar_spacing = 1.5 # Space between each bullet row
24-
bar_height = 0.8 # Maximum height of range bars
23+
bar_spacing = 1.5
24+
bar_height = 0.8
2525

26-
# Qualitative range colors (grayscale: poor, satisfactory, good - light to dark)
27-
range_colors = ["#dddddd", "#aaaaaa", "#777777"]
26+
# Qualitative range colors: lightest (widest, good) to darkest (narrowest, poor)
27+
range_colors = ["#d4d4d4", "#a8a8a8", "#737373"]
2828

2929
# Create figure
3030
p = figure(
3131
width=4800,
3232
height=2700,
33-
x_range=(0, 110),
34-
y_range=(-0.5, num_metrics * bar_spacing),
33+
x_range=Range1d(-38, 118),
34+
y_range=Range1d(-0.8, num_metrics * bar_spacing + 0.2),
3535
title="bullet-basic · bokeh · pyplots.ai",
36-
x_axis_label="% of Target Range",
36+
x_axis_label="% of Maximum Range",
3737
toolbar_location=None,
3838
)
3939

4040
# Remove y-axis ticks and gridlines
4141
p.yaxis.visible = False
4242
p.ygrid.grid_line_color = None
43-
p.xgrid.grid_line_alpha = 0.3
44-
p.xgrid.grid_line_dash = "dashed"
43+
p.xgrid.grid_line_alpha = 0.15
44+
p.xgrid.grid_line_dash = [6, 4]
4545

4646
# Draw bullets for each metric
4747
for i, metric in enumerate(metrics):
48-
y_pos = (num_metrics - 1 - i) * bar_spacing # Reverse so first is at top
48+
y_pos = (num_metrics - 1 - i) * bar_spacing
4949
actual = metric["actual"]
5050
target = metric["target"]
5151
ranges = metric["ranges"]
@@ -56,26 +56,27 @@
5656
norm_target = (target / max_range) * 100
5757
norm_ranges = [(r / max_range) * 100 for r in ranges]
5858

59-
# Draw qualitative ranges (background bands) - from outer to inner
59+
# Draw qualitative ranges (background bands) - widest first (lightest = good)
6060
for j in range(len(norm_ranges) - 1, -1, -1):
6161
range_width = norm_ranges[j]
62-
height_factor = 1 - j * 0.25 # Decrease height for inner ranges
62+
height_factor = 1 - j * 0.2
6363
h = bar_height * height_factor
6464
p.rect(x=range_width / 2, y=y_pos, width=range_width, height=h, color=range_colors[j], line_color=None)
6565

66-
# Draw actual value bar (primary measure)
67-
actual_bar_height = bar_height * 0.35
66+
# Draw actual value bar
67+
actual_bar_height = bar_height * 0.3
6868
p.rect(x=norm_actual / 2, y=y_pos, width=norm_actual, height=actual_bar_height, color="#306998", line_color=None)
6969

70-
# Draw target marker (thin black vertical line)
71-
target_marker_height = bar_height * 0.6
72-
p.rect(x=norm_target, y=y_pos, width=0.8, height=target_marker_height, color="#1a1a1a", line_color=None)
70+
# Draw target marker (thin vertical line)
71+
target_marker_height = bar_height * 0.55
72+
p.rect(x=norm_target, y=y_pos, width=0.7, height=target_marker_height, color="#1a1a1a", line_color=None)
7373

74-
# Add metric label on the left
74+
# Add metric label with unit
75+
unit_text = f" ({metric['unit']})" if metric["unit"] else ""
7576
label = Label(
7677
x=-2,
7778
y=y_pos,
78-
text=metric["label"],
79+
text=f"{metric['label']}{unit_text}",
7980
text_font_size="28pt",
8081
text_color="#333333",
8182
text_align="right",
@@ -84,40 +85,71 @@
8485
)
8586
p.add_layout(label)
8687

87-
# Add actual value text label on the right
88+
# Add actual value as text
89+
value_text = str(int(actual)) if actual == int(actual) else str(actual)
8890
value_label = Label(
89-
x=norm_actual + 3,
91+
x=norm_actual + 2,
9092
y=y_pos,
91-
text=str(metric["actual"]),
92-
text_font_size="24pt",
93+
text=value_text,
94+
text_font_size="22pt",
9395
text_color="#306998",
9496
text_align="left",
9597
text_baseline="middle",
9698
text_font_style="bold",
9799
)
98100
p.add_layout(value_label)
99101

100-
# Extend x_range to accommodate labels (but clip axis display)
101-
p.x_range.start = -35
102-
p.x_range.end = 115
102+
# Legend - positioned below the chart
103+
legend_y = -0.5
104+
legend_start_x = 10
105+
legend_spacing = 22
106+
range_labels = ["Poor", "Satisfactory", "Good"]
107+
box_w = 4
108+
box_h = 0.2
109+
110+
for k, (color, lbl) in enumerate(zip(range_colors[::-1], range_labels, strict=True)):
111+
lx = legend_start_x + k * legend_spacing
112+
p.rect(x=lx, y=legend_y, width=box_w, height=box_h, color=color, line_color="#999999", line_width=1)
113+
p.add_layout(
114+
Label(
115+
x=lx + box_w / 2 + 1,
116+
y=legend_y,
117+
text=lbl,
118+
text_font_size="20pt",
119+
text_color="#555555",
120+
text_align="left",
121+
text_baseline="middle",
122+
)
123+
)
124+
125+
# Target marker legend entry
126+
target_lx = legend_start_x + len(range_labels) * legend_spacing
127+
p.rect(x=target_lx, y=legend_y, width=1.0, height=box_h, color="#1a1a1a", line_color=None)
128+
p.add_layout(
129+
Label(
130+
x=target_lx + box_w / 2 + 1,
131+
y=legend_y,
132+
text="Target",
133+
text_font_size="20pt",
134+
text_color="#555555",
135+
text_align="left",
136+
text_baseline="middle",
137+
)
138+
)
103139

104-
# Styling - scaled for 4800x2700 canvas
105-
p.title.text_font_size = "42pt"
140+
# Style - scaled for 4800x2700 canvas
141+
p.title.text_font_size = "36pt"
106142
p.title.text_color = "#333333"
107143
p.title.align = "center"
108-
p.xaxis.axis_label_text_font_size = "28pt"
109-
p.xaxis.major_label_text_font_size = "22pt"
110-
111-
# Only show positive tick marks on x-axis
144+
p.xaxis.axis_label_text_font_size = "24pt"
145+
p.xaxis.major_label_text_font_size = "20pt"
112146
p.xaxis.ticker = [0, 20, 40, 60, 80, 100]
113-
114-
# Axis styling
115147
p.xaxis.axis_line_color = "#666666"
116148
p.outline_line_color = None
117149

118150
# Save as PNG
119151
export_png(p, filename="plot.png")
120152

121-
# Save as HTML for interactivity
153+
# Save as HTML
122154
output_file("plot.html")
123155
save(p)

plots/bullet-basic/metadata/bokeh.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: bokeh
22
specification_id: bullet-basic
33
created: '2025-12-23T09:18:59Z'
4-
updated: '2025-12-23T09:23:32Z'
5-
generated_by: claude-opus-4-5-20251101
4+
updated: '2026-02-22T12:00:00+00:00'
5+
generated_by: claude-opus-4-6
66
workflow_run: 20456605377
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 3.8.1
8+
python_version: 3.14.3
9+
library_version: 3.8.2
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/bokeh/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/bokeh/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/bullet-basic/bokeh/plot.html
13-
quality_score: 91
13+
quality_score: null
1414
impl_tags:
1515
dependencies: []
1616
techniques:

0 commit comments

Comments
 (0)