7.3. Visualization Libraries#

ipywidgets gives you controls. Matplotlib gives you plots. But neither was designed with interactivity as its first priority. To build charts that zoom, pan, animate, and respond to hover events without any widget plumbing, you need a library that treats interactivity as a first-class feature.

Two libraries dominate this space: Plotly and Bokeh. They work differently and are suited to different situations, but both transform Python data into browser-ready, interactive graphics. Understanding when to use each — and how to combine them with ipywidgets — rounds out the interactive visualization toolkit.


7.3.1. Plotly#

Plotly renders charts as interactive HTML using the Plotly.js engine under the hood. Every chart it produces supports zooming, panning, hover tooltips, and legend interaction without any extra code. The high-level plotly.express API follows the same conventions as many pandas operations, which makes it quick to learn.

pip install plotly

7.3.1.1. A First Plotly Chart#

The Iris scatter plot from the introduction chapter is worth revisiting here to show the full Plotly workflow:

import plotly.express as px
import plotly.io as pio
from sklearn.datasets import load_iris
import pandas as pd

pio.renderers.default = "notebook"

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = [iris.target_names[i] for i in iris.target]

fig = px.scatter(
    df,
    x='sepal length (cm)',
    y='sepal width (cm)',
    color='species',
    size='petal length (cm)',
    hover_data=iris.feature_names,
    title='Iris — Sepal dimensions coloured by species',
    template='plotly_white',
)
fig.show()

The size parameter maps a third variable to marker size with no extra configuration. The hover_data list controls exactly what appears in the tooltip. Both details go a long way toward making the chart genuinely informative rather than merely interactive.

7.3.1.2. More Chart Types#

plotly.express follows a consistent grammar across chart types, so once you know the scatter syntax, most others follow naturally:

# Parallel coordinates — useful for high-dimensional data
# px.parallel_coordinates requires a numeric color column; map species string → integer
df['species_id'] = iris.target   # 0 = setosa, 1 = versicolor, 2 = virginica

fig_parallel = px.parallel_coordinates(
    df,
    color='species_id',
    dimensions=iris.feature_names,
    color_continuous_scale=px.colors.diverging.Tealrose,
    color_continuous_midpoint=1,
    labels={'species_id': 'species (0/1/2)'},
    title='Iris — Parallel Coordinates',
)
fig_parallel.show()
# Animated scatter — useful for temporal data
# Here we simulate time by using each feature as a "frame"
fig_hist = px.histogram(
    df,
    x='petal length (cm)',
    color='species',
    barmode='overlay',
    nbins=30,
    opacity=0.7,
    title='Petal Length Distribution by Species',
    template='plotly_white',
)
fig_hist.show()

7.3.1.3. Combining Plotly with ipywidgets#

Plotly handles interaction inside the chart. ipywidgets handles interaction outside it — changing what the chart shows. Combining them gives you both.

import ipywidgets as widgets
from IPython.display import display
import plotly.graph_objects as go
import numpy as np

output = widgets.Output()

x_axis = widgets.Dropdown(
    options=iris.feature_names,
    value=iris.feature_names[0],
    description='X axis:',
)
y_axis = widgets.Dropdown(
    options=iris.feature_names,
    value=iris.feature_names[1],
    description='Y axis:',
)

def update_chart(_change):
    with output:
        output.clear_output(wait=True)
        fig = px.scatter(
            df,
            x=x_axis.value,
            y=y_axis.value,
            color='species',
            hover_data=iris.feature_names,
            template='plotly_white',
            title=f'{x_axis.value}  vs  {y_axis.value}',
        )
        fig.show()

x_axis.observe(update_chart, names='value')
y_axis.observe(update_chart, names='value')

display(widgets.HBox([x_axis, y_axis]))
display(output)
update_chart(None)

The dropdowns control which features appear on each axis. The chart itself then handles zooming, hovering, and legend interaction. Each layer does what it is best at.


7.3.2. Bokeh#

Bokeh takes a different approach. Where Plotly’s high-level API is designed for quick, polished charts, Bokeh is designed for fine-grained control over layout, interaction, and streaming data. It renders to HTML and JavaScript, and it has a powerful server mode (bokeh serve) for deploying fully interactive dashboards outside of notebooks.

pip install bokeh
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.transform import factor_cmap
from bokeh.palettes import Category10
import pandas as pd

output_notebook()

source_df = df.copy()
species_list = list(df['species'].unique())

p = figure(
    width=600, height=400,
    title='Iris — Sepal dimensions (Bokeh)',
    x_axis_label='Sepal length (cm)',
    y_axis_label='Sepal width (cm)',
    toolbar_location='above',
)

p.circle(
    x='sepal length (cm)',
    y='sepal width (cm)',
    source=source_df,
    size=8,
    color=factor_cmap('species', palette=Category10[3], factors=species_list),
    legend_field='species',
    alpha=0.7,
    line_color='white',
    line_width=0.5,
)

p.legend.location = 'top_right'
p.legend.click_policy = 'hide'   # clicking a legend item hides that group
show(p)
Loading BokehJS ...
BokehDeprecationWarning: 'circle() method with size value' was deprecated in Bokeh 3.4.0 and will be removed, use 'scatter(size=...) instead' instead.

The legend.click_policy = 'hide' line is one of many small pieces of built-in interaction that Bokeh provides without any callback code.

Bokeh’s ColumnDataSource model is worth learning if you expect to update charts dynamically. Instead of replotting from scratch on every callback, you update the data source and Bokeh propagates the change to the rendered chart efficiently — a significant performance advantage for large datasets.


7.3.3. Choosing Between Them#

There is no universal answer, but the following heuristics cover most situations:

Situation

Recommended library

Quick exploratory chart with hover and zoom

Plotly Express

Polished publication or report figure

Plotly (with fig.update_layout)

Fine control over individual glyphs and axes

Bokeh

Streaming or real-time updating data

Bokeh

Deploying a standalone dashboard (no Jupyter)

Bokeh serve or Plotly Dash

Combining charts with ipywidgets in a notebook

Either; Plotly is slightly simpler

Both libraries are mature, well-documented, and capable. The practical advice is to pick one, learn it well, and switch only when you hit a genuine limitation.

Note

Plotly Dash and Panel are worth knowing about if you eventually want to deploy interactive visualizations as standalone web applications — a natural extension of what we have covered in this chapter.