How to update figure in same window dynamically without opening and redrawing in new tab?

Viewed 1340

I am creating a 3D scatter plot based off a pandas dataframe, and then I want to re-draw it with slightly updated data whenever the user presses a button in my program. I almost have this functionality working, except the updated figure is drawn via a new opened tab, when really I just want my origin existing figure to be updated.

Here is my code. First I initialize the plot with 'version 1' of the data, then I set up a simple while loop to wait for the user to request an update. Then ideally once they enter input to ask for the update, I just re-draw everything in the same tab that is open. But instead a new tab is opened (which redraws the data correctly at least).

    fig = go.Figure(data=[go.Scatter3d(x=df['x'],y=df['y'],z=df['z'],mode='markers', marker=dict(
        size=4,
        color=df['y'],                # set color to an array/list of desired values
        colorscale='Viridis',   # choose a colorscale
        opacity=0.3
    ))])
    
    # Column max and mins for plotting:
    xmax = df_1.max(axis=0)['x']; xmin = df_1.min(axis=0)['x']
    ymax = df_1.max(axis=0)['y']; ymin = df_1.min(axis=0)['y']
    zmax = df_1.max(axis=0)['z']; zmin = df_1.min(axis=0)['z']
    
    fig.update_layout(
    scene = dict(xaxis = dict(nticks=4, range=[xmin,xmax],),
                     yaxis = dict(nticks=4, range=[ymin,ymax],),
                     zaxis = dict(nticks=4, range=[zmin,zmax],),))

    f2 = go.FigureWidget(fig)
    f2.show()
        
    #fig.show()
    
    while True:
        choice = input("> ")
        choice = choice.lower() #Convert input to "lowercase"

        if choice == 'exit':
            print("Good bye.")
            break

        if choice == 'w':
            print("W, moving forward")
            
            cube_origin = cube_origin + np.array([0.1,0,0])
            df_cube = createCubeMesh(cube_size, cube_density, cube_origin)
            new_df = df_scene_orig.copy()
            new_df = new_df.append(df_cube)
            
            fig = go.Figure(data=[go.Scatter3d(x=new_df['x'],y=new_df['y'],z=new_df['z'],mode='markers', marker=dict(
                size=4,
                color=new_df['y'],                # set color to an array/list of desired values
                colorscale='Viridis',   # choose a colorscale
                opacity=0.3
            ))])
            
            
            f2 = go.FigureWidget(fig)
            f2.show()

I based my code on another answer that said to use go.FigureWidget(fig), but it doesn't seem to work as intended.

Edit

Instead of me using f2.show() at the end, I just want a simple thing analogous to f2.update() that redraws.

3 Answers

This is the case you want.

Everywhere in this page that you see fig.show(), you can display the same figure in a Dash application by passing it to the figure argument of the Graph component from the built-in dash_core_components package like this:

import plotly.graph_objects as go

fig = go.Figure(
    data=[go.Scatter(
        mode="markers+text",
        x=[10, 20],
        y=[20, 25],
        text=["Point A", "Point B"]
    )],
    layout=dict(height=400, width=400, template="none")
)

import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash()
app.layout = html.Div([
    dcc.Graph(figure=fig)
])

app.run_server(debug=True, use_reloader=False)

reference: https://plotly.com/python/figure-introspection/

Help you write a code that is closest to your needs:

import plotly as py
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import pandas as pd
import numpy as np

py.offline.init_notebook_mode(connected=True)

app = JupyterDash('SimpleExample')
app.layout = html.Div([
    dcc.Dropdown(id='dropdown', options=[
        {'label': 'W', 'value': 'W'},
        {'label': 'exit', 'value': 'exit'}],
                 value='exit'),
    dcc.Graph(id='graph-court')

])


def random_data():
    # sample dataframe of a wide format
    np.random.seed(4)
    cols = list('xyz')
    X = np.random.randint(50, size=(3, len(cols)))
    df = pd.DataFrame(X, columns=cols)
    df.iloc[0] = 0
    return df


df = random_data()


def create_figure(df):
    fig = go.Figure(data=[go.Scatter3d(x=df['x'], y=df['y'], z=df['z'], mode='markers', marker=dict(
        size=10,
        color=df['y'],
        colorscale='Viridis',
        opacity=0.3
    ))])

    # Column max and mins for plotting:
    xmax = df.max(axis=0)['x']
    xmin = df.min(axis=0)['x']
    ymax = df.max(axis=0)['y']
    ymin = df.min(axis=0)['y']
    zmax = df.max(axis=0)['z']
    zmin = df.min(axis=0)['z']

    fig.update_layout(
        scene=dict(xaxis=dict(nticks=4, range=[xmin, xmax], ),
                   yaxis=dict(nticks=4, range=[ymin, ymax], ),
                   zaxis=dict(nticks=4, range=[zmin, zmax], ), ))
    fig = go.FigureWidget(fig)
    return fig


@app.callback(Output('graph-court', 'figure'),
              [Input('dropdown', 'value')])
def update_figure(selected_value):
    selected_value = selected_value.lower()  # Convert input to "lowercase"
    if selected_value == 'exit':
        print("Good bye.")
        new_x, new_y, new_z = [], [], []
    else:
        print("W, moving forward")
        # new data
        new_x, new_y, new_z = np.random.randint(10, size=(3, 1))

    # ploy
    fig = create_figure(df)  # Set as global variable or local variable as required
    fig.add_trace(go.Scatter3d(x=new_x, y=new_y, z=new_z, marker=dict(size=10, color='green'), mode='markers'))

    return fig


app.run_server(debug=False, use_reloader=False)

Estimated that your "tab" is referring to "browser tab" it is basically not possible with the standard renderer. With the renderer browser it serves a one-shot server on a random port, which is shutting down immediately after the rendering is done. You can check that by reloading the graph in browser.

You can:

  • generate a static image and serve that yourself in a webapp (e.g. with flask) with f2.write_image("test.svg")
  • generate a dynamic html content by f2.show(renderer = "iframe") and serve that with e.g. flask
  • simply use plotly dash, look here for impressions

Try using Plotly for plotting, it has a functionality (Visibility), using that you can update your plot on button click or drop down. The below example is for dropdown.


import pandas as pd
import numpy as np
import plotly.offline as py_offline
import plotly.graph_objs as go
from plotly import tools
py_offline.init_notebook_mode()

trace = go.Scatter(
    x=[1, 2, 3],
    y=[4, 5, 6]
)

fig = tools.make_subplots(rows=10, cols=1)

for k in range(10):
    fig.append_trace(trace, k+1, 1)

updatemenus=list([
    dict(
        buttons=[],
        direction = 'down',
        pad = {'r': 10, 't': 10},
        showactive = True,
        x = 0,
        xanchor = 'left',
        y = 1.2,
        yanchor = 'top' 
    ),
])

lister = []
for k in range(11):
    lister.append(dict(
        args=['visible', [True for k in range(10)] if k == 0 else [True if (i+1) == k else False for i in range(10)]],
        label='Show Trace ' + str( 'All' if k == 0 else k),
        method='restyle'
    ))

updatemenus[0]['buttons'] = lister

fig['layout']['updatemenus'] = updatemenus


fig['layout'].update(title='subplots')
py_offline.iplot(fig, filename='simple-subplot')

Related