0
# Events and Interactivity
1
2
Event system for building interactive applications and handling user interactions. Provides 30+ event types for mouse interactions, keyboard input, plot events, and custom events. Essential for creating responsive visualizations and interactive dashboards.
3
4
## Capabilities
5
6
### Base Event Classes
7
8
Core event classes that form the foundation of Bokeh's event system.
9
10
```python { .api }
11
class Event:
12
"""
13
Base class for all Bokeh events.
14
15
All events inherit from this class and provide common properties.
16
"""
17
model: Model # The model that generated the event
18
event_name: str # Name of the event type
19
20
class DocumentEvent(Event):
21
"""
22
Document-level events.
23
24
Events that occur at the document level, affecting the entire application.
25
"""
26
document: Document # The document where the event occurred
27
28
class ModelEvent(Event):
29
"""
30
Model-level events.
31
32
Events that occur on specific model objects.
33
"""
34
35
class PlotEvent(Event):
36
"""
37
Plot-specific events.
38
39
Events that occur within plot areas.
40
"""
41
42
class PointEvent(PlotEvent):
43
"""
44
Events with coordinate information.
45
46
Events that have associated x/y coordinates in data space.
47
"""
48
x: float # X coordinate in data space
49
y: float # Y coordinate in data space
50
sx: int # X coordinate in screen space (pixels)
51
sy: int # Y coordinate in screen space (pixels)
52
```
53
54
### Mouse Events
55
56
Events triggered by mouse interactions within plots.
57
58
```python { .api }
59
class Tap(PointEvent):
60
"""Single mouse click/tap event."""
61
62
class DoubleTap(PointEvent):
63
"""Double mouse click/tap event."""
64
65
class Press(PointEvent):
66
"""Mouse button press event."""
67
68
class PressUp(PointEvent):
69
"""Mouse button release event."""
70
71
class MouseEnter(PointEvent):
72
"""Mouse cursor enters plot area."""
73
74
class MouseLeave(PointEvent):
75
"""Mouse cursor leaves plot area."""
76
77
class MouseMove(PointEvent):
78
"""Mouse cursor movement within plot."""
79
80
class MouseWheel(PointEvent):
81
"""Mouse wheel scroll event."""
82
delta: float # Scroll delta value
83
```
84
85
### Pan and Zoom Events
86
87
Events related to plot navigation and viewport changes.
88
89
```python { .api }
90
class Pan(PointEvent):
91
"""Pan/drag event during movement."""
92
direction: int # Pan direction
93
94
class PanStart(PointEvent):
95
"""Pan/drag start event."""
96
97
class PanEnd(PointEvent):
98
"""Pan/drag end event."""
99
100
class Pinch(PointEvent):
101
"""Pinch/zoom gesture event."""
102
scale: float # Zoom scale factor
103
104
class PinchStart(PointEvent):
105
"""Pinch/zoom gesture start event."""
106
107
class PinchEnd(PointEvent):
108
"""Pinch/zoom gesture end event."""
109
110
class MouseWheel(PointEvent):
111
"""Mouse wheel zoom event."""
112
delta: float # Wheel delta value
113
```
114
115
### Selection Events
116
117
Events related to data selection and highlighting.
118
119
```python { .api }
120
class SelectionGeometry(PlotEvent):
121
"""Selection geometry change event."""
122
geometry: Dict[str, Any] # Selection geometry specification
123
final: bool # Whether selection is finalized
124
125
class Reset(PlotEvent):
126
"""Plot reset event (clear selections, reset view)."""
127
128
class RangesUpdate(PlotEvent):
129
"""Plot ranges update event."""
130
x0: float # New x-range start
131
x1: float # New x-range end
132
y0: float # New y-range start
133
y1: float # New y-range end
134
```
135
136
### UI Component Events
137
138
Events from interactive UI components like buttons and widgets.
139
140
```python { .api }
141
class ButtonClick(ModelEvent):
142
"""Button click event."""
143
144
class MenuItemClick(ModelEvent):
145
"""Menu item selection event."""
146
item: str # Selected menu item value
147
148
class ValueSubmit(ModelEvent):
149
"""Value submission event (from input widgets)."""
150
value: Any # Submitted value
151
152
class LegendItemClick(ModelEvent):
153
"""Legend item click event."""
154
item: str # Legend item identifier
155
```
156
157
### Plot-Specific Events
158
159
Events related to plot rendering and lifecycle.
160
161
```python { .api }
162
class LODStart(PlotEvent):
163
"""Level-of-detail rendering start event."""
164
165
class LODEnd(PlotEvent):
166
"""Level-of-detail rendering end event."""
167
168
class AxisClick(PlotEvent):
169
"""Axis label or tick click event."""
170
axis: Axis # The clicked axis object
171
```
172
173
### Connection and Lifecycle Events
174
175
Events related to client-server connection and document lifecycle.
176
177
```python { .api }
178
class DocumentReady(DocumentEvent):
179
"""Document fully loaded and ready event."""
180
181
class ConnectionLost(DocumentEvent):
182
"""Server connection lost event."""
183
184
class ClientReconnected(DocumentEvent):
185
"""Client reconnected to server event."""
186
```
187
188
### Touch and Gesture Events
189
190
Events for touch-based interactions on mobile devices.
191
192
```python { .api }
193
class Rotate(PointEvent):
194
"""Rotation gesture event."""
195
rotation: float # Rotation angle in radians
196
197
class RotateStart(PointEvent):
198
"""Rotation gesture start event."""
199
200
class RotateEnd(PointEvent):
201
"""Rotation gesture end event."""
202
```
203
204
### Event Handling
205
206
Methods for registering event callbacks and handling events.
207
208
```python { .api }
209
# Event callback registration (available on all models)
210
def on_event(self, event_type, *callbacks):
211
"""
212
Register callbacks for specific event types.
213
214
Parameters:
215
- event_type: Event class or event name string
216
- callbacks: Callback functions to register
217
218
Each callback receives the event object as its argument.
219
"""
220
221
# Example callback signature
222
def event_callback(event: Event) -> None:
223
"""
224
Event callback function.
225
226
Parameters:
227
- event: The event object containing event data
228
"""
229
230
# JavaScript callbacks for client-side handling
231
class CustomJS:
232
"""JavaScript callback for client-side event handling."""
233
def __init__(self, args=None, code=""):
234
"""
235
Parameters:
236
- args: Dictionary of Python objects available in JavaScript
237
- code: JavaScript code string to execute
238
"""
239
240
args: Dict[str, Any] # Python objects available as JavaScript variables
241
code: str # JavaScript code to execute
242
```
243
244
## Usage Examples
245
246
### Basic Event Handling
247
248
```python
249
from bokeh.plotting import figure, show, curdoc
250
from bokeh.events import Tap
251
from bokeh.models import ColumnDataSource
252
import numpy as np
253
254
# Create data and plot
255
x = np.random.random(100)
256
y = np.random.random(100)
257
source = ColumnDataSource(data=dict(x=x, y=y))
258
259
p = figure(width=400, height=400, tools="tap", title="Click on points")
260
p.circle('x', 'y', source=source, size=10, alpha=0.6)
261
262
def tap_handler(event):
263
"""Handle tap events."""
264
print(f"Tapped at: ({event.x:.2f}, {event.y:.2f})")
265
266
# Register event handler
267
p.on_event(Tap, tap_handler)
268
269
# For server applications
270
curdoc().add_root(p)
271
272
# For standalone scripts
273
# show(p)
274
```
275
276
### Selection Event Handling
277
278
```python
279
from bokeh.plotting import figure, curdoc
280
from bokeh.events import SelectionGeometry
281
from bokeh.models import ColumnDataSource
282
import numpy as np
283
284
# Create data
285
n = 300
286
x = np.random.random(n)
287
y = np.random.random(n)
288
colors = np.random.choice(['red', 'green', 'blue'], n)
289
290
source = ColumnDataSource(data=dict(x=x, y=y, colors=colors))
291
292
p = figure(width=500, height=500, tools="box_select,lasso_select,reset",
293
title="Select points to see coordinates")
294
p.circle('x', 'y', source=source, color='colors', size=8, alpha=0.6)
295
296
def selection_handler(event):
297
"""Handle selection events."""
298
indices = source.selected.indices
299
print(f"Selected {len(indices)} points")
300
if indices:
301
selected_x = [source.data['x'][i] for i in indices]
302
selected_y = [source.data['y'][i] for i in indices]
303
print(f"X range: {min(selected_x):.2f} - {max(selected_x):.2f}")
304
print(f"Y range: {min(selected_y):.2f} - {max(selected_y):.2f}")
305
306
p.on_event(SelectionGeometry, selection_handler)
307
308
curdoc().add_root(p)
309
```
310
311
### Mouse Movement Tracking
312
313
```python
314
from bokeh.plotting import figure, curdoc
315
from bokeh.events import MouseMove
316
from bokeh.models import Div, Column
317
import numpy as np
318
319
# Create plot
320
x = np.linspace(0, 4*np.pi, 100)
321
y = np.sin(x)
322
323
p = figure(width=500, height=300, title="Mouse Movement Tracker")
324
p.line(x, y, line_width=2)
325
326
# Create info display
327
info = Div(text="<p>Move mouse over plot</p>", width=500)
328
329
def mouse_handler(event):
330
"""Handle mouse movement."""
331
info.text = f"""
332
<p><b>Mouse Position:</b></p>
333
<p>Data coordinates: ({event.x:.3f}, {event.y:.3f})</p>
334
<p>Screen coordinates: ({event.sx}, {event.sy})</p>
335
"""
336
337
p.on_event(MouseMove, mouse_handler)
338
339
layout = Column(p, info)
340
curdoc().add_root(layout)
341
```
342
343
### Button Click Events
344
345
```python
346
from bokeh.plotting import figure, curdoc
347
from bokeh.models import Button, ColumnDataSource, Column
348
from bokeh.events import ButtonClick
349
import numpy as np
350
351
# Create plot with dynamic data
352
source = ColumnDataSource(data=dict(x=[1, 2, 3], y=[1, 4, 2]))
353
354
p = figure(width=400, height=300, title="Dynamic Data")
355
line = p.line('x', 'y', source=source, line_width=3)
356
357
# Create control buttons
358
update_button = Button(label="Update Data", button_type="success")
359
reset_button = Button(label="Reset Data", button_type="primary")
360
361
def update_data():
362
"""Generate new random data."""
363
n = np.random.randint(5, 15)
364
new_data = dict(
365
x=sorted(np.random.random(n) * 10),
366
y=np.random.random(n) * 10
367
)
368
source.data = new_data
369
370
def reset_data():
371
"""Reset to original data."""
372
source.data = dict(x=[1, 2, 3], y=[1, 4, 2])
373
374
# Handle button clicks
375
def button_handler(event):
376
if event.model == update_button:
377
update_data()
378
elif event.model == reset_button:
379
reset_data()
380
381
update_button.on_event(ButtonClick, button_handler)
382
reset_button.on_event(ButtonClick, button_handler)
383
384
layout = Column(p, update_button, reset_button)
385
curdoc().add_root(layout)
386
```
387
388
### JavaScript Callbacks for Client-Side Events
389
390
```python
391
from bokeh.plotting import figure, show
392
from bokeh.models import CustomJS, ColumnDataSource, Slider, Column
393
from bokeh.events import MouseMove
394
import numpy as np
395
396
# Create data
397
x = np.linspace(0, 4*np.pi, 100)
398
y = np.sin(x)
399
source = ColumnDataSource(data=dict(x=x, y=y))
400
401
# Create plot
402
p = figure(width=500, height=300, title="Client-Side Interaction")
403
line = p.line('x', 'y', source=source, line_width=2)
404
405
# JavaScript callback for mouse events
406
mouse_callback = CustomJS(args=dict(p=p), code="""
407
// This runs in the browser without server communication
408
console.log('Mouse at:', cb_obj.x, cb_obj.y);
409
410
// Update plot title with coordinates
411
p.title.text = 'Mouse at: (' + cb_obj.x.toFixed(2) + ', ' + cb_obj.y.toFixed(2) + ')';
412
""")
413
414
p.js_on_event('mousemove', mouse_callback)
415
416
# Slider with JavaScript callback
417
slider = Slider(start=0, end=2, value=1, step=0.1, title="Frequency")
418
419
slider_callback = CustomJS(args=dict(source=source), code="""
420
const data = source.data;
421
const f = cb_obj.value;
422
const x = data['x'];
423
const y = data['y'];
424
425
for (let i = 0; i < x.length; i++) {
426
y[i] = Math.sin(f * x[i]);
427
}
428
429
source.change.emit();
430
""")
431
432
slider.js_on_change('value', slider_callback)
433
434
layout = Column(p, slider)
435
show(layout)
436
```
437
438
### Range Update Events
439
440
```python
441
from bokeh.plotting import figure, curdoc
442
from bokeh.events import RangesUpdate
443
from bokeh.models import Div, Column
444
import numpy as np
445
446
# Create plot with pan/zoom tools
447
x = np.random.random(1000)
448
y = np.random.random(1000)
449
450
p = figure(width=500, height=400, tools="pan,wheel_zoom,box_zoom,reset",
451
title="Pan and zoom to see range updates")
452
p.circle(x, y, size=5, alpha=0.5)
453
454
# Info display
455
info = Div(text="<p>Pan or zoom to see range updates</p>", width=500)
456
457
def range_handler(event):
458
"""Handle range update events."""
459
info.text = f"""
460
<p><b>Current View Ranges:</b></p>
461
<p>X: {event.x0:.3f} to {event.x1:.3f}</p>
462
<p>Y: {event.y0:.3f} to {event.y1:.3f}</p>
463
<p>Area: {(event.x1-event.x0)*(event.y1-event.y0):.6f}</p>
464
"""
465
466
p.on_event(RangesUpdate, range_handler)
467
468
layout = Column(p, info)
469
curdoc().add_root(layout)
470
```
471
472
### Multi-Event Handler
473
474
```python
475
from bokeh.plotting import figure, curdoc
476
from bokeh.events import Tap, DoubleTap, Pan, MouseWheel
477
from bokeh.models import Div, Column
478
import numpy as np
479
480
# Create interactive plot
481
x = np.random.random(200)
482
y = np.random.random(200)
483
484
p = figure(width=500, height=400, tools="pan,tap",
485
title="Multi-Event Demo")
486
circles = p.circle(x, y, size=8, alpha=0.6, color='blue')
487
488
# Event log
489
log = Div(text="<p>Event log:</p>", width=500, height=200)
490
491
def event_logger(event):
492
"""Log different types of events."""
493
event_type = type(event).__name__
494
495
if hasattr(event, 'x') and hasattr(event, 'y'):
496
message = f"{event_type} at ({event.x:.2f}, {event.y:.2f})"
497
else:
498
message = f"{event_type} event"
499
500
# Add to log (keep last 10 entries)
501
lines = log.text.split('<br>')
502
if len(lines) > 10:
503
lines = lines[-9:] # Keep last 9 + new one = 10
504
505
lines.append(message)
506
log.text = '<br>'.join(lines)
507
508
# Register multiple event types
509
p.on_event(Tap, event_logger)
510
p.on_event(DoubleTap, event_logger)
511
p.on_event(Pan, event_logger)
512
p.on_event(MouseWheel, event_logger)
513
514
layout = Column(p, log)
515
curdoc().add_root(layout)
516
```