Interactive plots and applications in the browser from Python
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Core event classes that form the foundation of Bokeh's event system.
class Event:
"""
Base class for all Bokeh events.
All events inherit from this class and provide common properties.
"""
model: Model # The model that generated the event
event_name: str # Name of the event type
class DocumentEvent(Event):
"""
Document-level events.
Events that occur at the document level, affecting the entire application.
"""
document: Document # The document where the event occurred
class ModelEvent(Event):
"""
Model-level events.
Events that occur on specific model objects.
"""
class PlotEvent(Event):
"""
Plot-specific events.
Events that occur within plot areas.
"""
class PointEvent(PlotEvent):
"""
Events with coordinate information.
Events that have associated x/y coordinates in data space.
"""
x: float # X coordinate in data space
y: float # Y coordinate in data space
sx: int # X coordinate in screen space (pixels)
sy: int # Y coordinate in screen space (pixels)Events triggered by mouse interactions within plots.
class Tap(PointEvent):
"""Single mouse click/tap event."""
class DoubleTap(PointEvent):
"""Double mouse click/tap event."""
class Press(PointEvent):
"""Mouse button press event."""
class PressUp(PointEvent):
"""Mouse button release event."""
class MouseEnter(PointEvent):
"""Mouse cursor enters plot area."""
class MouseLeave(PointEvent):
"""Mouse cursor leaves plot area."""
class MouseMove(PointEvent):
"""Mouse cursor movement within plot."""
class MouseWheel(PointEvent):
"""Mouse wheel scroll event."""
delta: float # Scroll delta valueEvents related to plot navigation and viewport changes.
class Pan(PointEvent):
"""Pan/drag event during movement."""
direction: int # Pan direction
class PanStart(PointEvent):
"""Pan/drag start event."""
class PanEnd(PointEvent):
"""Pan/drag end event."""
class Pinch(PointEvent):
"""Pinch/zoom gesture event."""
scale: float # Zoom scale factor
class PinchStart(PointEvent):
"""Pinch/zoom gesture start event."""
class PinchEnd(PointEvent):
"""Pinch/zoom gesture end event."""
class MouseWheel(PointEvent):
"""Mouse wheel zoom event."""
delta: float # Wheel delta valueEvents related to data selection and highlighting.
class SelectionGeometry(PlotEvent):
"""Selection geometry change event."""
geometry: Dict[str, Any] # Selection geometry specification
final: bool # Whether selection is finalized
class Reset(PlotEvent):
"""Plot reset event (clear selections, reset view)."""
class RangesUpdate(PlotEvent):
"""Plot ranges update event."""
x0: float # New x-range start
x1: float # New x-range end
y0: float # New y-range start
y1: float # New y-range endEvents from interactive UI components like buttons and widgets.
class ButtonClick(ModelEvent):
"""Button click event."""
class MenuItemClick(ModelEvent):
"""Menu item selection event."""
item: str # Selected menu item value
class ValueSubmit(ModelEvent):
"""Value submission event (from input widgets)."""
value: Any # Submitted value
class LegendItemClick(ModelEvent):
"""Legend item click event."""
item: str # Legend item identifierEvents related to plot rendering and lifecycle.
class LODStart(PlotEvent):
"""Level-of-detail rendering start event."""
class LODEnd(PlotEvent):
"""Level-of-detail rendering end event."""
class AxisClick(PlotEvent):
"""Axis label or tick click event."""
axis: Axis # The clicked axis objectEvents related to client-server connection and document lifecycle.
class DocumentReady(DocumentEvent):
"""Document fully loaded and ready event."""
class ConnectionLost(DocumentEvent):
"""Server connection lost event."""
class ClientReconnected(DocumentEvent):
"""Client reconnected to server event."""Events for touch-based interactions on mobile devices.
class Rotate(PointEvent):
"""Rotation gesture event."""
rotation: float # Rotation angle in radians
class RotateStart(PointEvent):
"""Rotation gesture start event."""
class RotateEnd(PointEvent):
"""Rotation gesture end event."""Methods for registering event callbacks and handling events.
# Event callback registration (available on all models)
def on_event(self, event_type, *callbacks):
"""
Register callbacks for specific event types.
Parameters:
- event_type: Event class or event name string
- callbacks: Callback functions to register
Each callback receives the event object as its argument.
"""
# Example callback signature
def event_callback(event: Event) -> None:
"""
Event callback function.
Parameters:
- event: The event object containing event data
"""
# JavaScript callbacks for client-side handling
class CustomJS:
"""JavaScript callback for client-side event handling."""
def __init__(self, args=None, code=""):
"""
Parameters:
- args: Dictionary of Python objects available in JavaScript
- code: JavaScript code string to execute
"""
args: Dict[str, Any] # Python objects available as JavaScript variables
code: str # JavaScript code to executefrom bokeh.plotting import figure, show, curdoc
from bokeh.events import Tap
from bokeh.models import ColumnDataSource
import numpy as np
# Create data and plot
x = np.random.random(100)
y = np.random.random(100)
source = ColumnDataSource(data=dict(x=x, y=y))
p = figure(width=400, height=400, tools="tap", title="Click on points")
p.circle('x', 'y', source=source, size=10, alpha=0.6)
def tap_handler(event):
"""Handle tap events."""
print(f"Tapped at: ({event.x:.2f}, {event.y:.2f})")
# Register event handler
p.on_event(Tap, tap_handler)
# For server applications
curdoc().add_root(p)
# For standalone scripts
# show(p)from bokeh.plotting import figure, curdoc
from bokeh.events import SelectionGeometry
from bokeh.models import ColumnDataSource
import numpy as np
# Create data
n = 300
x = np.random.random(n)
y = np.random.random(n)
colors = np.random.choice(['red', 'green', 'blue'], n)
source = ColumnDataSource(data=dict(x=x, y=y, colors=colors))
p = figure(width=500, height=500, tools="box_select,lasso_select,reset",
title="Select points to see coordinates")
p.circle('x', 'y', source=source, color='colors', size=8, alpha=0.6)
def selection_handler(event):
"""Handle selection events."""
indices = source.selected.indices
print(f"Selected {len(indices)} points")
if indices:
selected_x = [source.data['x'][i] for i in indices]
selected_y = [source.data['y'][i] for i in indices]
print(f"X range: {min(selected_x):.2f} - {max(selected_x):.2f}")
print(f"Y range: {min(selected_y):.2f} - {max(selected_y):.2f}")
p.on_event(SelectionGeometry, selection_handler)
curdoc().add_root(p)from bokeh.plotting import figure, curdoc
from bokeh.events import MouseMove
from bokeh.models import Div, Column
import numpy as np
# Create plot
x = np.linspace(0, 4*np.pi, 100)
y = np.sin(x)
p = figure(width=500, height=300, title="Mouse Movement Tracker")
p.line(x, y, line_width=2)
# Create info display
info = Div(text="<p>Move mouse over plot</p>", width=500)
def mouse_handler(event):
"""Handle mouse movement."""
info.text = f"""
<p><b>Mouse Position:</b></p>
<p>Data coordinates: ({event.x:.3f}, {event.y:.3f})</p>
<p>Screen coordinates: ({event.sx}, {event.sy})</p>
"""
p.on_event(MouseMove, mouse_handler)
layout = Column(p, info)
curdoc().add_root(layout)from bokeh.plotting import figure, curdoc
from bokeh.models import Button, ColumnDataSource, Column
from bokeh.events import ButtonClick
import numpy as np
# Create plot with dynamic data
source = ColumnDataSource(data=dict(x=[1, 2, 3], y=[1, 4, 2]))
p = figure(width=400, height=300, title="Dynamic Data")
line = p.line('x', 'y', source=source, line_width=3)
# Create control buttons
update_button = Button(label="Update Data", button_type="success")
reset_button = Button(label="Reset Data", button_type="primary")
def update_data():
"""Generate new random data."""
n = np.random.randint(5, 15)
new_data = dict(
x=sorted(np.random.random(n) * 10),
y=np.random.random(n) * 10
)
source.data = new_data
def reset_data():
"""Reset to original data."""
source.data = dict(x=[1, 2, 3], y=[1, 4, 2])
# Handle button clicks
def button_handler(event):
if event.model == update_button:
update_data()
elif event.model == reset_button:
reset_data()
update_button.on_event(ButtonClick, button_handler)
reset_button.on_event(ButtonClick, button_handler)
layout = Column(p, update_button, reset_button)
curdoc().add_root(layout)from bokeh.plotting import figure, show
from bokeh.models import CustomJS, ColumnDataSource, Slider, Column
from bokeh.events import MouseMove
import numpy as np
# Create data
x = np.linspace(0, 4*np.pi, 100)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))
# Create plot
p = figure(width=500, height=300, title="Client-Side Interaction")
line = p.line('x', 'y', source=source, line_width=2)
# JavaScript callback for mouse events
mouse_callback = CustomJS(args=dict(p=p), code="""
// This runs in the browser without server communication
console.log('Mouse at:', cb_obj.x, cb_obj.y);
// Update plot title with coordinates
p.title.text = 'Mouse at: (' + cb_obj.x.toFixed(2) + ', ' + cb_obj.y.toFixed(2) + ')';
""")
p.js_on_event('mousemove', mouse_callback)
# Slider with JavaScript callback
slider = Slider(start=0, end=2, value=1, step=0.1, title="Frequency")
slider_callback = CustomJS(args=dict(source=source), code="""
const data = source.data;
const f = cb_obj.value;
const x = data['x'];
const y = data['y'];
for (let i = 0; i < x.length; i++) {
y[i] = Math.sin(f * x[i]);
}
source.change.emit();
""")
slider.js_on_change('value', slider_callback)
layout = Column(p, slider)
show(layout)from bokeh.plotting import figure, curdoc
from bokeh.events import RangesUpdate
from bokeh.models import Div, Column
import numpy as np
# Create plot with pan/zoom tools
x = np.random.random(1000)
y = np.random.random(1000)
p = figure(width=500, height=400, tools="pan,wheel_zoom,box_zoom,reset",
title="Pan and zoom to see range updates")
p.circle(x, y, size=5, alpha=0.5)
# Info display
info = Div(text="<p>Pan or zoom to see range updates</p>", width=500)
def range_handler(event):
"""Handle range update events."""
info.text = f"""
<p><b>Current View Ranges:</b></p>
<p>X: {event.x0:.3f} to {event.x1:.3f}</p>
<p>Y: {event.y0:.3f} to {event.y1:.3f}</p>
<p>Area: {(event.x1-event.x0)*(event.y1-event.y0):.6f}</p>
"""
p.on_event(RangesUpdate, range_handler)
layout = Column(p, info)
curdoc().add_root(layout)from bokeh.plotting import figure, curdoc
from bokeh.events import Tap, DoubleTap, Pan, MouseWheel
from bokeh.models import Div, Column
import numpy as np
# Create interactive plot
x = np.random.random(200)
y = np.random.random(200)
p = figure(width=500, height=400, tools="pan,tap",
title="Multi-Event Demo")
circles = p.circle(x, y, size=8, alpha=0.6, color='blue')
# Event log
log = Div(text="<p>Event log:</p>", width=500, height=200)
def event_logger(event):
"""Log different types of events."""
event_type = type(event).__name__
if hasattr(event, 'x') and hasattr(event, 'y'):
message = f"{event_type} at ({event.x:.2f}, {event.y:.2f})"
else:
message = f"{event_type} event"
# Add to log (keep last 10 entries)
lines = log.text.split('<br>')
if len(lines) > 10:
lines = lines[-9:] # Keep last 9 + new one = 10
lines.append(message)
log.text = '<br>'.join(lines)
# Register multiple event types
p.on_event(Tap, event_logger)
p.on_event(DoubleTap, event_logger)
p.on_event(Pan, event_logger)
p.on_event(MouseWheel, event_logger)
layout = Column(p, log)
curdoc().add_root(layout)