Skip to content

Commit 5102331

Browse files
feat(bokeh): implement scatter-basic (#5321)
## Implementation: `scatter-basic` - python/bokeh Implements the **python/bokeh** version of `scatter-basic`. **File:** `plots/scatter-basic/implementations/python/bokeh.py` **Parent Issue:** #611 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24860109612)* --------- 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 3e32c5e commit 5102331

2 files changed

Lines changed: 87 additions & 131 deletions

File tree

Lines changed: 31 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
""" anyplot.ai
22
scatter-basic: Basic Scatter Plot
33
Library: bokeh 3.9.0 | Python 3.14.4
4-
Quality: 92/100 | Created: 2026-04-23
4+
Quality: 90/100 | Updated: 2026-04-23
55
"""
66

77
import os
88

99
import numpy as np
1010
from bokeh.io import export_png, output_file, save
11-
from bokeh.models import Band, ColumnDataSource, HoverTool, Slope
11+
from bokeh.models import ColumnDataSource, HoverTool
1212
from bokeh.plotting import figure
1313

1414

@@ -18,90 +18,58 @@
1818
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
1919
BRAND = "#009E73"
2020

21-
# Data — study hours vs exam scores
21+
# Data — study hours vs exam scores, moderate positive correlation
2222
np.random.seed(42)
23-
n_points = 150
24-
study_hours = np.random.uniform(1, 10, n_points)
25-
exam_scores = study_hours * 7 + np.random.randn(n_points) * 6 + 25
26-
27-
# Natural outliers (test anxiety, gifted, under-performers)
28-
exam_scores[5] = 38
29-
exam_scores[22] = 92
30-
exam_scores[47] = 30
31-
exam_scores[71] = 95
32-
exam_scores[88] = 42
33-
34-
exam_scores = np.clip(exam_scores, 15, 98)
35-
36-
# Linear regression for trend line and confidence band
37-
slope_coef = np.polyfit(study_hours, exam_scores, 1)
38-
predicted = np.polyval(slope_coef, study_hours)
39-
residual_std = np.std(exam_scores - predicted)
40-
41-
sort_idx = np.argsort(study_hours)
42-
x_sorted = study_hours[sort_idx]
43-
y_pred_sorted = np.polyval(slope_coef, x_sorted)
44-
band_source = ColumnDataSource(
45-
data={"x": x_sorted, "upper": y_pred_sorted + 1.5 * residual_std, "lower": y_pred_sorted - 1.5 * residual_std}
46-
)
23+
n_points = 180
24+
study_hours = np.random.uniform(0.8, 9.6, n_points)
25+
exam_scores = study_hours * 7.2 + np.random.normal(0, 6.5, n_points) + 26
26+
exam_scores = np.clip(exam_scores, 18, 99)
4727

48-
source = ColumnDataSource(data={"x": study_hours, "y": exam_scores})
28+
source = ColumnDataSource(data={"study_hours": study_hours, "exam_scores": exam_scores})
4929

30+
# Plot
5031
p = figure(
5132
width=4800,
5233
height=2700,
5334
title="scatter-basic · bokeh · anyplot.ai",
54-
x_axis_label="Study Hours per Day (hrs)",
35+
x_axis_label="Study Hours per Day",
5536
y_axis_label="Exam Score (%)",
5637
toolbar_location=None,
57-
x_range=(0, 11),
58-
y_range=(12, 103),
59-
)
60-
61-
# Confidence band — Bokeh-native Band glyph
62-
band = Band(
63-
base="x",
64-
lower="lower",
65-
upper="upper",
66-
source=band_source,
67-
level="underlay",
68-
fill_alpha=0.15,
69-
fill_color=BRAND,
70-
line_width=0,
71-
)
72-
p.add_layout(band)
73-
74-
# Trend line via Slope model — Bokeh-specific annotation
75-
trend = Slope(
76-
gradient=slope_coef[0],
77-
y_intercept=slope_coef[1],
78-
line_color=BRAND,
79-
line_width=5,
80-
line_alpha=0.55,
81-
line_dash="dashed",
38+
x_range=(0, 10.5),
39+
y_range=(10, 104),
8240
)
83-
p.add_layout(trend)
8441

85-
# Scatter points with white edge for definition in dense areas
8642
scatter_renderer = p.scatter(
87-
x="x", y="y", source=source, size=32, color=BRAND, alpha=0.7, line_color="white", line_width=1
43+
x="study_hours", y="exam_scores", source=source, size=34, color=BRAND, alpha=0.7, line_color=PAGE_BG, line_width=1.2
8844
)
8945

90-
# HoverTool — Bokeh's distinctive interactive feature
91-
hover = HoverTool(renderers=[scatter_renderer], tooltips=[("Study Hours", "@x{0.1} hrs"), ("Exam Score", "@y{0.0}%")])
46+
# HoverTool — Bokeh's distinctive interactive feature (HTML only; PNG stays clean)
47+
hover = HoverTool(
48+
renderers=[scatter_renderer],
49+
tooltips=[("Study Hours", "@study_hours{0.1} hrs"), ("Exam Score", "@exam_scores{0.0}%")],
50+
)
9251
p.add_tools(hover)
9352

94-
# Typography — explicitly sized for 4800×2700 canvas
53+
# Typography — sized for 4800×2700 canvas
9554
p.title.text_font_size = "42pt"
96-
p.title.text_color = INK
9755
p.title.text_font_style = "bold"
56+
p.title.text_color = INK
57+
p.title.align = "center"
9858

9959
p.xaxis.axis_label_text_font_size = "32pt"
10060
p.yaxis.axis_label_text_font_size = "32pt"
61+
p.xaxis.axis_label_text_font_style = "normal"
62+
p.yaxis.axis_label_text_font_style = "normal"
10163
p.xaxis.major_label_text_font_size = "24pt"
10264
p.yaxis.major_label_text_font_size = "24pt"
65+
p.xaxis.axis_label_standoff = 28
66+
p.yaxis.axis_label_standoff = 28
10367

10468
# Theme-adaptive chrome
69+
p.background_fill_color = PAGE_BG
70+
p.border_fill_color = PAGE_BG
71+
p.outline_line_color = None
72+
10573
p.xaxis.axis_label_text_color = INK
10674
p.yaxis.axis_label_text_color = INK
10775
p.xaxis.major_label_text_color = INK_SOFT
@@ -113,20 +81,18 @@
11381
p.xaxis.minor_tick_line_color = None
11482
p.yaxis.minor_tick_line_color = None
11583

84+
# Clean L-frame: keep left+bottom axis lines only (handled above via axis_line_color)
11685
p.xgrid.grid_line_color = INK
11786
p.ygrid.grid_line_color = INK
11887
p.xgrid.grid_line_alpha = 0.10
11988
p.ygrid.grid_line_alpha = 0.10
12089
p.xgrid.grid_line_width = 2
12190
p.ygrid.grid_line_width = 2
12291

123-
p.background_fill_color = PAGE_BG
124-
p.border_fill_color = PAGE_BG
125-
p.outline_line_color = None
126-
12792
p.xaxis.ticker.desired_num_ticks = 10
12893
p.yaxis.ticker.desired_num_ticks = 8
12994

95+
# Save
13096
export_png(p, filename=f"plot-{THEME}.png")
13197
output_file(f"plot-{THEME}.html", title="scatter-basic · bokeh · anyplot.ai")
13298
save(p)

0 commit comments

Comments
 (0)