|
1 | 1 | """ anyplot.ai |
2 | 2 | scatter-basic: Basic Scatter Plot |
3 | 3 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import os |
8 | 8 |
|
9 | 9 | import numpy as np |
10 | 10 | 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 |
12 | 12 | from bokeh.plotting import figure |
13 | 13 |
|
14 | 14 |
|
|
18 | 18 | INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
19 | 19 | BRAND = "#009E73" |
20 | 20 |
|
21 | | -# Data — study hours vs exam scores |
| 21 | +# Data — study hours vs exam scores, moderate positive correlation |
22 | 22 | 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) |
47 | 27 |
|
48 | | -source = ColumnDataSource(data={"x": study_hours, "y": exam_scores}) |
| 28 | +source = ColumnDataSource(data={"study_hours": study_hours, "exam_scores": exam_scores}) |
49 | 29 |
|
| 30 | +# Plot |
50 | 31 | p = figure( |
51 | 32 | width=4800, |
52 | 33 | height=2700, |
53 | 34 | title="scatter-basic · bokeh · anyplot.ai", |
54 | | - x_axis_label="Study Hours per Day (hrs)", |
| 35 | + x_axis_label="Study Hours per Day", |
55 | 36 | y_axis_label="Exam Score (%)", |
56 | 37 | 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), |
82 | 40 | ) |
83 | | -p.add_layout(trend) |
84 | 41 |
|
85 | | -# Scatter points with white edge for definition in dense areas |
86 | 42 | 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 |
88 | 44 | ) |
89 | 45 |
|
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 | +) |
92 | 51 | p.add_tools(hover) |
93 | 52 |
|
94 | | -# Typography — explicitly sized for 4800×2700 canvas |
| 53 | +# Typography — sized for 4800×2700 canvas |
95 | 54 | p.title.text_font_size = "42pt" |
96 | | -p.title.text_color = INK |
97 | 55 | p.title.text_font_style = "bold" |
| 56 | +p.title.text_color = INK |
| 57 | +p.title.align = "center" |
98 | 58 |
|
99 | 59 | p.xaxis.axis_label_text_font_size = "32pt" |
100 | 60 | 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" |
101 | 63 | p.xaxis.major_label_text_font_size = "24pt" |
102 | 64 | p.yaxis.major_label_text_font_size = "24pt" |
| 65 | +p.xaxis.axis_label_standoff = 28 |
| 66 | +p.yaxis.axis_label_standoff = 28 |
103 | 67 |
|
104 | 68 | # Theme-adaptive chrome |
| 69 | +p.background_fill_color = PAGE_BG |
| 70 | +p.border_fill_color = PAGE_BG |
| 71 | +p.outline_line_color = None |
| 72 | + |
105 | 73 | p.xaxis.axis_label_text_color = INK |
106 | 74 | p.yaxis.axis_label_text_color = INK |
107 | 75 | p.xaxis.major_label_text_color = INK_SOFT |
|
113 | 81 | p.xaxis.minor_tick_line_color = None |
114 | 82 | p.yaxis.minor_tick_line_color = None |
115 | 83 |
|
| 84 | +# Clean L-frame: keep left+bottom axis lines only (handled above via axis_line_color) |
116 | 85 | p.xgrid.grid_line_color = INK |
117 | 86 | p.ygrid.grid_line_color = INK |
118 | 87 | p.xgrid.grid_line_alpha = 0.10 |
119 | 88 | p.ygrid.grid_line_alpha = 0.10 |
120 | 89 | p.xgrid.grid_line_width = 2 |
121 | 90 | p.ygrid.grid_line_width = 2 |
122 | 91 |
|
123 | | -p.background_fill_color = PAGE_BG |
124 | | -p.border_fill_color = PAGE_BG |
125 | | -p.outline_line_color = None |
126 | | - |
127 | 92 | p.xaxis.ticker.desired_num_ticks = 10 |
128 | 93 | p.yaxis.ticker.desired_num_ticks = 8 |
129 | 94 |
|
| 95 | +# Save |
130 | 96 | export_png(p, filename=f"plot-{THEME}.png") |
131 | 97 | output_file(f"plot-{THEME}.html", title="scatter-basic · bokeh · anyplot.ai") |
132 | 98 | save(p) |
0 commit comments