Plotly interactive plot python: Change the dropdown menu to input box - python

I want to change the dropdown button with an input box so I can search for the item by starting to type the name and then select. So far I have a drop down box where you can select either one item or all of them at the same time. However, I want the user to be able to start typing the name of the item and then click and select the item they want to display their graph.
As I am new to plotly, any suggestion is very welcome and appreciated :)
Here is what the plot looks like so far:
My code:
def interactive_multi_plot(actual, forecast_1, forecast_2, title, addAll = True):
fig = go.Figure()
for column in forecast_1.columns.to_list():
fig.add_trace(
go.Scatter(
x = forecast_1.index,
y = forecast_1[column],
name = "Forecast_SI"
)
)
button_all = dict(label = 'All',
method = 'update',
args = [{'visible': forecast_1.columns.isin(forecast_1.columns),
'title': 'All',
'showlegend':True}])
for column in forecast_2.columns.to_list():
fig.add_trace(
go.Scatter(
x = forecast_2.index,
y = forecast_2[column],
name = "Forecast_LSTM"
)
)
button_all = dict(label = 'All',
method = 'update',
args = [{'visible': forecast_2.columns.isin(forecast_2.columns),
'title': 'All',
'showlegend':True}])
for column in actual.columns.to_list():
fig.add_trace(
go.Scatter(
x = actual.index,
y = actual[column],
name = "True values"
)
)
button_all = dict(label = 'All',
method = 'update',
args = [{'visible': actual.columns.isin(actual.columns),
'title': 'All',
'showlegend':True}])
fig.layout.plot_bgcolor = '#010028'
fig.layout.paper_bgcolor = '#010028'
def create_layout_button(column):
return dict(label = column,
method = 'update',
args = [{'visible': actual.columns.isin([column]),
'title': column,
'showlegend': True}])
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = ([button_all] * addAll) + list(actual.columns.map(lambda column: create_layout_button(column)))
)
]
)
# Update remaining layout properties
fig.update_layout(
title_text=title,
height=800,
font = dict(color='#fff', size=12)
)
fig.show()
This is the error I receive:

small changes to interactive_multi_plot().
for all three add_trace() add meta = column for each of the scatter creations
change to return fig instead of fig.show()
simulate some data and call interactive_multi_plot(). I have assumed all three data frames have the same columns
S = 100
C = 10
actual = pd.DataFrame(
{
c: np.sort(np.random.uniform(0, 600, S))
for c in [
f"{a}{b}-{c}"
for a, b, c in zip(
np.random.randint(100, 200, C),
np.random.choice(list("ABCDEF"), C),
np.random.randint(300, 400, C),
)
]
}
)
f1 = actual.assign(**{c:actual[c]*1.1 for c in actual.columns})
f2 = actual.assign(**{c:actual[c]*1.2 for c in actual.columns})
fig = interactive_multi_plot(actual, f1, f2, "Orders")
solution
use dash this does support interactive drop downs
simple case of show figure and define a callback on item selected from dash drop down
it could be considered that updatemenus is now redundant. I have not considered sync of updatemenus back to dash drop down
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from jupyter_dash import JupyterDash
# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
[
dcc.Dropdown(
id="lines",
options=[{"label": c, "value": c} for c in ["All"] + actual.columns.tolist()],
value="All",
),
dcc.Graph(id="interactive-multiplot", figure=fig),
]
)
#app.callback(
Output("interactive-multiplot", "figure"),
Input("lines", "value"),
State("interactive-multiplot", "figure"),
)
def updateGraphCB(line, fig):
# filter traces...
fig = go.Figure(fig).update_traces(visible=False).update_traces(visible=True, selector={"meta":line} if line!="All" else {})
# syn button to dash drop down
fig = fig.update_layout(updatemenus=[{"active":0 if line=="All" else actual.columns.get_loc(line)+1}])
return fig
app.run_server(mode="inline")

Related

Python dash return several values inside for loop

