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