Search code examples
pythonplotlyjupyteripywidgets

how can I export this interactive plot to view in a browser without jupyter?


I have this interactive plot in python:

import ipywidgets as widgets
import plotly.graph_objects as go 
from numpy import linspace


def leaf_plot(sense, spec):
    fig = go.Figure()

    x = linspace(0,1,101)
    x[0] += 1e-16
    x[-1] -= 1e-16

    positive =  sense*x/(sense*x + (1-spec)*(1-x)) 
                                                    #probability a person is infected, given a positive test result, 
                                                    #P(p|pr) = P(pr|p)*P(p)/P(pr)
                                                    #        = P(pr|p)*P(p)/(P(pr|p)*P(p) + P(pr|n)*P(n))
                                                    #        =   sense*P(p)/(  sense*P(p) +(1-spec)*P(n))
    negative =  1-spec*(1-x)/((1-sense)*x + spec*(1-x))

    fig.add_trace(
        go.Scatter(x=x, y  = positive, name="Positive",marker=dict( color='red'))
    )

    fig.add_trace(
        go.Scatter(x=x, y  = negative, 
                   name="Negative", 
                   mode = 'lines+markers',
                   marker=dict( color='green'))
    )
 
    fig.update_xaxes(title_text = "Base Rate")
    fig.update_yaxes(title_text = "Post-test Probability")
    fig.show()

sense_ = widgets.FloatSlider(
    value=0.5,
    min=0,
    max=1.0,
    step=0.01,
    description='Sensitivity:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
)

spec_ = widgets.FloatSlider(
    value=0.5,
    min=0,
    max=1.0,
    step=0.01,
    description='Specificity:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
)
ui = widgets.VBox([sense_, spec_])

out = widgets.interactive_output(leaf_plot, {'sense': sense_, 'spec': spec_})

display(ui, out)

How can I export this so that it can be viewed as a standalone web page in a browser, say as HTML, while retaining the interactivity, as e.g. in https://gabgoh.github.io/COVID/index.html ?

Using plotly's fig.write_html() option I get a standalone web page, but this way I lose the sliders.

With some modification, plotly allows at most for a single slider (the ipywidgets are not included in the plotly figure object).

Plus, in plotly, the said slider basically controls the visibility of pre-calculated traces (see e.g. https://plotly.com/python/sliders/), which restricts the interactivity (sometimes the parameter space is huge).

What's the best way to go?

(I don't necessarily need to stick with plotly/ipywidgets)


Solution

  • you need to rework things a bit, but you can achieve what you want with dash and Heroku.

    first you need to modify leaf_plot() to return a figure object.

    from numpy import linspace
    
    
    def leaf_plot(sense, spec):
        fig = go.Figure()
    
        x = linspace(0,1,101)
        x[0] += 1e-16
        x[-1] -= 1e-16
    
        positive = sense*x/(sense*x + (1-spec)*(1-x)) 
        negative = 1-spec*(1-x)/((1-sense)*x + spec*(1-x))
    
        fig.add_trace(
            go.Scatter(x=x, y  = positive, name="Positive",marker=dict( color='red'))
        )
    
        fig.add_trace(
            go.Scatter(x=x, y  = negative, 
                       name="Negative", 
                       mode = 'lines+markers',
                       marker=dict( color='green'))
        )
        
        fig.update_layout(
        xaxis_title="Base rate",
        yaxis_title="After-test probability",
        )
    
        return fig
    

    Then write the dash app:

    from jupyter_dash import JupyterDash
    import dash_core_components as dcc
    import dash_html_components as html
    from dash.dependencies import Input, Output
    
    
    # Build App
    app = JupyterDash(__name__)
    app.layout = html.Div([
        html.H1("Interpreting Test Results"),
        dcc.Graph(id='graph'),
        html.Label([
            "sensitivity",
            dcc.Slider(
                id='sensitivity-slider',
                min=0,
                max=1,
                step=0.01,
                value=0.5,
                marks = {i: '{:5.2f}'.format(i) for i in linspace(1e-16,1-1e-16,11)}
            ),
        ]),
        html.Label([
            "specificity",
            dcc.Slider(
                id='specificity-slider',
                min=0,
                max=1,
                step=0.01,
                value=0.5,
                marks = {i: '{:5.2f}'.format(i) for i in linspace(1e-16,1-1e-16,11)}
            ),
        ]),
    ])
    
    # Define callback to update graph
    @app.callback(
        Output('graph', 'figure'),
        Input("sensitivity-slider", "value"),
        Input("specificity-slider", "value")
    )
    def update_figure(sense, spec):
        return leaf_plot(sense, spec)
    
    # Run app and display result inline in the notebook
    app.run_server()
    

    If you execute this in a jupyter notebook, you will only be able to access your app locally.

    If you want to publish, you can try Heroku