In this tutorial, we create a fully interactive, visually compelling data visualization dashboard using Bokeh. We start by turning raw data into insightful plots, then enhance them with features such as linked brushing, color gradients, and real-time filters powered by dropdowns and sliders. As we progress, we bring our dashboard to life with Custom JavaScript (CustomJS) interactivity, enabling instant browser-side responses without a single Python callback. By blending the best of Python’s analytical strength with JavaScript’s responsiveness, we build a seamless, dynamic dashboard experience that redefines how we visualize and interact with data. Check out the FULL CODES here.
!pip install bokeh pandas numpy scipy -q
import numpy as np
import pandas as pd
from bokeh.io import output_notebook, show, export_png, output_file
from bokeh.plotting import figure
from bokeh.layouts import row, column, gridplot
from bokeh.models import (
ColumnDataSource, HoverTool, LassoSelectTool, BoxSelectTool, TapTool,
ColorBar, LinearColorMapper, BasicTicker, PrintfTickFormatter, Slider,
Select, CheckboxGroup, CustomJS, CDSView, BooleanFilter, Div, Button
)
from bokeh.palettes import Viridis256
from bokeh.models.widgets import DataTable, TableColumn
output_notebook()
np.random.seed(42)
N = 300
data = pd.DataFrame({
"temp_c": 20 + 5 * np.random.randn(N),
"pressure_kpa": 101 + 3 * np.random.randn(N),
"humidity_pct": 40 + 15 * np.random.randn(N),
"sensor_id": np.random.choice(["A1","A2","B7","C3"], size=N),
"timestep": np.arange(N)
})
source_main = ColumnDataSource(data)
p_scatter = figure(title="Temperature vs Pressure", width=400, height=300,
x_axis_label="Temperature (°C)", y_axis_label="Pressure (kPa)",
tools="pan,wheel_zoom,reset")
scat = p_scatter.circle(x="temp_c", y="pressure_kpa", size=8, fill_alpha=0.6,
fill_color="orange", line_color="black", source=source_main,
legend_label="Sensor Readings")
hover = HoverTool(tooltips=[
("Temp (°C)", "@temp_c{0.0}"), ("Pressure", "@pressure_kpa{0.0} kPa"),
("Humidity", "@humidity_pct{0.0}%"), ("Sensor", "@sensor_id"),
("Timestep", "@timestep")], renderers=[scat])
p_scatter.add_tools(hover)
p_scatter.legend.location = "top_left"
show(p_scatter)
We begin by setting up our environment and importing all the necessary libraries. We then create a synthetic dataset and visualize temperature against pressure using a simple scatter plot with hover functionality. This helps us establish a foundation for our interactive dashboard. Check out the FULL CODES here.
p_humidity = figure(title="Humidity vs Temperature (Linked Selection)", width=400, height=300,
x_axis_label="Temperature (°C)", y_axis_label="Humidity (%)",
tools="pan,wheel_zoom,reset,box_select,lasso_select,tap")
r2 = p_humidity.square(x="temp_c", y="humidity_pct", size=8, fill_alpha=0.6,
fill_color="navy", line_color="white", source=source_main)
p_humidity.add_tools(HoverTool(tooltips=[
("Temp (°C)", "@temp_c{0.0}"), ("Humidity", "@humidity_pct{0.0}%"),
("Sensor", "@sensor_id")], renderers=[r2]))
layout_linked = row(p_scatter, p_humidity)
show(layout_linked)
We extend our visualization by adding another plot that links humidity and temperature through shared data. We use linked brushing so that selections in one plot automatically reflect in the other, helping us analyze relationships across multiple variables simultaneously. Check out the FULL CODES here.
color_mapper = LinearColorMapper(palette=Viridis256, low=data["humidity_pct"].min(),
high=data["humidity_pct"].max())
p_color = figure(title="Pressure vs Humidity (Colored by Humidity)", width=500, height=350,
x_axis_label="Pressure (kPa)", y_axis_label="Humidity (%)",
tools="pan,wheel_zoom,reset,box_select,lasso_select")
r3 = p_color.circle(x="pressure_kpa", y="humidity_pct", size=8, fill_alpha=0.8,
line_color=None, color={"field": "humidity_pct", "transform": color_mapper},
source=source_main)
color_bar = ColorBar(color_mapper=color_mapper, ticker=BasicTicker(desired_num_ticks=5),
formatter=PrintfTickFormatter(format="%4.1f%%"), label_standoff=8,
border_line_color=None, location=(0,0), title="Humidity %")
p_color.add_layout(color_bar, "right")
show(p_color)
We enhance our visualization by introducing a continuous color mapping feature to represent humidity levels. By adding a color bar and gradient, we make our chart more informative and intuitive, allowing us to interpret variations visually. Check out the FULL CODES here.
sensor_options = sorted(data["sensor_id"].unique().tolist())
sensor_select = Select(title="Sensor ID Filter", value=sensor_options[0], options=sensor_options)
temp_slider = Slider(title="Max Temperature (°C)",
start=float(data["temp_c"].min()),
end=float(data["temp_c"].max()), step=0.5,
value=float(data["temp_c"].max()))
columns_available = ["temp_c", "pressure_kpa", "humidity_pct", "sensor_id", "timestep"]
checkbox_group = CheckboxGroup(labels=columns_available,
active=list(range(len(columns_available))))
def filter_mask(sensor_val, max_temp):
return [(s == sensor_val) and (t Interactive Filters"), sensor_select,
temp_slider, Div(text="Columns in Table"), checkbox_group)
dashboard_layout = row(column(p_filtered, table_widget), dashboard_controls)
show(dashboard_layout)
We introduce interactivity through widgets such as dropdowns, sliders, and checkboxes. We dynamically filter data and update tables in real time, enabling us to easily explore different subsets and attributes of the dataset. Check out the FULL CODES here.
mini_source = ColumnDataSource({
"x": np.linspace(0, 2*np.pi, 80),
"y": np.sin(np.linspace(0, 2*np.pi, 80))
})
p_wave = figure(title="Sine Wave (CustomJS: Enlarge points)", width=400, height=250,
tools="pan,wheel_zoom,reset")
wave_render = p_wave.circle(x="x", y="y", size=6, fill_alpha=0.8,
fill_color="green", line_color="black", source=mini_source)
js_callback = CustomJS(args=dict(r=wave_render),
code="const new_size = r.glyph.size.value + 2; r.glyph.size = new_size;")
grow_button = Button(label="Enlarge points (CustomJS)", button_type="success")
grow_button.js_on_click(js_callback)
show(column(p_wave, grow_button))
We implement a JavaScript-based interaction using Bokeh’s CustomJS. We create a sine wave visualization and allow users to enlarge the plot markers with a button click, demonstrating client-side control without any Python callbacks. Check out the FULL CODES here.
stream_source = ColumnDataSource({"t": [], "val": []})
p_stream = figure(title="Streaming Sensor Value", width=500, height=250,
x_axis_label="timestep", y_axis_label="value",
tools="pan,wheel_zoom,reset")
p_stream.line(x="t", y="val", source=stream_source, line_width=3, line_alpha=0.8)
p_stream.circle(x="t", y="val", source=stream_source, size=6, fill_color="red")
show(p_stream)
for t in range(10):
new_point = {"t": [t], "val": [np.sin(t/2) + 0.2*np.random.randn()]}
stream_source.stream(new_point, rollover=200)
show(p_stream)
We simulate a live data stream by continuously adding new data points to our plot. We watch the visualization update dynamically, showcasing how Bokeh can handle real-time data and provide instant visual feedback.
In conclusion, we create a fully functional, real-time, and browser-interactive dashboard that showcases the full potential of Bokeh. We learn how to visualize multiple dimensions of data, dynamically filter and update visuals, and even harness JavaScript integration to make instant, client-side updates directly within the browser. This hands-on experience shows us how Bokeh effortlessly merges Python and JavaScript, empowering us to design dashboards that are not just interactive but intelligent, responsive, and production-ready.
Check out the FULL CODES here. Feel free to check out our GitHub Page for Tutorials, Codes and Notebooks. Also, feel free to follow us on Twitter and don’t forget to join our 100k+ ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.
Asif Razzaq is the CEO of Marktechpost Media Inc.. As a visionary entrepreneur and engineer, Asif is committed to harnessing the potential of Artificial Intelligence for social good. His most recent endeavor is the launch of an Artificial Intelligence Media Platform, Marktechpost, which stands out for its in-depth coverage of machine learning and deep learning news that is both technically sound and easily understandable by a wide audience. The platform boasts of over 2 million monthly views, illustrating its popularity among audiences.

