plotly.scatter figure doesnt reset when updating with callback (python dash) - python

I have the following problem:
I try to plot a plotly.scatter plot combined with dash which should be changeable with sliders using callback.
I searched through the documentation of dash and plotly and found some examples for basic and advanced callbacks and also for Sliders
https://dash.plotly.com/basic-callbacks
https://dash.plotly.com/advanced-callbacks
https://dash.plotly.com/dash-core-components/slider
The Sliders are working and the shown data is also changing but the old plot doesnt vanish even if i reload the page.
I dont know if the problem is in the html component or in the callback.
from math import cos,sin,radians
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
from dash import Dash, dcc, html, Input, Output, callback
import pandas as pd
I skip the calculation which is not called in the callback because im quite sure the problem isnt there but if you want i can provide it aswell.
def s_out(XTvar, YTvar, ZTvar, XCvar, YCvar, ZCvar):
for a in np.linspace(0,360,3600):
s1i=cos(radians(a))
s2i=sin(radians(a))
fe=fE_tsaiwu((s1i,s2i,0,0,0,0),getm(XTvar, YTvar, ZTvar, XCvar, YCvar,ZCvar))
s1.append(s1i/fe)
s2.append(s2i/fe)
return s1, s2
s1, s2 = s_out(XTvar, YTvar, ZTvar, XCvar, YCvar, ZCvar)
Html component with callback:
(Initialising the figure in the Div is not even necessary i figured out)
app = Dash(__name__)
app.layout = html.Div([
dcc.Graph(
id='Tsai',
# figure=fig
figure = {}
),
html.H3(children='XTvar'),
html.H5(children='Der Wert des Sliders wird hier direkt unter dem momentanen Punkt angezeigt'),
dcc.Slider(
id= "XTvar",
min = 1,
max = 1500,
value = 1000,
tooltip={"placement": "bottom", "always_visible": True}
),
html.H3(children='YTvar'),
dcc.Slider(
id= "YTvar",
min = 1,
max = 50,
value = 40,
),
html.H3(children='ZTvar'),
dcc.Slider(
id= "ZTvar",
min = 1,
max = 50,
value = 40,
),
html.H3(children='XCvar'),
dcc.Slider(
id= "XCvar",
min = 500,
max = 1000,
value = 700,
),
html.H3(children='YCvar'),
dcc.Slider(
id= "YCvar",
min = 100,
max = 150,
value = 120,
),
html.H3(children='ZCvar'),
dcc.Slider(
id= "ZCvar",
min = 100,
max = 150,
value = 120,
)
])
#app.callback(
Output(component_id='Tsai', component_property='figure'),
Input(component_id='XTvar', component_property='value'),
Input(component_id='YTvar', component_property='value'),
Input(component_id='ZTvar', component_property='value'),
Input(component_id='XCvar', component_property='value'),
Input(component_id='YCvar', component_property='value'),
Input(component_id='ZCvar', component_property='value')
)
def updatefigure(XTvar, YTvar, ZTvar, XCvar, YCvar, ZCvar):
df = zip(s_out(XTvar, YTvar, ZTvar, XCvar, YCvar, ZCvar))
fig = px.scatter(df,s1, s2)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
Here you can see what is happening if i move one of the sliders.
I appreciate every kind of help and i hope the problem is well described.

Your logic in your dash callback is incomplete.
If you take a look at s1, s2 after you move your slider, you can find their length is increasing by 3600 every time you move your slider, since you never clear s1, s2 after each call to the callback.
Therefore everytime you move your slider, you are drawing a new eclipse by appending new values to s1, s2!
Here are some advises to your code, to avoid this behaviour.
Avoid defining global variables like s1, s2 = s_out(XTvar, YTvar, ZTvar, XCvar, YCvar, ZCvar) in your code. Global variables is a cache in your Dash app, and its value persist until you kill your debugging session. We have little control to them in Dash. See Dash Documentation for details.
If you need to cache some values between callbacks, use dcc.Store
Use Dash built-in debugger to see what is inside the callbacks. It could be useful to trace more complex callbacks.

Related

Plotly: is there a way to save the data of a clicked point in a list?

