Python Plotly Chart to Dash - python

I have a plot that I've created with plotly that's interactive with clicks and widgets within Jupyter. I'm happy with how my plot works and I want to export it to use outside of Jupyter using dash. When I take my figure and try to output it with dash all the interactive functions I've created through plotly in Jupyter don't work. When I run the code the figure and layout are present, but the interactive part is gone. If I comment out running this on the app.run_server everything works great. All my functions in plotly use #out.capture() then xx.on_click(function) and I use widgets.Button or widgets.FloatText. Are there changes that I need to make to my capture functions to work in dash? Any suggestions are appreciated!
import pandas as pd
import numpy as np
import io
import os
import plotly.graph_objects as go
import json
import ipywidgets as widgets
from dash import Dash, dcc, html
app=Dash(__name__)
x=np.random.uniform(-10,10,size=50)
y=np.sin(x)
# Initialize the figure using plotly
fig=go.FigureWidget([go.Scatter(x=x,
y=y,
mode='markers',
opacity=1,
marker=dict(
color=['#a3a7e4']*100,
size=[10]*100)
),
go.Scatter(x=[],
y=[],
mode='lines',
marker=dict(
color='DarkSlateGrey')
)])
fig.update_layout(
template='simple_white',
showlegend=False,
title='Title',
xaxis=dict(title="X Axis",
mirror=True,
),
yaxis=dict(title='Y Axis',
mirror=True,
)
)
scatter=fig.data[0]
line = fig.data[1]
# Create the box on the bottom to append the data in the output
out = widgets.Output(layout={'border': '1px solid black'})
out.append_stdout('\n')
reset = widgets.Button(description="Reset")
export = widgets.Button(description="Export")
enter_text=widgets.FloatText(description='Text:')
# Create our callback function
#out.capture()
def update_point(trace, points, selector):
x = list(line.x) + points.xs
y = list(line.y) + points.ys
line.update(x=x, y=y)
c = list(scatter.marker.color)
s = list(scatter.marker.size)
for i in points.point_inds:
c[i] = '#bae2be'
s[i] = 20
with fig.batch_update():
scatter.marker.color = c
scatter.marker.size = s
# Function to clear the output when 'Reset' button is clicked
#out.capture()
def on_reset_clicked(b):
line.update(x=[], y=[])
scatter.marker.color=['#a3a7e4']*100
scatter.marker.size=[10]*100
out.clear_output()
# Function for exporting the points clicked
#out.capture()
def on_export_clicked(b):
line.update(x=[],y=[])
scatter.marker.color=['#a3a7e4']*100
scatter.marker.size=[10]*100
out.clear_output
reset.on_click(on_reset_clicked)
export.on_click(on_export_clicked)
scatter.on_click(update_point)
widgets.VBox([widgets.HBox([reset, export,enter_text]), widgets.VBox([fig, out])])
app.layout=html.Div([
dcc.Graph(
id='Title',
figure=fig
)
])
if __name__=='__main__':
app.run_server(debug=False)

Related

switch plotly chart via button

I want to design the Dash app layout in such a way, that two trend/line charts are arranged in the same container/figure and I want to switch it with a button to display one chart at a time, the x-axis is the same for both charts.
I have tried with the below code but it doesn't update the label of the y-axis and hover text according to charts.
import pandas as pd
import plotly.express as px
dfx = pd.DataFrame({'months':['apr','may','jun','jul','aug','sep','oct','nov','dec','jan','feb','mar']
,'imported_qty':[25,35,45,30,35,45,50,25,30,35,45,40]
,'unit_price':[1.80,1.75,2.10,2.08,2.25,2.20,2.35,2.38,2.28,2.32,2.38,2.51]})
fig = px.line(dfx, x='months', y=dfx['imported_qty'])
fig.update_traces(mode="lines")
fig.update_layout(yaxis={'showgrid': False}
,xaxis={'showgrid': False}
,template='plotly_dark'
,hovermode="x"
,legend=dict(y=1, x=1, font=dict(size=8))
,height=350
,font=dict(size=10, color='gray')
,title={'text': "Import Qty. Trend"
,'y':0.95
,'x':0.5
,'xanchor': 'center'
,'yanchor': 'top'
,'font_size':15
,'font_color':'white'}
,updatemenus=[
dict(
type="buttons",
direction="right",
x=0.7,
y=1,
showactive=True,
buttons=list(
[
dict(
label="Qty",
method="update",
args=[{"y": [dfx['imported_qty']]}
,{'title':'Import Qty. Trend'}
,{'y':'Import qty.'}],
),
dict(
label="Price",
method="update",
args=[{"y": [dfx['unit_price']]}
,{'title':'Unit Price Trend'}
,{'y':'Unit Price'}],
),
]
),
)
]
)
Thank You.
Why not put you fig as dash Graph as a children of a html Div, create two buttons in the layout, and then create a callback that listens on these two buttons and that updates the children of the html Div depending on which button has been clicked ?

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 drawing different graph when clicking on different button

