Plotly interaction#

  • Here we show some basic ways of bringing Plotly graphs to life.

# The following renders plotly graphs in Jupyter Notebook, Jupyter Lab and VS Code formats
import plotly.io as pio
pio.renderers.default = "notebook+plotly_mimetype"

Animation#

  • Many plot types can easily be animated.

  • Parameters animation_frame and animation_group control what is animated

# Gapminder dataset of health and wealth stats for different countries
import plotly.express as px
df = px.data.gapminder()
df.head()
country continent year lifeExp pop gdpPercap iso_alpha iso_num
0 Afghanistan Asia 1952 28.801 8425333 779.445314 AFG 4
1 Afghanistan Asia 1957 30.332 9240934 820.853030 AFG 4
2 Afghanistan Asia 1962 31.997 10267083 853.100710 AFG 4
3 Afghanistan Asia 1967 34.020 11537966 836.197138 AFG 4
4 Afghanistan Asia 1972 36.088 13079460 739.981106 AFG 4
# Select a single year
df_year = px.data.gapminder().query("year == 1952")
px.scatter(df_year, x="gdpPercap", y="lifeExp",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])
# Animate all years
px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])

Plotly templates#

  • Themes can easily be changed to one in the default set or be tuned specifically.

import plotly.io as pio
pio.templates
Templates configuration
-----------------------
    Default template: 'plotly'
    Available templates:
        ['ggplot2', 'seaborn', 'simple_white', 'plotly',
         'plotly_white', 'plotly_dark', 'presentation', 'xgridoff',
         'ygridoff', 'gridon', 'none']
# https://plotly.com/python/templates/
# Animate all years
px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
           size="pop", color="continent", hover_name="country",
           log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90], template='plotly_dark')
# Play with me!

Plotly callbacks#

  • The following code generates a random scatter.

  • The callback update_point changes markers based on clicking and selection.

import plotly.graph_objects as go

import numpy as np
np.random.seed(1)

x = np.random.rand(100)
y = np.random.rand(100)

# Main plot
f = go.FigureWidget([go.Scatter(x=x, y=y, mode='markers')])

# Attributes of the scatter object
scatter = f.data[0]
colors = ['#a3a7e4'] * 100
scatter.marker.color = colors
scatter.marker.size = [10] * 100
f.layout.hovermode = 'closest'

# Create our callback function
def update_point(trace, points, selector):
    c = list(scatter.marker.color)
    s = list(scatter.marker.size)
    for i in points.point_inds:
        c[i] = '#bae2be'
        s[i] = 20
    with f.batch_update():
        scatter.marker.color = c
        scatter.marker.size = s

# Assign the callback function to the scatter object
scatter.on_click(update_point)
scatter.on_selection(update_point)

f # Do not use .show() or the figure will not be interactive

Selection behaviour#

  • The selection in one graph affects a second graph.

from plotly.subplots import make_subplots
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from plotly.graph_objs import FigureWidget

# Random data and counting of positive and negative x values
np.random.seed(1)
x = np.random.randn(100)
y = np.random.randn(100)
df = pd.DataFrame({'x': x, 'y': y})
allU = np.unique(df['x'].values>=0, return_counts=True)
# Switch the order of counts if most positive samples are selected
if allU[0][0]:
    allU[1][0], allU[1][1] = allU[1][1], allU[1][0]

# Create figure with subplots
fig = make_subplots(rows=1, cols=2, 
                    specs=[[{"type": "xy"}, {"type": "domain"}]])
fig.add_trace(
    go.Scatter(x=x, y=y, mode='markers'),
    row=1, col=1
)
fig.add_trace(
    go.Pie(labels=['negative x', 'positive x'], values = allU[1], 
           marker_colors=("#FF5050","#5050FF"), sort=False),
    row=1, col=2
)
fig.update_layout(height=500, width=800, title_text="Side By Side Subplots")

# Convert Figure to FigureWidget
fig_widget = FigureWidget(fig)

# Get scatter and pie from FigureWidget
scatter2 = fig_widget.data[0]
pie = fig_widget.data[1]

# Create our callback function
def selection_handler(trace, points, selector):
    df_selected = pd.DataFrame({'x': points.xs, 'y': points.ys})
    sel_log, sel_num = np.unique(df_selected['x'].values>=0, return_counts=True)
    # If only positive selections, add a zero count for negative x
    if sel_log.shape[0] == 1 and sel_log[0]:
        sel_log = np.concatenate([np.array([False]), sel_log])
        sel_num = np.concatenate([np.array([0]), sel_num])
    # Switch the order of counts if most positive samples are selected
    if sel_log[0]:
        sel_num[0], sel_num[1] = sel_num[1], sel_num[0]
    with fig_widget.batch_update():
        pie.values = sel_num
    fig_widget.update_traces()

scatter2.on_selection(selection_handler)

fig_widget # Careful here!

Adding widgets to a Plotly graph#

  • As with the above interactivity, accessing the object to minpulate is important.

  • Using the interactive function from ipywidgets in combination with a function that sets the properties is convenient.

import plotly.graph_objects as go
from ipywidgets import interactive
import numpy as np

np.random.seed(1)

x = np.random.rand(1000)
y = np.random.rand(1000)

# Main plot
f = go.FigureWidget([go.Scatter(x=x, y=y, mode='markers')])

# Attributes of the scatter object
scatter = f.data[0]
scatter.marker.size = 10

# Add ipywidget slider to control the marker size
def set_marker_size(size):
    scatter.marker.size = size

marker_slider = interactive(set_marker_size, size=(1, 20, 1))
marker_slider.children[0].layout.width = '400px'
display(f, marker_slider)