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

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.


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