For my dash app, in order to update some graphs dynamically, I have to use a function that I named update_graphs inside a for loop. Some of the graphs contain several traces while some others only have one. The update_graphs function is called inside a callback and returns a dict and an int to update the extendData property of the graph object. However, since I am using a return statement inside a for loop, I only get the first trace.
I am not familiar with the generators and the yield keyword, maybe this is an option. But I haven't been able to make it work.
I have also tried to store the results of the update_graphs inside a list but it is not working.
Any help is appreciated!
Here is the code for the app:
import dash
from dash.dependencies import Output, Input, State, MATCH, ALL
from dash import dcc, html, ctx
import plotly
import plotly.express as px
import random
import plotly.graph_objs as go
import pandas as pd
# Initializing the data with the correct format
init_store = {}
n=3
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int'), 'c':pd.Series(dtype='int'), 'd':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store['0'] = init_df
for i in range(n):
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store[f'{i+1}'] = init_df
# Function to update the dataframes with the new observations
def get_data(json_data):
df = pd.read_json(json_data)
compteur = df['a'][len(df['a'])-1]
if len(df.columns) > 2:
new_row = {'a':compteur + 1, 'b':random.randint(13,26), 'c':random.randint(13,26), 'd':random.randint(13,26)}
else:
new_row = {'a':compteur + 1, 'b':random.randint(13,26)}
df = df.shift(periods=-1)
df.iloc[len(df)-1] = new_row
return(df.to_json())
# Function to update the graphs based on the dataframes
def update_graphs(json_data, column, index=0):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = df['a'][len(df)-1]
y_new = df[column][nb_obs-1]
return dict(x=[[x_new]], y=[[y_new]]), index
colors = px.colors.qualitative.G10
def generate_graph_containers(index, json_data):
dataframe = pd.read_json(json_data)
X = dataframe['a']
Y = dataframe.loc[:, dataframe.columns != 'a']
graph_id = {'type': 'graph-', 'index': index}
return(
html.Div(
html.Div(
dcc.Graph(
id=graph_id,
style={"height": "8rem"},
config={
"staticPlot": False,
"editable": False,
"displayModeBar": False,
},
figure=go.Figure(
{
"data": [
{
"x": list(X),
"y": list(Y[Y.columns[i]]),
"mode": "lines",
"name": Y.columns[i],
"line": {"color": colors[i+2]},
}
for i in range(len(Y.columns))
],
"layout": {
"uirevision": True,
"margin": dict(l=0, r=0, t=4, b=4, pad=0),
"xaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"yaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"paper_bgcolor": "rgba(0,0,0,0)",
"plot_bgcolor": "rgba(0,0,0,0)",
}
}
)
)
)
)
)
app = dash.Dash(__name__)
store = [dcc.Store(id={'type':'store-', 'index':i}, data=init_store[str(i)].to_json()) for i in range(n)]
def make_layout():
return(
html.Div(
[
html.Div(
store
),
dcc.Interval(
id = 'interval',
interval = 1000,
n_intervals = 0
),
html.Div(
[
generate_graph_containers(str(i), store[i].data) for i in range(n)
]
)
]
)
)
app.layout = make_layout
#app.callback(
Output(component_id={'type':'store-', 'index':MATCH}, component_property='data'),
[
Input('interval', 'n_intervals'),
State(component_id={'type':'store-', 'index':MATCH}, component_property='data')
]
)
def update_data(time, data):
return(get_data(data))
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
triggered_id = ctx.triggered_id
print(triggered_id['index'])
columns = ['b', 'c', 'd']
if triggered_id['index'] == 0:
for i in range(len(columns)):
return(update_graphs(data, columns[i], i))
else:
return(update_graphs(data, 'b'))
if __name__ == '__main__':
app.run_server(debug=True)
I figured it out. The trick is in the format expected to update the extendData property of a figure. When trying to update several traces, the format should be a dictionary with a key for the x values and one for the y values. The values associated should be an array for each key, containing an array per trace. Don't forget to add the trace indices after the dictionary. So for example, in the case of 3 distinct traces, the function should return something like:
dict(x=[[x_0], [x_1], [x_2]], y=[[y_0], [y_1], [y_2]]), [0, 1, 2]
Therefore the update_graphs function should be:
def update_graphs(json_data):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = []
y_new = []
trace_index = []
for i in range(len(df.columns)-1):
x_new.append([df['a'][len(df)-1]])
y_new.append([df[df.columns[i+1]][nb_obs-1]])
trace_index.append(i)
return(dict(x=x_new, y=y_new), trace_index)
And the callback to update the graphs should be changed to:
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
return(update_graphs(data))

DataFrame to table with dropdown list - Python, pandas

