Plot (Plotly)

#| standalone: true
#| components: [viewer]
#| viewerHeight: 500

import plotly.express as px
from palmerpenguins import load_penguins
from shiny import App, ui
from shinywidgets import output_widget, render_widget  

penguins = load_penguins()

app_ui = ui.page_fluid(
    ui.input_slider("n", "Number of bins", 1, 100, 20),
    output_widget("plot"),  
)

def server(input, output, session):
    @render_widget  
    def plot():  
        scatterplot = px.histogram(
            data_frame=penguins,
            x="body_mass_g",
            nbins=input.n(),
        ).update_layout(
            title={"text": "Penguin Mass", "x": 0.5},
            yaxis_title="Count",
            xaxis_title="Body Mass (g)",
        )

        return scatterplot  

app = App(app_ui, server)
import plotly.express as px
from palmerpenguins import load_penguins
from shiny import App, ui
from shinywidgets import output_widget, render_widget  

penguins = load_penguins()

app_ui = ui.page_fluid(
    ui.input_slider("n", "Number of bins", 1, 100, 20),
    output_widget("plot"),  
)

def server(input, output, session):
    @render_widget  
    def plot():  
        scatterplot = px.histogram(
            data_frame=penguins,
            x="body_mass_g",
            nbins=input.n(),
        ).update_layout(
            title={"text": "Penguin Mass", "x": 0.5},
            yaxis_title="Count",
            xaxis_title="Body Mass (g)",
        )

        return scatterplot  

app = App(app_ui, server)
import plotly.express as px
from palmerpenguins import load_penguins
from shiny.express import input, ui
from shinywidgets import render_widget  

penguins = load_penguins()

ui.input_slider("n", "Number of bins", 1, 100, 20)

@render_widget  
def plot():  
    scatterplot = px.histogram(
        data_frame=penguins,
        x="body_mass_g",
        nbins=input.n(),
    ).update_layout(
        title={"text": "Penguin Mass", "x": 0.5},
        yaxis_title="Count",
        xaxis_title="Body Mass (g)",
    )

    return scatterplot  
No matching items

Relevant Functions

No matching items

Details

Plotly is an interactive graphics plotting library.

To make an interactive plot with Plotly in Shiny for Python, we will need to use the shinywidgets library to connect Shiny with ipywidgets.

To make a Plotly figure, we need to do the following steps:

  1. Import the output_widget() and render_widget() functions from the shinywidgets library, from shinywidgets import output_widget, render_widget

  2. Call output_widget() to the UI of your app to create a div in which to display the figure. Where you call this function will determine where the figure will appear within the layout of the app. The id parameter you provide will be used to link to other parts of the Shiny app.

  3. Define a function within the server() function that creates the figure.

    • The name of the function should be the same value you passed into the id parameter in your output_widget() function call in the UI.

    • If your function calls reactive values, Shiny will update your figure whenever those values change, in a reactive fashion.

  4. Decorate your plotting function with a @render_widget() decorator.

    • If your plotting function is not the same as the id you used in the ui.output_widget(), you can add an additional @output(id=...) decorator.
    • If you use the @output() decorator, make sure it is above the @render_widget() decorator.

Visit shiny.posit.co/py/docs/ipywidgets.html to learn more about using ipywidgets with Shiny.

Plots as Inputs

You can use a Plotly figure as an input widget, collecting the locations of user clicks, hovers, and selections.

  1. Convert your Plotly figure to a FigureWidget using plotly.graph_objects.FigureWidget(), which extends the functionality of a standard Plotly figure and enables event handling.

  2. Use the .data attribute of the FigureWidget to access its traces. The data attribute is a list that contains all the traces in the figure. Individual traces are accessible as .data[0], .data[1], etc., depending on how many traces are present in the figure.

  3. Use event handlers to listen for user interactions with the plot. These handlers include methods like .on_click(), .on_hover(), and .on_selection(), which are available for individual traces within the figure. You attach these handlers to a specific trace (e.g., .data[0].on_click()) to capture interactions with the data points in that trace.

  4. When you use an event handler like .on_click(), you need to pass it a callback function that defines what should happen when the event occurs. When defining the callback function, it should receive the parameters trace, points, and state, which provide information about the data points interacted with. In our example app below, our callback function updates a reactive value to contain the information about the points clicked, hovered over, or selected.

Variations

Plot as input

First, convert your Plotly figure to a FigureWidget using plotly.graph_objects.FigureWidget(). Then, you can use .on_click(), .on_hover(), .on_selection(), and other methods to control what happens when the user clicks, hover, or selects points. Capture the click, hover, and selection information as reactive variables. The app below displays the values returned, but you can also call the values from within your computations to filter tables, perform calculations, and so on.

#| standalone: true
#| components: [viewer]
#| viewerHeight: 720

