mixed_markers_streaming#
Exercise mixed-marker selection overrides and streaming updates.
Click the button repeatedly: marker metadata, geometry, and selection masks are updated without recreating the plot or its WebGL resources.
import numpy as np
from bokeh.layouts import column
from bokeh.models import Button, ColumnDataSource, CustomJS, Scatter
from bokeh.plotting import figure, show
rng = np.random.default_rng(7)
markers = ["circle", "square", "triangle", "diamond", "hex", "star"]
n = 20_000
source = ColumnDataSource(data=dict(
x=rng.normal(size=n),
y=rng.normal(size=n),
marker=[markers[i % len(markers)] for i in range(n)],
size=np.full(n, 7),
selected_size=np.full(n, 18),
color=np.where(np.arange(n) % 2 == 0, "#3b82f6", "#f97316"),
))
source.selected.indices = list(range(0, n, 997))
p = figure(width=900, height=520, output_backend="webgl",
title="Mixed markers: cached masks, derived sizes, stream/patch refresh")
renderer = p.scatter("x", "y", marker="marker", size="size", color="color",
fill_alpha=0.55, line_alpha=0.8, source=source)
renderer.selection_glyph = Scatter(
size="selected_size", fill_color="color", line_color="white", line_width=2,
)
button = Button(label="Stream and mutate marker types", button_type="primary", width=260)
button.js_on_click(CustomJS(args=dict(source=source, markers=markers), code="""
const data = source.data
const i = Math.floor(Math.random()*data.x.length)
data.marker[i] = markers[(markers.indexOf(data.marker[i]) + 1) % markers.length]
data.size[i] = 12
source.patch({marker: [[i, data.marker[i]]], size: [[i, 12]]})
source.stream({
x: [2*Math.random() - 1], y: [2*Math.random() - 1],
marker: [markers[data.x.length % markers.length]], size: [10],
selected_size: [22], color: ["#22c55e"],
}, 25000)
source.selected.indices = [i, data.x.length - 1]
"""))
show(column(button, p))