|
1 | 1 | """ pyplots.ai |
2 | 2 | density-basic: Basic Density Plot |
3 | | -Library: bokeh 3.8.1 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: bokeh 3.8.2 | Python 3.14.3 |
| 4 | +Quality: 91/100 | Updated: 2026-02-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
8 | 8 | from bokeh.io import export_png |
9 | | -from bokeh.models import ColumnDataSource |
| 9 | +from bokeh.models import ColumnDataSource, HoverTool, Label, NumeralTickFormatter |
10 | 10 | from bokeh.plotting import figure |
11 | 11 |
|
12 | 12 |
|
|
32 | 32 | density += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2) |
33 | 33 | density /= n * bandwidth * np.sqrt(2 * np.pi) |
34 | 34 |
|
| 35 | +# Identify peaks for visual storytelling |
| 36 | +peak1_idx = np.argmax(density[:250]) |
| 37 | +peak2_idx = 250 + np.argmax(density[250:]) |
| 38 | +peak1_x, peak1_y = x_range[peak1_idx], density[peak1_idx] |
| 39 | +peak2_x, peak2_y = x_range[peak2_idx], density[peak2_idx] |
| 40 | + |
35 | 41 | # Create data source |
36 | 42 | source = ColumnDataSource(data={"x": x_range, "density": density}) |
37 | 43 |
|
| 44 | +# Highlight regions around each peak (for visual emphasis) |
| 45 | +mask1 = (x_range > peak1_x - 60) & (x_range < peak1_x + 60) |
| 46 | +mask2 = (x_range > peak2_x - 55) & (x_range < peak2_x + 55) |
| 47 | +highlight1 = ColumnDataSource(data={"x": x_range[mask1], "density": density[mask1]}) |
| 48 | +highlight2 = ColumnDataSource(data={"x": x_range[mask2], "density": density[mask2]}) |
| 49 | + |
38 | 50 | # Rug plot data (individual observations) |
39 | | -rug_y_pos = -0.0004 # Position below x-axis |
| 51 | +rug_y_pos = -0.0006 |
40 | 52 | rug_source = ColumnDataSource( |
41 | 53 | data={ |
42 | 54 | "x": response_times, |
43 | 55 | "y0": np.full_like(response_times, rug_y_pos), |
44 | | - "y1": np.full_like(response_times, rug_y_pos + 0.0004), |
| 56 | + "y1": np.full_like(response_times, rug_y_pos + 0.0008), |
45 | 57 | } |
46 | 58 | ) |
47 | 59 |
|
48 | | -# Create figure (4800 x 2700 px for 16:9 landscape) |
| 60 | +# Create figure |
49 | 61 | p = figure( |
50 | 62 | width=4800, |
51 | 63 | height=2700, |
|
55 | 67 | ) |
56 | 68 |
|
57 | 69 | # Fill under the curve |
58 | | -p.varea(x="x", y1=0, y2="density", source=source, fill_color="#306998", fill_alpha=0.35) |
| 70 | +p.varea(x="x", y1=0, y2="density", source=source, fill_color="#306998", fill_alpha=0.25) |
59 | 71 |
|
60 | 72 | # Density curve |
61 | | -p.line(x="x", y="density", source=source, line_color="#306998", line_width=5, line_alpha=0.95) |
| 73 | +density_line = p.line(x="x", y="density", source=source, line_color="#306998", line_width=6, line_alpha=0.9) |
| 74 | + |
| 75 | +# Emphasize peak regions with darker fill |
| 76 | +p.varea(x="x", y1=0, y2="density", source=highlight1, fill_color="#306998", fill_alpha=0.15) |
| 77 | +p.varea(x="x", y1=0, y2="density", source=highlight2, fill_color="#306998", fill_alpha=0.15) |
| 78 | + |
| 79 | +# Annotate peaks for data storytelling |
| 80 | +p.add_layout( |
| 81 | + Label( |
| 82 | + x=peak1_x, |
| 83 | + y=peak1_y, |
| 84 | + text="Fast Responses", |
| 85 | + text_font_size="22pt", |
| 86 | + text_color="#1a3d5c", |
| 87 | + text_font_style="bold", |
| 88 | + text_align="center", |
| 89 | + y_offset=18, |
| 90 | + ) |
| 91 | +) |
| 92 | +p.add_layout( |
| 93 | + Label( |
| 94 | + x=peak2_x, |
| 95 | + y=peak2_y, |
| 96 | + text="Slower Responses", |
| 97 | + text_font_size="22pt", |
| 98 | + text_color="#1a3d5c", |
| 99 | + text_font_style="bold", |
| 100 | + text_align="center", |
| 101 | + y_offset=18, |
| 102 | + ) |
| 103 | +) |
| 104 | + |
| 105 | +# Hover tool showing density values at cursor position |
| 106 | +hover = HoverTool( |
| 107 | + renderers=[density_line], |
| 108 | + tooltips=[("Response Time", "@x{0.0} ms"), ("Density", "@density{0.00000}")], |
| 109 | + mode="vline", |
| 110 | + line_policy="nearest", |
| 111 | +) |
| 112 | +p.add_tools(hover) |
62 | 113 |
|
63 | 114 | # Rug plot - vertical segments at bottom |
64 | | -p.segment(x0="x", y0="y0", x1="x", y1="y1", source=rug_source, line_color="#306998", line_width=2, line_alpha=0.5) |
| 115 | +p.segment(x0="x", y0="y0", x1="x", y1="y1", source=rug_source, line_color="#306998", line_width=3, line_alpha=0.65) |
65 | 116 |
|
66 | | -# Style text sizes for large canvas (scaled up) |
| 117 | +# Text sizes for large canvas |
67 | 118 | p.title.text_font_size = "36pt" |
68 | 119 | p.xaxis.axis_label_text_font_size = "28pt" |
69 | 120 | p.yaxis.axis_label_text_font_size = "28pt" |
70 | 121 | p.xaxis.major_label_text_font_size = "22pt" |
71 | 122 | p.yaxis.major_label_text_font_size = "22pt" |
72 | 123 |
|
73 | | -# Axis styling |
74 | | -p.xaxis.axis_line_width = 2 |
75 | | -p.yaxis.axis_line_width = 2 |
76 | | -p.xaxis.major_tick_line_width = 2 |
77 | | -p.yaxis.major_tick_line_width = 2 |
| 124 | +# Y-axis tick format |
| 125 | +p.yaxis.formatter = NumeralTickFormatter(format="0.0000") |
| 126 | + |
| 127 | +# Axis styling - softened to match minimalist chrome |
| 128 | +p.xaxis.axis_line_width = 1 |
| 129 | +p.yaxis.axis_line_width = 1 |
| 130 | +p.xaxis.axis_line_alpha = 0.5 |
| 131 | +p.yaxis.axis_line_alpha = 0.5 |
| 132 | +p.xaxis.minor_tick_line_color = None |
| 133 | +p.yaxis.minor_tick_line_color = None |
| 134 | +p.xaxis.major_tick_line_color = None |
| 135 | +p.yaxis.major_tick_line_color = None |
78 | 136 |
|
79 | | -# Grid styling |
80 | | -p.xgrid.grid_line_alpha = 0.3 |
81 | | -p.ygrid.grid_line_alpha = 0.3 |
82 | | -p.xgrid.grid_line_dash = "dashed" |
83 | | -p.ygrid.grid_line_dash = "dashed" |
| 137 | +# Grid - y-axis only, subtle |
| 138 | +p.xgrid.grid_line_color = None |
| 139 | +p.ygrid.grid_line_alpha = 0.15 |
| 140 | +p.ygrid.grid_line_width = 1 |
84 | 141 |
|
85 | 142 | # Background |
86 | 143 | p.background_fill_color = "#fafafa" |
87 | 144 | p.border_fill_color = "#ffffff" |
| 145 | +p.outline_line_color = None |
88 | 146 |
|
89 | 147 | # Remove toolbar |
90 | 148 | p.toolbar_location = None |
|
0 commit comments