import plotly.express as px
import plotly.graph_objects as go
from plotly.callbacks import Points
import plotly.express as px
from palmerpenguins import load_penguins
from shiny import App, ui, render, reactive
from shinywidgets import output_widget, render_widget  

penguins = load_penguins()

app_ui = ui.page_fluid(
    output_widget("plot"),  
    "Click info",
    ui.output_text_verbatim("click_info", placeholder=True),
    "Hover info",
    ui.output_text_verbatim("hover_info", placeholder=True),   
    "Selection info (use box or lasso select)",
    ui.output_text_verbatim("selection_info", placeholder=True)
)

def server(input, output, session):

    click_reactive = reactive.value() 
    hover_reactive = reactive.value() 
    selection_reactive = reactive.value() 
    
    @render_widget  
    def plot():  
        fig = px.scatter(
            data_frame=penguins, x="body_mass_g", y="bill_length_mm"
        ).update_layout(
            yaxis_title="Bill Length (mm)",
            xaxis_title="Body Mass (g)",
        )
        w = go.FigureWidget(fig.data, fig.layout) 
        w.data[0].on_click(on_point_click) 
        w.data[0].on_hover(on_point_hover) 
        w.data[0].on_selection(on_point_selection) 
        return w 

    
    def on_point_click(trace, points, state): 
        click_reactive.set(points) 

    def on_point_hover(trace, points, state): 
        hover_reactive.set(points) 

    def on_point_selection(trace, points, state): 
        selection_reactive.set(points) 

    @render.text
    def click_info():
        return click_reactive.get()

    @render.text
    def hover_info():
        return hover_reactive.get()

    @render.text
    def selection_info():
        return selection_reactive.get()

app = App(app_ui, server)
import plotly.express as px
import plotly.graph_objects as go
from palmerpenguins import load_penguins
from plotly.callbacks import Points
from shiny import reactive
from shiny.express import input, render, ui
from shiny.ui import output_code, output_plot
from shinywidgets import render_plotly

penguins = load_penguins()

@render_plotly  
def plot():  
    fig = px.scatter(
        data_frame=penguins, x="body_mass_g", y="bill_length_mm"
    ).update_layout(
        yaxis_title="Bill Length (mm)",
        xaxis_title="Body Mass (g)",
    )
    # Need to create a FigureWidget() for on_click to work
    w = go.FigureWidget(fig.data, fig.layout) 
    w.data[0].on_click(on_point_click) 
    w.data[0].on_hover(on_point_hover) 
    w.data[0].on_selection(on_point_selection) 
    return w 

# Capture the clicked point in a reactive value
click_reactive = reactive.value() 
hover_reactive = reactive.value() 
selection_reactive = reactive.value() 

def on_point_click(trace, points, state): 
    click_reactive.set(points) 

def on_point_hover(trace, points, state): 
    hover_reactive.set(points) 

def on_point_selection(trace, points, state): 
    selection_reactive.set(points) 

"Click info"
@render.code
def click_info():
    return str(click_reactive.get())

"Hover info"
@render.code
def hover_info():
    return str(hover_reactive.get())

"Selection info (use box or lasso select)"
@render.code
def selection_info():
    return str(selection_reactive.get())
import plotly.express as px
import plotly.graph_objects as go
from plotly.callbacks import Points
import plotly.express as px
from palmerpenguins import load_penguins
from shiny import App, ui, render, reactive
from shinywidgets import output_widget, render_widget  

penguins = load_penguins()

app_ui = ui.page_fluid(
    output_widget("plot"),  
    "Click info",
    ui.output_text_verbatim("click_info", placeholder=True),
    "Hover info",
    ui.output_text_verbatim("hover_info", placeholder=True),   
    "Selection info (use box or lasso select)",
    ui.output_text_verbatim("selection_info", placeholder=True)
)

def server(input, output, session):

    click_reactive = reactive.value() 
    hover_reactive = reactive.value() 
    selection_reactive = reactive.value() 
    
    @render_widget  
    def plot():  
        fig = px.scatter(
            data_frame=penguins, x="body_mass_g", y="bill_length_mm"
        ).update_layout(
            yaxis_title="Bill Length (mm)",
            xaxis_title="Body Mass (g)",
        )
        w = go.FigureWidget(fig.data, fig.layout) 
        w.data[0].on_click(on_point_click) 
        w.data[0].on_hover(on_point_hover) 
        w.data[0].on_selection(on_point_selection) 
        return w 

    
    def on_point_click(trace, points, state): 
        click_reactive.set(points) 

    def on_point_hover(trace, points, state): 
        hover_reactive.set(points) 

    def on_point_selection(trace, points, state): 
        selection_reactive.set(points) 

    @render.text
    def click_info():
        return click_reactive.get()

    @render.text
    def hover_info():
        return hover_reactive.get()

    @render.text
    def selection_info():
        return selection_reactive.get()

app = App(app_ui, server)
No matching items