I have a 2D plotly graph with a hover feature. When you hover over each point, the label (e.g. 'image 2, cluster 1') associated with that point appears. I'd like for label to be appended onto an existing list if I were to click on the point (rather than just hover over it). The reason why is that I'd later like to use the data of this point to perform another task. Is there an example online that demonstrates how to do this-- have looked through the documentation but haven't found something for this yet. Thanks!
The hoverData that is available to you by default, with some sample data, is this:
{
"points": [
{
"curveNumber": 1,
"pointNumber": 7,
"pointIndex": 7,
"x": 1987,
"y": 74.32,
"bbox": {
"x0": 420.25,
"x1": 426.25,
"y0": 256,
"y1": 262
}
}
]
}
I'm not quite sure what you mean by 'label', so I can only assume that it would be the name of a trace or something similar, like in this example from the Plotly docs:
But as you can see, that's not readily available in the hoverData dict. This means that you'll have to use this information to reference your figure structure as well, so that you end up with something like this:
[['New Zealand', 2002, 79.11]]
And that's not a problem as long as you're willing to use Plotly Dash. I've made a complete setup for you that should meet your requirements. In the app in the image below you'll find a figure along with two output fields for strings. The first field shows the info from that last point you've clicked in the figure. On every click, a new element is added to a list named store. The last fields shows the complete information from the same click.
The answer to your question is, yes, there is a way to save the data of a clicked point in a list. And one way to do so is through the following callback that uses clickdata to reference your figure object, store those references in a list, and append new elements every time you click a new element.
App
Complete code:
import json
from textwrap import dedent as d
import pandas as pd
import plotly.graph_objects as go
import numpy as np
import dash
from dash import dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
from jupyter_dash import JupyterDash
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# app info
app = JupyterDash(__name__)
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
# data
df = px.data.gapminder().query("continent=='Oceania'")
# plotly figure
fig = px.line(df, x="year", y="lifeExp", color="country", title="No label selected")
fig.update_traces(mode="markers+lines")
app.layout = html.Div([
dcc.Graph(
id='figure1',
figure=fig,
),
html.Div(className
='row', children=[
html.Div([
dcc.Markdown(d("""Hoverdata using figure references""")),
html.Pre(id='hoverdata2', style=styles['pre']),
], className='three columns'),
html.Div([
dcc.Markdown(d("""
Full hoverdata
""")),
html.Pre(id='hoverdata1', style=styles['pre']),
], className='three columns')
]),
])
# container for clicked points in callbacks
store = []
#app.callback(
Output('figure1', 'figure'),
Output('hoverdata1', 'children'),
Output('hoverdata2', 'children'),
[Input('figure1', 'clickData')])
def display_hover_data(hoverData):
if hoverData is not None:
traceref = hoverData['points'][0]['curveNumber']
pointref = hoverData['points'][0]['pointNumber']
store.append([fig.data[traceref]['name'],
fig.data[traceref]['x'][pointref],
fig.data[traceref]['y'][pointref]])
fig.update_layout(title = 'Last label was ' + fig.data[traceref]['name'])
return fig, json.dumps(hoverData, indent=2), str(store)
else:
return fig, 'None selected', 'None selected'
app.run_server(mode='external', port = 7077, dev_tools_ui=True,
dev_tools_hot_reload =True, threaded=True)
You need to use callbacks to perform this type of action (register on_click()). Have defined clicked as a list of clicked points. Demonstrated how this can be achieved with ipwidgets or dash
ipwidgets
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import ipywidgets as widgets
from pathlib import Path
import json
x = np.random.uniform(-10, 10, size=50)
y = np.sin(x)
clicked = []
# construct figure that has holders for points, interpolated line and final lines
fig = go.FigureWidget(
[
go.Scatter(x=x, y=y, mode="markers", name="base_points"),
]
)
fig.update_layout(template="simple_white")
out = widgets.Output(layout={"border": "1px solid black"})
out.append_stdout("Output appended with append_stdout\n")
# create our callback function
#out.capture()
def base_click(trace, points, selector):
global clicked
clicked.append(points.__dict__)
fig.data[0].on_click(base_click)
widgets.HBox([fig, out])
dash
from jupyter_dash import JupyterDash
import dash
from dash.dependencies import Input, Output, State
import numpy as np
import json
clicked = []
# Build App
app = JupyterDash(__name__)
app.layout = dash.html.Div(
[
dash.dcc.Graph(
id="fig",
figure=go.Figure(go.Scatter(x=x, y=y, mode="markers", name="base_points")),
),
dash.html.Div(id="debug"),
]
)
#app.callback(
Output("debug", "children"),
Input("fig", "clickData"),
)
def point_clicked(clickData):
global clicked
clicked.append(clickData)
return json.dumps(clickData)
# Run app and display result inline in the notebook
app.run_server(mode="inline")

Dash callback preventing other callbacks

