How to build a fully interactive, real-time visual dashboard using Bokeh and custom JavaScript?
In this tutorial, we create a fully interactive, visually compelling data visualization dashboard using Bokeh. We first transform raw data into insightful charts, then enhance them with features like linked brushes, color gradients, and live filters driven by dropdowns and sliders. As we progressed, we brought the dashboard to life with custom JavaScript (CustomJS) interactivity, enabling instant browser-side response without the need for a single Python callback. By combining the best analytical capabilities of Python with the responsiveness of JavaScript, we build seamless, dynamic dashboard experiences that redefine the way we visualize and interact with data. Check The complete code is 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 first set up the environment and import all necessary libraries. We then create a synthetic dataset and visualize temperature versus pressure using a simple scatter plot with hover functionality. This helps us lay the foundation for an interactive dashboard. Check The complete code is 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 the visualization by adding another plot linking humidity and temperature through shared data. We use link brushes so that selections in one plot are automatically reflected in another plot, helping us analyze relationships between multiple variables simultaneously. Check The complete code is 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 the visualization by introducing a continuous color mapping feature to represent humidity levels. By adding color bars and gradients, we make the chart more informative and intuitive, allowing us to visually explain changes. Check The complete code is here.
sensoroptions = sort(data["sensor_id"].unique().tolist())sensor_select = Select(title="Sensor ID Filter", value=sensor_options[0]options=sensor_options) temp_slider = Slider(title="Maximum temperature (°C)", start=float(data["temp_c"].min()), end=float(data["temp_c"].max()), step=0.5, value=floating point(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": [],"value": []}) p_stream = Figure(title="stream 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],"value": [np.sin(t/2) + 0.2*np.random.randn()]}stream_source.stream(new_point, rollover=200) show(p_stream)
We simulate real-time data flow by continuously adding new data points to the graph. We dynamically watch the visualization update, showing how Bokeh processes real-time data and provides instant visual feedback.
In summary, we created a fully functional, real-time, browser-interactive dashboard that showcases Bokeh’s full potential. We learn how to visualize data across multiple dimensions, dynamically filter and update visuals, and even leverage JavaScript integration for instant client-side updates directly in the browser. This hands-on experience showed us how easily Bokeh blends Python and JavaScript, allowing us to design dashboards that are not only interactive, but also smart, responsive, and production-ready.
Check The complete code is here. Please feel free to check out our GitHub page for tutorials, code, and notebooks. In addition, welcome to follow us twitter And don’t forget to join our 100k+ ML SubReddit and subscribe our newsletter. wait! Are you using Telegram? Now you can also join us via telegram.
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 the benefit of society. His most recent endeavor is the launch of Marktechpost, an AI media platform that stands out for its in-depth coverage of machine learning and deep learning news that is technically sound and easy to understand for a broad audience. The platform has more than 2 million monthly views, which shows that it is very popular among viewers.
🙌 FOLLOW MARKTECHPOST: Add us as your go-to source on Google.