I have been trying to connect two dropdowns to each other i.e. If I select a value from one DropDown the path and the contents of the other DropDown should change accordingly.
e.g I have a folder of dates (06-06-2020, 07-06-2020 and 08-06-2020) so if I select a date which is 07-06-2020 the DropDown below it should display the values which are inside the date folder 07-06-2020.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate
import plotly.graph_objects as go
import os
import pandas as pd
os.chdir(r"C:\Users\ankit\Desktop\dash_assign\SessionCode")
dir_route = os.getcwd()
app = dash.Dash(__name__)
g_t = ["OHLC Bars", "Candlesticks"]
# external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# csv_list = [files for files in os.listdir(dir_route)]
csv_list = []
date_list = []
path_list = []
for dates in os.listdir(dir_route):
date = dates
date_list.append(date)
app.layout = html.Div(
children=[
html.H1("Time Series Graph"),
dcc.Dropdown(id="select_date",
options=[{"label": dates, "value": str(dates)} for dates in date_list],
value=20180102,
style={
"margin": "10px"
}
),
dcc.Dropdown(id="sym",
placeholder="Select Symbol",
style={
"margin": "10px"
}),
# html.Button("PLOT", id='plot'),
dcc.Graph(id='graph')
]
)
#app.callback(Output('sym', component_property="options"),
[Input('select_date', 'value')])
def update_dates(dat):
lst = os.listdir(os.path.join(dir_route, dat))
for files in lst:
if files.endswith(".csv"):
files.split(" ")
new_file = files[0]
return new_file
#app.callback(Output('graph', 'figure'),
[Input('select_date', 'value'),
Input("sym", "options"),
])
def update_graph(date, symbols):
path = os.path.join(dir_route, date, symbols)
df = pd.read_csv(os.path.join(path+".csv"), index_col=False)
fig = go.Figure(go.Ohlc(x =df["TimeStamp"],
open=df['Open'],
high=df['High'],
low=df['Low'],
close=df['Close'],
increasing_line_color='#00ff00',
decreasing_line_color='#ff0000',
name="Price",
))
return fig
if __name__ == "__main__":
# fig = update_candle("A.csv")
# fig.show()
app.run_server(debug=True, port=5001)
This is my root directory and inside it i have various csv's which should appear in the DropDown once I select the dates
csv files inside one date directory
You can use a callback to update the second dropdown. Below is an example where cascading dropdown is created from table
#app.callback(
Output(component_id='id_first_dropdown', component_property='options'),
[Input(component_id='id_second_dropdown', component_property='value')]
)
def update_dp(filter_value):
sql = "Select distinct second_dropdown_options from table where first_dropdown_value='" + filter_value + "'"
df = pd.read_sql_query(sql, conn)
df = df['second_dropdown_options']
return [{'label': i, 'value': i} for i in df.unique()]
Related
I am coding a Dash page to plot some data for myself.
I want to have some dynamic features on some of my plots.
On my dash I have one dropdown, one pie chart and one scatter chart.
Here is my code:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
app = dash.Dash()
app.layout = html.Div([
html.Label(['Select the Type:']),
dcc.Dropdown(
id='type_filter',
options=[{'label': i, 'value': i} for i in df.Type.unique()],
value='All'
),
dcc.Graph(
id='allocation_chart'
),
dcc.Graph(
id='allocation_history_chart'
),
])
#app.callback(
Output('allocation_chart','figure'),
[
Input('type_filter', 'value')
]
)
def update_allocation(type_):
if type_ == 'All':
filtered = df
else:
mask = df['Type'] == type_
filtered = df.loc[mask]
data = [
go.Pie(
labels=filtered['Asset'],
values=filtered['Total']
)
]
return {
'data': data,
'layout': go.Layout(title='Asset Allocation by Type')
}
#app.callback(
Output('allocation_history_chart','figure'),
[
Input('type_filter','value')
]
)
def update_allocation_history(type_):
if type_ == 'All':
filtered = df_index
else:
mask = df['Type'] == type_
filtered = df_index.loc[mask]
data = go.Figure()
for ticker in filtered.index:
data.add_trace(
go.Scatter(
x = filtered.loc[ticker].index,
y = filtered.loc[ticker],
name = ticker
)
)
return {
'data': [data],
'layout': go.Layout(title='History of Asset')
}
if __name__ == '__main__':
app.run_server()
here is the Dash:
Here is the scatter chart ran in my Notebook:
Everything is working - Dash is running well, i can see the dropdown and the pie chart - except the scatter chart - the second one.
I absolutely don't understand why because when I run the scatter chart in my Notebook it's working pretty well.
If you have any idea, don't hesitate.
i have a directory containing three files, years.csv, 2014.csv and 2015.csv. i want to plot a population pyramid graph for the two files but i want pandas to pick the dataframe from the years.csv with respect to the slider value.
my years.csv looks like, on the slider when i select 2014, from the code you can see, its an int that i convert into a string and append .csv to it. but all i want is that final string interpreted as df = pd.read_csv('2014.csv') so that i can be able to generate graphs of all the years as long as that file is in the directoy.
years
0
2014(2014.csv)
1
2015(2015.csv)
from dash import Dash, dcc, html, Input, Output
# import plotly.express as px
import plotly.graph_objects as gp
import pandas as pd
# df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
df = pd.read_csv('years.csv')
app = Dash(__name__)
app.layout = html.Div([
dcc.Graph(id='graph-with-slider'),
dcc.Slider(
df['year'].min(),
df['year'].max(),
step=None,
value=df['year'].min(),
marks={str(year): str(year) for year in df['year'].unique()},
id='year-slider'
)
])
#app.callback(
Output('graph-with-slider', 'figure'),
Input('year-slider', 'value'))
def update_figure(selected_year):
new_df = str(df[df.year == selected_year]) + ".csv"
print(new_df)
# fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
# size="pop", color="continent", hover_name="country",
# log_x=True, size_max=55)
y_age = new_df['Age']
x_M = new_df['Male']
x_F = new_df['Female'] * -1
# fig.update_layout(transition_duration=500)
# Creating instance of the figure
fig = gp.Figure()
# Adding Male data to the figure
fig.add_trace(gp.Bar(y= y_age, x = x_M,
name = 'Male',
orientation = 'h'))
# Adding Female data to the figure
fig.add_trace(gp.Bar(y = y_age, x = x_F,
name = 'Female', orientation = 'h'))
# Updating the layoutout for our graph
fig.update_layout(title = 'Population Pyramid of Uganda-2015',
title_font_size = 22, barmode = 'relative',
bargap = 0.0, bargroupgap = 0,
xaxis = dict(tickvals = [-600000, -400000, -200000,
0, 200000, 400000, 600000],
ticktext = ['6k', '4k', '2k', '0',
'2k', '4k', '6k'],
title = 'Population in Thousands',
title_font_size = 14)
)
# fig.show()
return fig
if __name__ == '__main__':
app.run_server(debug=True)
I am trying to read sensor values from raspberry pi sense hat and live plotting 3 sensor data on same y-axis and time on x-axis. But only one graph is visible out of 3. For this example I am using a random number generator for sensor values. How to show live data for all 3 values on y-axis? What am I doing wrong?
# from dash.dependencies import Output, Event
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import random
import plotly.graph_objs as go
from collections import deque
# define a function to get CPU temperature data
def check_CPU_temp():
a = random.randint(0,9)
b = random.randint(0,9)
c = random.randint(0,9)
return a, b, c
X = deque(maxlen=50)
X.append(1)
acc_X = deque(maxlen=50)
acc_Y = deque(maxlen=50)
acc_Z = deque(maxlen=50)
a, b, c = check_CPU_temp()
acc_X.append(a)
acc_Y.append(b)
acc_Z.append(c)
def update_values(acc_X, acc_Y, acc_Z):
a1, b1, c1 = check_CPU_temp()
acc_X.append(a1)
acc_Y.append(b1)
acc_Z.append(c1)
return acc_X, acc_Y, acc_Z
app = dash.Dash(__name__)
app.layout = html.Div(
[
dcc.Graph(id='live-graph', animate=True),
dcc.Interval(
id='refresh',
interval=1 * 1000,
n_intervals=0
),
]
)
#app.callback(Output('live-graph', 'figure'), [Input("refresh", "n_intervals")])
def update_graph_scatter(n_intervals):
X.append(X[-1] + 1)
update_values(acc_X, acc_Y, acc_Z)
data_x = go.Scatter(
x=list(X),
y=list(acc_X),
name='acc x',
showlegend=True,
mode= 'lines',
yaxis='y'
)
data_y = go.Scatter(
x=list(X),
y=list(acc_Y),
name='acc y',
showlegend=True,
mode= 'lines',
yaxis='y1'
)
data_z = go.Scatter(
x=list(X),
y=list(acc_Z),
name='acc z',
showlegend=True,
mode= 'lines',
yaxis='y2'
)
layout = go.Layout(xaxis=dict(range=[min(X), max(X)],),
yaxis=dict(
range=[0, 10],
title='y'),
yaxis1=dict(
range=[0, 10],
side='right',
title='y1'),
yaxis2=dict(
range=[0, 10],
side='left',
title='y2'))
return {'data': [data_x, data_y, data_z], 'layout': layout}
if __name__ == '__main__':
app.run_server(debug=True, host='192.168.1.16', port='8050')
I was able to solve this using fig.addtrace()
import dash
from datetime import datetime as dt
from dash.dependencies import Output, Input
from dash import dcc
from dash import html
from collections import deque
from plotly.subplots import make_subplots
import plotly.graph_objs as go
from sense_hat import SenseHat
sense = SenseHat()
sense.set_imu_config(True, True, True) # accelerometer, magnetometer , gyroscope
sense.clear()
# define a function to get acceleration data
def check_acc_data():
acc = sense.get_accelerometer_raw()
return round(acc["x"],3), round(acc["y"],3), round(acc["z"],3)
def update_values(times, acc_X, acc_Y, acc_Z):
times.append(dt.strptime(str(dt.now()), '%Y-%m-%d %H:%M:%S.%f'))
a1, b1, c1 = check_acc_data()
acc_X.append(a1)
acc_Y.append(b1)
acc_Z.append(c1)
return times, acc_X, acc_Y, acc_Z
times = deque(maxlen=50)
acc_X = deque(maxlen=50)
acc_Y = deque(maxlen=50)
acc_Z = deque(maxlen=50)
update_values(times, acc_X, acc_Y, acc_Z)
app = dash.Dash(__name__)
app.layout = html.Div(
[
dcc.Graph(id='example_graph', figure={}),
dcc.Interval(
id='graph-update',
interval=250,
n_intervals=0
),
]
)
#app.callback(
Output("example_graph", "figure"),
[Input('graph-update', 'n_intervals')]
)
def update_figure(n):
update_values(times, acc_X, acc_Y, acc_Z)
fig = make_subplots(specs=[[{"secondary_y": True}]])
fig.add_trace(
go.Scatter(x=list(times), y=list(acc_X), mode="lines+markers", name='Acc X'),
secondary_y=False
)
fig.add_trace(
go.Scatter(x=list(times), y=list(acc_Y), mode="lines+markers", name='Acc Y'),
secondary_y=True,
)
fig.add_trace(
go.Scatter(x=list(times), y=list(acc_Z), mode="lines+markers", name='Acc Z'),
secondary_y=True,
)
# fig.update_yaxes(secondary_y=False)
# fig.update_yaxes(secondary_y=True)
fig.update_layout(yaxis=dict(range=[-2.5,2.5]),
yaxis1=dict(range=[-2.5,2.5]),
yaxis2=dict(range=[-2.5,2.5]))
return fig
if __name__ == '__main__':
app.run_server(host='192.168.1.16',port='8050')
I've been working on getting a select menu and Bokeh plot up and running on a dataset I'm working with. The dataset can be found here. I have no experience with JavaScript, but I believe my select menu isn't connected/-ing to my plot. Therefore, I have a plot outline, but no data displayed. As I run the script from the console with bokeh serve --show test.py, I get the first 7 notifications in my JS console. The last three (those in the red bracket in the screenshot) occur when I try and change to a different item in my select menu.
Goal: Display the plot of data for rows those id number ('ndc' in this example) is selected in the Select menu.
Here's my code (modified from this post) that I used to get started. This one was also used, as were a handful of others, and the Bokeh documentation itself.
import pandas as pd
from bokeh.io import curdoc, output_notebook, output_file
from bokeh.layouts import row, column
from bokeh.models import Select, DataRange1d, ColumnDataSource
from bokeh.plotting import figure
# output_notebook()
output_file('test.html')
def get_dataset(src, drug_id):
src.drop('Unnamed: 0', axis = 1, inplace = True)
df = src[src.ndc == drug_id].copy()
df['date'] = pd.to_datetime(df['date'])
df = df.set_index(['date'])
df.sort_index(inplace=True)
source = ColumnDataSource(data=df)
return source
def make_plot(source, title):
plot = figure(plot_width=800, plot_height = 800, tools="", x_axis_type = 'datetime', toolbar_location=None)
plot.xaxis.axis_label = 'Time'
plot.yaxis.axis_label = 'Price ($)'
plot.axis.axis_label_text_font_style = 'bold'
plot.x_range = DataRange1d(range_padding = 0.0)
plot.grid.grid_line_alpha = 0.3
plot.title.text = title
plot.line(x= 'date', y='nadac_per_unit', source=source)
return plot
def update_plot(attrname, old, new):
ver = vselect.value
plot.title.text = "Drug Prices"
src = get_dataset(df, ver)
source.date.update(src.date)
df = pd.read_csv('data/plotting_data.csv')
ver = '54034808' #Initial id number
cc = df['ndc'].astype(str).unique() #select-menu options
vselect = Select(value=ver, title='Drug ID', options=sorted((cc)))
source = get_dataset(df, ver)
plot = make_plot(source, "Drug Prices")
vselect.on_change('value', update_plot)
controls = row(vselect)
curdoc().add_root(row(plot, controls))
There were some problems in your code:
You want to drop the Unnamed: 0 column. This can only be done once and when you try this again it will throw an error since this column does not exist anymore.
The way you tried to filter the dataframe didn't work and would result in an empty dataframe. You can select rows based on a column value like this: df.loc[df['column_name'] == some_value]
Updating the ColumnDataSource object can be done by replacing source.data with the new data.
import pandas as pd
from bokeh.io import curdoc, output_notebook, output_file
from bokeh.layouts import row, column
from bokeh.models import Select, DataRange1d, ColumnDataSource
from bokeh.plotting import figure
output_notebook()
output_file('test.html')
def get_dataset(src, drug_id):
src.drop('Unnamed: 0', axis = 1, inplace = True)
df = src.loc[src['ndc'] == int(drug_id)]
df['date'] = pd.to_datetime(df['date'])
df = df.set_index(['date'])
df.sort_index(inplace=True)
source = ColumnDataSource(data=df)
return source
def make_plot(source, title):
plot = figure(plot_width=800, plot_height = 800, tools="", x_axis_type = 'datetime', toolbar_location=None)
plot.xaxis.axis_label = 'Time'
plot.yaxis.axis_label = 'Price ($)'
plot.axis.axis_label_text_font_style = 'bold'
plot.x_range = DataRange1d(range_padding = 0.0)
plot.grid.grid_line_alpha = 0.3
plot.title.text = title
plot.line(x= 'date', y='nadac_per_unit', source=source)
return plot
def update_plot(attrname, old, new):
ver = vselect.value
df1 = df.loc[df['ndc'] == int(new)]
df1['date'] = pd.to_datetime(df1['date'])
df1 = df1.set_index(['date'])
df1.sort_index(inplace=True)
newSource = ColumnDataSource(df1)
source.data = newSource.data
df = pd.read_csv('data/plotting_data.csv')
ver = '54034808' #Initial id number
cc = df['ndc'].astype(str).unique() #select-menu options
vselect = Select(value=ver, title='Drug ID', options=sorted((cc)))
source = get_dataset(df, ver)
plot = make_plot(source, "Drug Prices")
vselect.on_change('value', update_plot)
controls = row(vselect)
curdoc().add_root(row(plot, controls))
I’m trying to create a time-series Dash line graph that has multiple interactive dropdown user input variables. I would ideally like each of the dropdown inputs to allow for multiple selections.
While I’m able to create the drop down menus successfully, the chart isn’t updating like I’d like. When I allow the dropdowns to have multiple selections, I get an error that arrays are different lengths. And when I limit the dropdowns to one selection, I get an error that [‘Vendor_Name’] is not in index. So this may be two separate problems.
Graph that doesn’t work:
Snippet of Excel data imported into DF
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import pandas as pd
#import plotly.graph_objs as go
df = pd.read_csv("Data.csv", sep = "\t")
df['YearMonth'] = pd.to_datetime(df['YearMonth'], format = '%Y-%m')
cols = ['Product_1', 'Product_2', 'Product_3']
vendor = df['Vendor'].unique()
app = dash.Dash('Data')
app.layout = html.Div([
html.Div([
html.Div([
html.Label('Product'),
dcc.Dropdown(
id = 'product',
options = [{
'label' : i,
'value' : i
} for i in cols],
multi = True,
value = 'Product_1'
),
]),
html.Div([
html.Label('Vendor'),
dcc.Dropdown(
id = 'vendor',
options = [{
'label' : i,
'value' : i
} for i in vendor],
multi = True,
value = 'ABC')
,
]),
]),
dcc.Graph(id = 'feature-graphic')
])
#app.callback(Output('feature-graphic', 'figure'),
[Input('product', 'value'),
Input('vendor', 'value')])
def update_graph(input_vendor, input_column):
df_filtered = df[df['Vendor'] == input_vendor]
##also tried setting an index because of the error I was getting. Not sure if necessary
df_filtered = df_filtered.set_index(['Vendor'])
traces = []
df_by_col = df_filtered[[input_column, 'YearMonth']]
traces.append({
'x' :pd.Series(df_by_col['YearMonth']),
'y' : df_by_col[input_column],
'mode' : 'lines',
'type' : 'scatter',
'name' :'XYZ'}
)
fig = {
'data': traces,
'layout': {'title': 'Title of Chart'}
}
return fig
if __name__ == '__main__':
app.run_server(debug=False)
Thanks in advance for helping! Still new-ish to Python, but very excited about Dash’s capabilities. I’ve been able to create other graphs with single inputs, and have read through documentation.
Here is the approach I followed: (editing common example available in google with my approach):
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash(__name__)
all_options = {
'America': ['New York City', 'San Francisco', 'Cincinnati'],
'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
dcc.Dropdown(
id='countries-dropdown',
options=[{'label': k, 'value': k} for k in all_options.keys()],
value='America', #default value to show
multi=True,
searchable=False
),
dcc.Dropdown(id='cities-dropdown', multi=True, searchable=False, placeholder="Select a city"),
html.Div(id='display-selected-values')
])
#app.callback(
dash.dependencies.Output('cities-dropdown', 'options'),
[dash.dependencies.Input('countries-dropdown', 'value')])
def set_cities_options(selected_country):
if type(selected_country) == 'str':
return [{'label': i, 'value': i} for i in all_options[selected_country]]
else:
return [{'label': i, 'value': i} for country in selected_country for i in all_options[country]]
if __name__ == '__main__':
app.run_server(debug=True)
Workaround here is: When there is single input present in parent dropdown, the value is in string format. But for multiple values, it comes in list format.
This code also work perfectly and gets updated automatically even when you click on cross option to remove any selected option.
Note: I have used 'placeholder' attribute instead of defining default value for it as it made no sense in this case. But you can also update the value dynamically in similar way.
1 input data
The data as it is in the csv is hard to loop.
And I would argue that it is the main reason your code does not work,
because you seem to understand the fundamental code structure.
Having put on my SQL glasses I think you should try to get it to sth like
Date, Vendor, ProductName, Value
2 callback input types change
multi is tricky because it changes switches between returning a str if only 1 item is selected and list if more than one is selected
3 callback return type
you code returns a dict but the callback declared figure as the return type
but here is the code with debugging traces of print() and sleep()
import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import time
df = pd.read_csv("Data.csv", sep="\t")
df['YearMonth'] = pd.to_datetime(df['YearMonth'], format='%Y-%m')
products = ['Product_1', 'Product_2', 'Product_3']
vendors = df['Vendor'].unique()
app = dash.Dash('Data')
app.layout = html.Div([
html.Div([
html.Div([
html.Label('Product'),
dcc.Dropdown(
id='product',
options=[{'label' : p, 'value' : p} for p in products],
multi=True,
value='Product_1'
),
]),
html.Div([
html.Label('Vendor'),
dcc.Dropdown(
id='vendor',
options=[{'label': v, 'value': v} for v in vendors],
multi=True,
value='ABC'
),
]),
]),
dcc.Graph(id='feature-graphic', figure=go.Figure())
])
#app.callback(
Output('feature-graphic', 'figure'),
[Input('product', 'value'),
Input('vendor', 'value')])
def update_graph(input_product, input_vendor):
# df_filtered[['Product_1', 'YearMonth']]
if type(input_product) == str:
input_product = [input_product]
if type(input_vendor) == str:
input_vendor= [input_vendor]
datasets = ['']
i = 1
for vendor in input_vendor:
df_filtered = df[df['Vendor'] == vendor]
for product in input_product:
datasets.append((df_filtered[['YearMonth', 'Vendor', product]]).copy())
datasets[i]['ProductName'] = product
datasets[i].rename(columns={product: 'Value'}, inplace=True)
i += 1
datasets.pop(0)
print(datasets)
traces = ['']
for dataset in datasets:
print(dataset)
time.sleep(1)
traces.append(
go.Scatter({
'x': dataset['YearMonth'],
'y': dataset['Value'],
'mode': 'lines',
'name': f"Vendor: {dataset['Vendor'].iloc[0]} Product: {dataset['ProductName'].iloc[0]}"
}))
traces.pop(0)
layout = {'title': 'Title of Chart'}
fig = {'data': traces, 'layout': go.Layout(layout)}
return go.Figure(fig)
if __name__ == '__main__':
app.run_server()
quick and dirty disclosure:
If you handle the 1. issue it will dramatically simplify everything.
So I'd try to isolate the pd.DataFrame() juggling out of the callback and into the upper I/O part.
1) don't use counters in for loops
2) my variable names aren't the best either
3) the following style is caveman's python and there must be a better way:
traces = ['']
traces.append(this_and_that)
traces.pop(0)
Generally:
using print(input_variable) and print(type(input_variable))
gets my wheels most of the time out of the mud.
after all
you should notice that each trace got its individual name which will show up in the legend. Clicking on the name in the legend will add or remove the trace without the need for#app.callback()