I am relatively new to Dash, and thought I understood the callbacks pretty well. However, I am now in a situation where it seems I need to have all callbacks within one callback, as my program works fine when the one is called.
When I have multiple callbacks, they work fine individually; meaning that if I comment one out the callback works as desired. I am able to get the correct functionality using context.triggered, but I do not want to have everything in one massive callback, and I feel like this is an issue with my understanding. I have tried limiting to one input on the callback, but that still does not work. Am I passing the whole app layout to the callback?
If I am, how do I limit what is being passed beyond the ids? How
can callbacks?
An adapted working example is below:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import dash_table
from dash.dependencies import Input, Output, State
import plotly.express as px
from dash import callback_context, no_update
import webbrowser
from flask import request
chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/Mining-BTC-180.csv").drop('Unnamed: 0',axis=1)
selected_cols = ['Number-transactions', 'Output-volume(BTC)',
'Market-price', 'Hash-rate', 'Cost-per-trans-USD', 'Mining-revenue-USD',
'Transaction-fees-BTC']
class Parameter_Viewer():
def __init__(self, df, **kwargs):
revenue_plot = px.scatter(df, x="Date", y="Mining-revenue-USD",title='Mining-revenue-USD')
Hash_plot = px.scatter(df, x="Date", y="Hash-rate",title='Hash-rate')
parameter_table = dash_table.DataTable(
id='db-table',
columns=[{"name": i, "id": i} for i in selected_cols],
data=df.to_dict('records'),
#page_action="native",
page_action='none',
style_table={'height': '300px', 'overflowY': 'auto'},
editable=True
)
app = dash.Dash()
app.layout = html.Div(
children=[
#first row
html.Div(
children=[
html.Div(children=[
html.H1("Parameter", className="menu-title"),
dcc.Dropdown(
id="parameter-filter",
options=[
{"label": parameter, "value": parameter} for parameter in df.columns
],
value="Mining-revenue-USD",
clearable=False,
className="dropdown",
),]),
html.Div(children=[
html.H1("Data Type", className="menu-title"),
dcc.Dropdown(
id="data-selector",
options=[
{"label": data_col, "value": data_col} for data_col in selected_cols
],
value="Hash-rate",
clearable=False,
className="dropdown",
),]),
html.Button("Close Viewer", id="Close_Btn")
]
),
html.Div(children=[
html.H1(children="Database Analytics",),
parameter_table]),
html.Div(children=[
html.Div(
dcc.Graph(id='param-plot',figure=revenue_plot),
style={"width":'50%', "margin": 0, 'display': 'inline-block'},className="six columns"),
html.Div(
dcc.Graph(id='param-plot2',figure=Hash_plot),
style={"width":'50%', "margin": 0, 'display': 'inline-block'},className="six columns")],className="row")
])
#app.callback(
[Output("db-table", "data"), Output("param-plot", "figure"), Output("param-plot2", "figure")],
[Input("parameter-filter", "value"),Input("data-selector", "value"),Input('db-table', 'data')])#,prevent_initial_call=True)
def update_charts(parameter,data_type,table_data):
changed_inputs = [x["prop_id"]for x in callback_context.triggered]
if changed_inputs[0] == 'parameter-filter.value':
df = pd.DataFrame.from_dict(table_data)
ua_plot = px.scatter(df, x="Date", y=data_type)
aa_plot = px.scatter(df, x="Date", y=parameter)
return table_data, ua_plot, aa_plot
elif changed_inputs[0] == 'db-table.data':
df = pd.DataFrame.from_dict(table_data)
ua_plot = px.scatter(df, x="Date", y=data_type)
aa_plot = px.scatter(df, x="Date", y=parameter)
return no_update, ua_plot, aa_plot
else:
return no_update,no_update,no_update
#app.callback(Output("db-table", "data"),Input("Close_Btn", "n_clicks"),prevent_initial_call=True)
def close_browser(n_clicks):
print('In close callback\n')
if n_clicks>0:
self.shutdown()
return no_update
host='127.0.0.1'
port='8050'
url = 'http://{}:{}/'.format(host,port)
webbrowser.get(chrome_path).open(url)
app.run_server(debug=False)
def shutdown(self):
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('Not running with the Werkzeug Server')
func()
Parameter_Viewer(df)```
It's easier to consider base cases of what your callbacks may look like and then extend upon them. From my experience, the main reason I typically structure a callback is to handle a particular output caused by a particular input.
E.g.
If output O1 is an effect of input I1, then I make a callback C1(I1 → O1).
If output O1 is an effect of Inputs I1 and I2, then I make a callback C1([I1, I2] → O1)
If you happen to have multiple outputs that are effects of an input, then you combine those outputs in the same callback.
E.g.
If output O1 is an effect of input I1 and output O2 is an effect of input I1, then I make a callback C1(I1 → [O1, O2])
Also, keep in mind that Dash doesn't allow multiple callbacks to have the same output component (e.g., a particular component property can be associated with one and only one callback). In which case, if you have a particular output that requires to be updated simultaneously with another, but is not necessarily an effect of that same input, then you should still combine the outputs into the single callback.
E.g.
If output O1 is an effect of input I1 and output O2 must be updated with O1, then I make a callback C1(I1 → [O1, O2])
I'm not sure why in the comment you tried using State() instead of Input(), as State() is used in the event a property must still be read as input, but doesn't necessarily have to be triggered.
So using the above logic, e.g.,
if output O1 that is an effect of input I1 and is not an effect of Input (which we now call State) S1, but still requires S1, then I make a callback C1([I1, S1] → [O1])
It's normal to have large callbacks that have many inputs and outputs as long as the callback is not trying to do too many disjointed things at once (otherwise when you go back to look at your code you'll spend a lot of time trying to remember what you did believe me :)).
If you're a beginner like me. You're probably looking for the most valuable information, in my case, in Daniel Al Mouiee's answer:
Dash doesn't allow multiple callbacks to have the same output component

Dashboard with DASH - Argument issues dcc.graph()

I'm trying to create a dashboard in Pycharm using dash. Here is the error I keep receiving,
html.Div(dcc.Graph(id='line-plot')),
TypeError: Graph() takes no arguments
And below is a snippet of my code where the error is being found (bottom of code). This code ran fine and I was about to populate the dashboard without receiving any errors inside IBM's python environment. I'm assuming I have to tweak something
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
Here is the rest of the code,
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
# Run the app
if __name__ == '__main__':
app.run_server()
Its safe to say I need an adult.
have added import and simulation of data frame
all other code runs without issue on plotly 5.1.0
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import numpy as np
app = JupyterDash(__name__)
# simulate data...
dr = pd.date_range("1-jan-2010", freq="W", periods=200)
airline_data = pd.DataFrame({"Year":dr.year, "Month":dr.month, "ArrDelay":np.random.uniform(2,5,len(dr))})
# TASK 3 - UPDATE LAYOUT COMPONENETS
# html.H1 tag for title , style, and overall font size
# html.Div & dcc.Input() tag to set inputs of the dashboard
# Update output componenent 2nd html.Div to layout the graph dcc.Graph()
app.layout = html.Div(children=[html.H1('Airline Performance Dashboard',
style={'textAlign': 'center', 'color': '#503D36',
'font-size': 40}),
html.Div(["Input Year: ", dcc.Input(id='input-year', value='2010',
type='number',
style={'height': '50px', 'font-size': 35}), ],
style={'font-size': 40}),
html.Br(),
html.Br(),
html.Div(dcc.Graph(id='line-plot')),
])
# TASK 4 - ADD APPLICATION CALL BACK FUNCTION and outputs / inputs
# add callback decorator
#app.callback(Output(component_id='line-plot', component_property='figure'),
Input(component_id='input-year', component_property='value'))
# Add computation to callback function and return graph
def get_graph(entered_year):
# Select 2019 data
df = airline_data[airline_data['Year'] == int(entered_year)]
# Group the data by Month and compute average over arrival delay time.
line_data = df.groupby('Month')['ArrDelay'].mean().reset_index()
# TASK 5 - UPDATE CALL BACK FUNCTION go.Figure(data=) and update fig.update_layout()
fig = go.Figure(
data=go.Scatter(x=line_data['Month'], y=line_data['ArrDelay'], mode='lines', marker=dict(color='green')))
fig.update_layout(title='Month vs Average Flight Delay Time', xaxis_title='Month', yaxis_title='ArrDelay')
return fig
app.run_server(mode="inline")

Plotly table in a Dash app flashes up a empty chart axis before loading fully

I am developing a Dash app which will display a large table of data. However, because the table is so large, it takes a while to load the first time the app is opened on a given run; while it is loading, an empty chart axis is shown instead.
While loading, it looks like this:
This seems very jarring since the view changes entirely once the table itself loads. Once it's thought for a moment, this is what's shown instead:
Is there some way to stop the temporary chart appearing? Maybe do something like replace it with a temporary "We'll be with you in a moment!" kind of message?
If it's helpful, this is the code I used to produce the above screenshots:
import pandas as pd
import plotly.graph_objects as go
import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
d = {
'A scores': [100, 90, 80, 90],
'B scores': [95, 85, 75, 95],
'C scores': [75, 60, 65, 70]
}
df = pd.DataFrame(data=d)
def display_table(min_a_score=0):
filtered_data = df[df['A scores'] >= min_a_score]
fig = go.Figure(
go.Table(
header=dict(
values=filtered_data.columns,
align="left"
),
cells=dict(
values=[filtered_data[x].tolist() for x in filtered_data],
align='left'
)
)
)
return fig
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Slider(
id='score-slider',
min=10,
max=100,
step=10,
value=0,
marks=dict(zip(range(0, 110, 10), [f"{x} marks" for x in range(0, 110, 10)])),
),
dcc.Graph(id="scores-table")
])
#app.callback(
Output("scores-table", "figure"),
[Input("score-slider", "value")]
)
def display_filtered_table(min_a_score):
fig = display_table(min_a_score)
return fig
if __name__ == '__main__':
app.run_server(debug=True)
On recommendation of a friend, I added a dcc.Loading() component wrapping the dcc.Graph() call.
That is, I replaced the line
dcc.Graph(id="scores-table")
in the app.layout bit with
dcc.Loading(
id="loading-1",
children=[dcc.Graph(id="scores-table")],
type="circle"
)
This means that instead of showing the empty axes, it shows a rotating circle suggesting progress is happening:
There may be a way to control the display of the initial graph, but if you set an empty graph and designate it as the initial graph, unnecessary graphs will not be displayed.
fig = go.Figure(data=[go.Table(
header=dict(values=['A Scores', 'B Scores']),
cells=dict(values=[[],[]])
)])
app.layout = html.Div([...
dcc.Graph(id="scores-table", figure=fig)
])

Dash(Python) - can't update Slider's values based on input from another Slider

I've started learning Dash(after using R-Shiny for ~3months). During which I've tried to make an app, which will have two Sliders, one of whose values(min, max, step) are fixed and values of the other Slider will be updated based on the input from first Slider. And I couldn't update values of second Slider.
Here's what I've done and tried:
from dash import Dash
import dash_html_components as dash_html
import dash_core_components as dash_core
from dash.dependencies import Input, Output
from flask import Flask
# initiate the dash app with Flask server
app = Dash(__name__, server=Flask(__name__))
# code the ui now
app.layout = dash_html.Div(children=[
dash_core.Slider(id="first_slider",
min=10, max=110, value=10, step=10,
marks={i : '{}'.format(i) for i in range(10,110,10)},
# marks={i : 'Level {}'.format(i) for i in range(10,110,10)}
),
dash_html.Hr(), dash_html.Hr(),
# second slider
dash_core.Slider(id="second_slider"),
# # dash_html.Hr(),
# print values from both sliders
dash_html.Div(id="display_selected_values"),
])
# take the input from first and update second slider
#app.callback(Output(component_id='second_slider', component_property=['min','max']),
# component_property='children'),
# component_property='value'),
[Input(component_id='first_slider', component_property='value')])
def print_the_value_from_slider(value_from_slider):
# update the values of second slider. ex: if input is 10, second slider will have 11 to 20
# return value_from_slider
return list((value_from_slider+1, value_from_slider+1+10))
# #app.callback(Output(component_id="second_slider", component_property='options'),
# [Input(component_id='second_slider', component_property='value')])
# def fill_second_slider(value_from_first_slider):
# return range(value_from_first_slider+1, value_from_first_slider+1+10, 1)
# #app.callback(Output('display_selected_values', 'children'),
# [Input('first_slider', 'value'),
# Input('second_slider', 'value')])
# def set_display_children(first_input, second_input):
# return '{} from {}s range'.format(second_input, first_input)
if __name__ == '__main__':
app.run_server(debug=True)
and the error is:
dash.exceptions.NonExistentPropException:
Attempting to assign a callback with
the property "['min', 'max']" but the component
"second_slider" doesn't have "['min', 'max']" as a property.
Here are the available properties in "second_slider":
['id', 'marks', 'value', 'className', 'disabled', 'dots', 'included', 'min', 'max', 'tooltip', 'step', 'vertical', 'updatemode', 'loading_state']
But, Slider does have min and max properties and they are listed in error too. I don't know what I'm doing wrong. But, the second slider is not getting updated.
The slider does have the min and max props, but you are trying to set a single prop [min, max] which does not exist. You need two outputs on your callback, one to each prop.
#app.callback([Output(component_id='second_slider', component_property='min'),
Output(component_id='second_slider', component_property='max')]
[Input(component_id='first_slider', component_property='value')])
That should work.

Categories