I want to draw graph A when clicking on button A and graph B when clicking on button B, if either graphs is drawn and I click on either button I want the page to be updated with the new graph, I'm using dash and plotly, here the pseudo code
#app.callback([Output("graph-content", "children")],[Input("KPI_gauge","n_clicks")])
def kpi_gauge(v):
if v == None:
raise PreventUpdate
fig1 = KPIGauge(88.7,102)
return [dcc.Graph(figure=fig1)]
#app.callback([Output("graph-content", "children")],[Input("comparaison_graph","n_clicks")]
def comparaison_graph(v):
if v == None:
raise PreventUpdate
fig = ComparaisonGraph()
return [dcc.Graph(figure=fig)]
but I get this error dash duplicate callback output, how to resolve this error?
https://dash.plotly.com/callback-gotchas A component/property pair can only be the Output of one callback. Your code broke this rule, graph-content was the output of two callbacks
this code demonstrates a way to output plotly figure on first click and update data in graph on subsequent clicks
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import dash
from jupyter_dash import JupyterDash
import random
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import numpy as np
# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
[
html.Button("KPI", id="KPI_gauge", n_clicks=0),
html.Button("Graph", id="comparaison_graph", n_clicks=0),
html.Div(id="graph-content"),
]
)
#app.callback(
Output("graph-content", "children"),
Input("KPI_gauge", "n_clicks"),
Input("comparaison_graph", "n_clicks"),
State("graph-content", "children"),
)
def update_graph(kpi_but, graph_but, content):
if not kpi_but and not graph_but:
raise PreventUpdate
ctx = dash.callback_context
button = ctx.triggered[0]["prop_id"].split(".")[0]
fig_id = f"{button}_fig"
if button=="KPI_gauge":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["value"] = random.randint(0,400)
else: # create
fig = go.Figure(go.Indicator(mode="gauge+number",value=270,title={"text": "KPI"},))
elif button=="comparaison_graph":
if content and content["type"]=="Graph" and content["props"]["id"]==fig_id: # update
fig = content["props"]["figure"]
fig["data"][0]["y"] = np.sort(np.random.uniform(1,5,365*2))
else: # create
df = pd.DataFrame({"date":pd.date_range("1-aug-2019", periods=365*2), "value":np.linspace(1,5,365*2)})
fig = px.line(df, x="date", y="value")
else:
raise PreventUpdate
return dcc.Graph(id=fig_id, figure=fig)
app.run_server(mode="inline", port=8051)

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

Plotly-Dash: How to remove the status icon from Dash WebApp?

I am building Web Dashboard for IPL using the Dash plotly. In there a Status icon that shows the webserver status.
How Could I remove it from the Web Page? I am not able to explain it much so adding the image below marked in Yellow
Simply setting Debug=False here should do the trick:
app.run_server(debug=False)
I'll use a previous post of mine to illustrate it. You'll find a complete code snippet below.
1 - app.run_server(debug=True)
2 - app.run_server(debug=False)
Complete code
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from IPython.core.debugger import set_trace
# Load Data
df = px.data.tips()
# Build App
app = JupyterDash(__name__)
app.layout = html.Div([
html.H1("ScikitLearn: Polynomial features"),
dcc.Graph(id='graph'),
html.Label([
"Set number of features",
dcc.Slider(id='PolyFeat',
min=1,
max=6,
marks={i: '{}'.format(i) for i in range(10)},
value=1,
)
]),
])
# Define callback to update graph
#app.callback(
Output('graph', 'figure'),
[Input("PolyFeat", "value")]
)
def update_figure(nFeatures):
global model
# data
df = px.data.tips()
x=df['total_bill']
y=df['tip']
# model
model = make_pipeline(PolynomialFeatures(nFeatures), LinearRegression())
model.fit(np.array(x).reshape(-1, 1), y)
x_reg = x.values
y_reg = model.predict(x_reg.reshape(-1, 1))
df['model']=y_reg
# figure setup and trace for observations
fig = go.Figure()
fig.add_traces(go.Scatter(x=df['total_bill'], y=df['tip'], mode='markers', name = 'observations'))
# trace for polynomial model
df=df.sort_values(by=['model'])
fig.add_traces(go.Scatter(x=df['total_bill'], y=df['model'], mode='lines', name = 'model'))
# figure layout adjustments
fig.update_layout(yaxis=dict(range=[0,12]))
fig.update_layout(xaxis=dict(range=[0,60]))
#print(df['model'].tail())
fig.update_layout(template = 'plotly_dark')
return(fig)
# Run app and display result inline in the notebook
app.enable_dev_tools(dev_tools_hot_reload =True)
app.run_server(mode='inline', port = 8040, dev_tools_ui=True, debug=False,
dev_tools_hot_reload =True, threaded=True)

Categories