5

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.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
JDS
  • 16,388
  • 47
  • 161
  • 224
  • Note that we prefer a technical style of writing here. We gently discourage greetings, hope-you-can-helps, thanks, advance thanks, notes of appreciation, regards, kind regards, signatures, please-can-you-helps, chatty material and abbreviated txtspk, pleading, how long you've been stuck, voting advice, meta commentary, etc. Just explain your problem, and show what you've tried, what you expected, and what actually happened. – halfer Jan 09 '22 at 14:40
  • 1
    Is this helpful? Maybe you might need to use JS, Flask will only deliver the page. https://stackoverflow.com/questions/8470431/what-is-the-best-way-to-implement-a-forced-page-refresh-using-flask – pedro_bb7 Jan 10 '22 at 10:16

3 Answers3

2

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)
lazy
  • 744
  • 3
  • 13
0

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
araisch
  • 1,727
  • 4
  • 15
  • Hey thanks for your answer. I'm open to using flask/iframe stuff, would you be able to provide a simple example I can adapt? Like you can literally have any 3d scatter plot, change the data after sleeping in a loop or whatever, and update the graph in real time, and that would be great for my purposes. – JDS Jan 10 '22 at 18:09
  • 1
    I have no example for that and honestly time's not worth to work on one. Dash is the best choice. It is plotly with webmethods and a real server. Check Callbacks for letting the user change the diagram from browser: https://dash.plotly.com/basic-callbacks – araisch Jan 11 '22 at 07:39
0

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')

Subhamp7
  • 29
  • 5