I have a task and I'm done.
I've dataframe and I need to save it for the users - it can't be an excel file, but it should be something with filtering button - dropdown list. Something user friendly..
Do you have any idea how to do it?
I tried to join this:
def multi_plot(df, title, addAll = True):
fig = go.Figure()
for column in df.columns.to_list():
fig.add_trace(
go.Scatter(
x = df.index,
y = df[column],
name = column
)
)
button_all = dict(label = 'All',
method = 'update',
args = [{'visible': df.columns.isin(df.columns),
'title': 'All',
'showlegend':True}])
def create_layout_button(column):
return dict(label = column,
method = 'update',
args = [{'visible': df.columns.isin([column]),
'title': column,
'showlegend': True}])
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = ([button_all] * addAll) + list(df.columns.map(lambda column: create_layout_button(column)))
)
],
yaxis_type="log"
)
But i'm lost :/
Should I add this to html table?

Is there a way to force the initial load of a figure?

Using Python/Plotly, I'm trying to create a figure that has a map and a slider to choose the year. Depeding on the year that is selected in the slider, the map should render different results.
Evertyhing is working fine with exception for one thing - I cannot set the initial load state of the map to be the same as the initial step as defined in
sliders = [dict(active=0, pad={"t": 1}, steps=steps)]
When the first render is done, and the slider has not yet been used, the data displayed seems to be accordingly to the last state of the data_slider list, ie. it shows the last values that were loaded in the list (I'm not totally sure about this conclusion).
Is there a way to set the first data display accordingly to the step that is pre-defined? Or in alternative, a way to force the first load to be something like data_slider[0].
Below is the code I'm using.
import pandas as pd
import os
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import numpy as np
import random
ds = 'https://archive.org/download/globalterrorismdb_0718dist/globalterrorismdb_0718dist.csv'
# ds = os.getcwd() + '\globalterrorismdb_0718dist.csv'
fields = ['eventid', 'iyear', 'country', 'country_txt', 'region_txt', 'city', 'latitude', 'longitude', 'nkill']
df = pd.read_csv(ds, encoding='ISO-8859-1', usecols=fields)
df = df.loc[df['iyear'] >= 2000]
df['nkill'].fillna(0, inplace=True)
df = df.groupby(['country_txt', 'iyear'])['nkill'].sum().reset_index()
df = df.loc[df['nkill'] > 0]
data_slider = []
for year in df.iyear.unique():
df_year = df[(df['iyear'] == year)]
data_year = dict(
type='choropleth',
name='',
colorscale='amp',
locations=df_year['country_txt'],
z=df_year['nkill'],
zmax=15000,
zmin=0,
locationmode='country names',
colorbar=dict(
len=0.5,
thickness=10,
title=dict(
text='Number of fatalities',
font=dict(
family='Arial',
size=14,
),
side='right'
),
tickfont=dict(
family='Arial',
size=12),
)
)
data_slider.append(data_year)
steps = []
for i in range(len(data_slider)):
step = dict(
method='restyle',
args=['visible', [False] * len(data_slider)],
label=(format(i + 2000))
)
step['args'][1][i] = True
steps.append(step)
sliders = [dict(active=0, pad={"t": 1}, steps=steps)]
layout = dict(
geo=dict(
scope='world',
showcountries=True,
projection=dict(
type='equirectangular'
),
showland=True,
landcolor='rgb(255, 255, 255)',
showlakes=False,
showrivers=False,
showocean=True,
oceancolor='white',
),
sliders=sliders
)
fig = go.Figure(data=data_slider, layout=layout)
fig.update_layout(
font=dict(family='Arial', size=12),
autosize=False,
width=1250,
height=750,
margin=dict(
l=25,
r=0,
b=100,
t=50,
pad=0,
)
)
app = dash.Dash(__name__)
server = app.server
app.layout = html.Div(children=[
# html.H1(children='Test2'),
# html.Div(children='''
# Example of html Container
# '''),
dcc.Graph(
id='fig',
figure=fig
)
])
if __name__ == '__main__':
app.run_server(debug=True)
The problem here is not on sliders but on the traces you are defining in data_slider. You need to play with the visible parameter in order to have the first plot properly rendered. The following trick should work.
data_slider = []
for i, year in enumerate(df.iyear.unique()):
df_year = df[(df['iyear'] == year)]
data_year = dict(
type='choropleth',
name='',
colorscale='amp',
locations=df_year['country_txt'],
z=df_year['nkill'],
zmax=15000,
zmin=0,
locationmode='country names',
visible= True if i==0 else False,
colorbar=dict(
len=0.5,
thickness=10,
title=dict(
text='Number of fatalities',
font=dict(
family='Arial',
size=14,
),
side='right'
),
tickfont=dict(
family='Arial',
size=12),
)
)
data_slider.append(data_year)
Extra
Given that you are generating all the plots outside the app you could actually avoid to use dash.

Error drawing a choropleth map with plotly

I'm new in Plotly. I'm trying to draw a choropleth map with this tool. I have my data in a database and I'm trying to show some of them in a map.
First, I launch my das application : app = dash.Dash()
Once I'm connected to the database, I execute the following code:
#Load dataframes
df = pd.read_sql('SELECT * FROM Companies_Public', con=db_connection)
#Choropleth map
app.layaout = html.Div([
dcc.Graph(
id = 'Map',
figure={
'data': [ dict(
type = 'choropleth',
locations = df['ISOCountry'],
z = sum(df['FinalPointsPerDemography']),
text = df['CountryName'],
colorscale = [[0,"rgb(5, 10, 172)"],[0.35,"rgb(40, 60, 190)"],[0.5,"rgb(70, 100, 245)"],\
[0.6,"rgb(90, 120, 245)"],[0.7,"rgb(106, 137, 247)"],[1,"rgb(220, 220, 220)"]],
autocolorscale = False,
reversescale = True,
marker = dict(
line = dict (
color = 'rgb(180,180,180)',
width = 0.5
) ),
colorbar = dict(
autotick = False,
tickprefix = '$',
title = 'Points<br>'),
) ],
'layout': go.Layout(
title = 'Points by Company per Demography',
geo = dict(
showframe = False,
showcoastlines = False,
projection = dict(
type = 'Mercator'
)
)
)
}
)
])
# Add the server clause:
if __name__ == '__main__':
app.run_server()
I get the next message in the console:
dash.exceptions.NoLayoutException: The layout was None at the time that run_server was called. Make sure to set the layout attribute of your application before running the server.
The following figure shows the structure of df:
df structure

Python Plotly - Align Y Axis for Scatter and Bar

I'm trying to create a plotly graph with a Scatter and Graph elements. It all goes nicely, but one issue - the two Y axis don't align around 0.
I have tried playing with different attributes, such as 'mirror' and tick0, I also tried following the examples on plotly's site, but it's mostly multiple y-axis with the same graph type.
What can I do to fix this?
import utils
import pandas as pd
import plotly.plotly as py
import plotly.graph_objs as go
import plotly
pd_data ['dt'] = ... dates
pd_data['price'] = ... prices
pd_data['car'] = ... cars
price = go.Scatter(
x = pd_data['dt'],
y = pd_data['price'],
mode = 'lines',
name = 'Price',
xaxis = 'x',
yaxis='y1',
marker = dict(
color = utils.prep_color_string('orange'),
),
line = dict(
width = utils.line_width,
),
)
car = go.Bar(
x = pd_data['dt'],
y = pd_data['car'],
#mode = 'lines',
name = 'Cars',
xaxis = 'x',
yaxis='y2',
marker = dict(
color = utils.prep_color_string('light_green'),
),
#line = dict(
# width = utils.line_width,
#),
)
data = [price, car]
layout = dict(
title = 'Price/Car',
geo = dict(
showframe = True,
showcoastlines = True,
projection = dict(
type = 'Mercator'
)
),
yaxis=dict(
title = 'Price',
tickprefix = "$",
overlaying='y2',
anchor = 'x'
),
yaxis2=dict(
title = 'Car',
dtick = 1,
#tickprefix = "",
side = 'right',
anchor = 'x',
),
)
fig = dict( data=data, layout=layout)
div = plotly.offline.plot( fig, validate=False, output_type = 'file',filename='graph.html' ,auto_open = False)
I have been struggling with this as well. Exact same problem, but I am using R. The way I figured around it was to use the rangemode="tozero" for both the yaxis and yaxis2 layouts.
I think in your case, it would look like this:
layout = dict(
title = 'Price/Car',
geo = dict(
showframe = True,
showcoastlines = True,
projection = dict(
type = 'Mercator'
)
),
yaxis=dict(
title = 'Price',
tickprefix = "$",
overlaying='y2',
anchor = 'x',
rangemode='tozero'
),
yaxis2=dict(
title = 'Car',
dtick = 1,
#tickprefix = "",
side = 'right',
anchor = 'x',
rangemode = 'tozero'
),
)
Let me know if that works for you.

Categories