Strange bug downloading data using app.server.route: filename not updating - plotly-dash

This is a very strange bug with plotly dash: downloaded files are not updated even when the source code is changed. I am using Mac OS X, and find the bug when using chrome or firefox but it works correctly using safari. The problem is that the downloaded file does not change after the code is updated.
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np
import flask
import io
from dash.dependencies import Input, Output, State
# import plotly.graph_objs as go
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Dropdown(
id='dropdown',
options=[
{'label': 'New York City', 'value': 'NYC'},
{'label': 'Montreal', 'value': 'MTL'},
{'label': 'San Francisco', 'value': 'SF'}
],
value='NYC'
),
html.A('Download',
id='download',
href=''),
# dcc.Store(id='data_store',storage_type='memory'),
])
# Calculate the data and store it.
#app.callback(
Output('download', 'href'),
[Input('dropdown', 'value')])
def update(value):
return '/download_csv/get_it?value={}'.format(value)
#app.server.route('/download_csv/get_it')
def download_excel():
param = flask.request.args
print(param)
# Dummy dataframe for downloading.
d = {'col1': param['value'],'col2': 'Did it change?'}
df = pd.DataFrame(data=d,index=[0])
#Convert DF
str_io = io.StringIO()
df.to_csv(str_io, sep=",")
mem = io.BytesIO()
mem.write(str_io.getvalue().encode('utf-8'))
mem.seek(0)
str_io.close()
return flask.send_file(mem,
mimetype='text/csv',
attachment_filename='downloadFile.csv',
as_attachment=True)
if __name__ == '__main__':
app.run_server(debug=True)
Running this file, we are able to download a csv. However, when using chrome, if the attachment_filename is changed to 'donwloadFile_new.csv' and the download button is pressed again, I still get 'donwloadFile.csv' as the downloaded file! Also, the contents of the downloaded file don't change either.
However, in safari the download function works as expected: file names and contents are updated.

I just answered a similar question here.
you need to specify the cache_timeout parameter in send_file function.

Related

dash dcc.Uploader not working in multipage app

0
I have a multi-page dash app for generating custom dashboards based on user submission. I'm trying to create a secondary page for uploading files. The problem is that the dcc.Upload or the callback function does not produce the expected behavior, that is: a html.Div that shows the uploaded data.
This is my secondary page, organized in tabs:
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
from dash import dash_table
from dash.exceptions import PreventUpdate
import os
import unicodedata
import requests
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.parse import quote as urlquote
import sqlite3
from dash_iconify import DashIconify
from dash.dependencies import Input, Output, State
import pandas as pd
from app import app
def layout():
return [
dmc.Tabs(
[
dmc.TabsList(
[
dmc.Tab("Import", value="import"),
dmc.Tab("Connect BD", value="connectbd"),
dmc.Tab("Explore", value="explore"),
]
),
dmc.TabsPanel(
html.Div([
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '260px',
'lineHeight': '60px',
'borderWidth': '1px',
#'borderStyle': 'dashed',
'background' : '#f8f9fa',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
# Allow multiple files to be uploaded
multiple=True,
),
html.Div(id='output-data-upload'),
]),
value="import"),
dmc.TabsPanel("Settings tab content", value="connectbd"),
dmc.TabsPanel("Settings tab content", value="explore"),
],
#color="black",
orientation="horizontal",
)
]
def parse_contents(contents, filename, date):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return html.Div([
html.H5(filename),
html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
df.to_dict('records'),
[{'name': i, 'id': i} for i in df.columns]
),
html.Hr(), # horizontal line
# For debugging, display the raw contents provided by the web browser
html.Div('Raw Content'),
html.Pre(contents[0:200] + '...', style={
'whiteSpace': 'pre-wrap',
'wordBreak': 'break-all'
})
])
#app.callback(Output('output-data-upload', 'children'),
Input('upload-data', 'contents'),
State('upload-data', 'filename'),
State('upload-data', 'last_modified'))
def update_output(list_of_contents, list_of_names, list_of_dates):
if list_of_contents is not None:
children = [
parse_contents(c, n, d) for c, n, d in
zip(list_of_contents, list_of_names, list_of_dates)]
return children
Solution attempts: the example of the dcc.Upload is in the documentation , but I use it inside a tab inside a secondary page that is called with the layout function in the app.py. The example is working correctly when is run as it is implemented in the documentation, but I can´t adapt it to my specific use case. I think the problem is near the callback or in the function that outputs the table.
I´m a newbie in dash. Thanks in advance for any help.
Expted: html.Div with uploaded data.

How to extend ‘text’ field in Graph.ExtendData with Plotly-Dash?

I have an animated graph that I update in a clientside callback. However, I want to update the text as well as the x and y values of the traces in Graph.extendData(), but it seems that that doesn't work. Is there something I'm missing? Alternatively, is there a different method I should be using instead?
Adopting the code from this post (Plotly/Dash display real time data in smooth animation), I'd like something like this, but where updating the text with extendData actually worked:
import dash
import dash_html_components as html
import dash_core_components as dcc
import numpy as np
from dash.dependencies import Input, Output, State
# Example data (a circle).
resolution = 1000
t = np.linspace(0, np.pi * 2, resolution)
x, y = np.cos(t), np.sin(t)
text = str(t)
# Example app.
figure = dict(data=[{'x': [], 'y': []}], text = [], layout=dict(xaxis=dict(range=[-1, 1]), yaxis=dict(range=[-1, 1])))
app = dash.Dash(__name__, update_title=None) # remove "Updating..." from title
app.layout = html.Div([
dcc.Graph(id='graph', figure=dict(figure)), dcc.Interval(id="interval", interval=25),
dcc.Store(id='offset', data=0), dcc.Store(id='store', data=dict(x=x, y=y, text=text, resolution=resolution)),
])
# This makes the graph fail to draw instead of just extending the text as wel!
app.clientside_callback(
"""
function (n_intervals, data, offset) {
offset = offset % data.x.length;
const end = Math.min((offset + 10), data.x.length);
return [[{x: [data.x.slice(offset, end)], y: [data.y.slice(offset, end)], text: [data.text.slice(offset, end)]}, [0], 500], end]
}
""",
[Output('graph', 'extendData'), Output('offset', 'data')],
[Input('interval', 'n_intervals')], [State('store', 'data'), State('offset', 'data')]
)
if __name__ == '__main__':
app.run_server()
Alternatively, is there a different method I should be using instead?

Dash Plotly: Highlighting point on the graph

I was googl'ing around trying to find solution for the following question (no luck so far). I am using Plotly Dash callback in order to build a graph:
#app.callback(
Output("graph", "figure"),
[Input("some-input", "value")],
[State("some-state", "value")])
def build_graph(input_value, state_value):
// computing data for graph_figure
return graph_figure
Now, I want to have another callback which will highlight a specific point on the graph (or add/remove a point) based on some input condition. I struggle to figure out what to use for Output in this case? Because I cannot output graph.figure again (Dash does not allow output to the same component from different callbacks). And re-drawing entire graph seems to be inefficient.
I will appreciate any suggestions.
It is possible to change the layout of your graph without redrawing the whole graph by using this library: https://github.com/jimmybow/mydcc
There is an example how to use it here: https://github.com/jimmybow/mydcc#3-mydccrelayout-
I have prepared a small example that adds an annotation on button click. Don't forget to pip install mydcc beforehand. I had to add a cache - in form of an invisible div - to preserve the old annotations when adding a new one.
import dash
import dash_core_components as dcc
import dash_html_components as html
import mydcc
import random
import json
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
initial_layout = {'title': 'Dash Data Visualization'}
app.layout = html.Div(children=[
html.H1(children='Hello Dash'),
html.Div(children='''
Dash: A web application framework for Python.
'''),
dcc.Graph(
id='example-graph',
figure={
'data': [{'x': [1, 2, 3], 'y': [4, 1, 2], 'name': 'Test'}],
'layout': initial_layout
}
),
mydcc.Relayout(id="rrr", aim='example-graph'),
html.Button('Add random annotation', id='button'),
html.Div(id='user-cache', style={'display': 'none'},
children=json.dumps(initial_layout)),
])
#app.callback(
[dash.dependencies.Output('rrr', 'layout'),
dash.dependencies.Output('user-cache', 'children')],
[dash.dependencies.Input('button', 'n_clicks')],
[dash.dependencies.State('user-cache', 'children')])
def update_graph_annotations(n_clicks, layout):
if n_clicks is not None:
layout = json.loads(layout)
if not 'annotations' in layout:
layout['annotations'] = []
layout['annotations'].append(dict(
x=random.uniform(0, 1) * 2 + 1,
y=random.uniform(0, 1) * 2 + 1,
xref="x",
yref="y",
text="Annotation" + str(n_clicks),
showarrow=True
))
return layout, json.dumps(layout)
return dash.no_update, dash.no_update
if __name__ == '__main__':
app.run_server(debug=True)

How Dynamically populate the DropDown from the data uploaded through Plotly Dash dcc upload option

I have uploaded the data using Plotly Dash dcc upload option. I am not able to dynamically populated the unique columns of uploaded dataframe as part of DropDown
I am able to successfuly upload the file using dcc upload option; i am able to see the output in dash table. but using same input, i am not able to see the output from dropdown.
the dataframe, i have uploaded with the columns of (country, continent, population)
i should able to get the unique columns dropdown
this is my code:
import base64
import datetime
import io
import plotly.graph_objs as go
import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_table
import dash_table_experiments as dte
from dash.dependencies import Input, Output, State
import pandas as pd
app = dash.Dash()
app.layout = html.Div([
html.Div([
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
multiple=True
),
html.Div(id='output-data-upload'),
])
dcc.Tab(label='Discrete Info', children=[
html.Div([
dcc.Dropdown(
id='select_column',
),
]),
html.Div([
dcc.Graph(
id='bar1',
hoverData={'points': [{'customdata': ''}]},
)
], style={'width': '100%', 'display': 'inline-block', 'padding': '0 10'}),
]),
])
def parse_contents(contents, filename, date):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return html.Div([
html.H5(filename),
html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
data=df.to_dict('records'),
columns=[{'name': i, 'id': i} for i in df.columns]
),
html.Hr(), # horizontal line
# For debugging, display the raw contents provided by the web browser
html.Div('Raw Content'),
html.Pre(contents[0:200] + '...', style={
'whiteSpace': 'pre-wrap',
'wordBreak': 'break-all'
})
])
#app.callback(Output('output-data-upload', 'children'),
[Input('upload-data', 'contents')],
[State('upload-data', 'filename'),
State('upload-data', 'last_modified')])
def update_output (list_of_contents, list_of_names, list_of_dates):
if list_of_contents is not None:
children = [
parse_contents(c, n, d) for c, n, d in
zip(list_of_contents, list_of_names, list_of_dates)]
return children
#app.callback(Output('select_column', 'options'
[Input('output-data-upload', 'children'')]
def dd_update (options)
return(options)
This is a little tricky without specific code to reference, but the basic idea is to set up a callback that will update the options prop of your dcc.Dropdown. You can have that callback triggered (Input) by the upload, make it read in the dataframe, obtain the columns, and then output the properly formatted list of dictionaries.
Please use a following simple example that solves your problem
import base64
import io
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output
app = dash.Dash()
app.layout = html.Div([
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
),
dcc.Dropdown(
id='dropdown',
options=[],
value=None),
])
def parse_data(contents, filename):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV or TXT file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
elif 'txt' or 'tsv' in filename:
# Assume that the user upl, delimiter = r'\s+'oaded an excel file
df = pd.read_csv(
io.StringIO(decoded.decode('utf-8')), delimiter = r'\s+')
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return df
#app.callback(
Output('dropdown', 'options'),
[Input('upload-data', 'contents'),
Input('upload-data', 'filename')])
def update_options(contents, filename):
if contents:
df = parse_data(contents, filename)
df = df.set_index(df.columns[0])
lst = [{'label': i, 'value': i} for i in df.columns]
return lst
else:
return []
if __name__ == '__main__':
app.run_server(debug=True)
Here is my working code, very basic, that you can use as a reference
html.Div(children=[
html.Div(children=[
dcc.Dropdown(id='dropdown_students_cols', options=[{'label': i, 'value': i} for i in list(filter(lambda x: x!='userid', list(df)))], value='component'),
]),
html.Div(children=[
dcc.Dropdown(id='dropdown_row_names'),
]),
])
#app.callback(
Output(component_id='dropdown_row_names', component_property='options'),
[Input(component_id='dropdown_students_cols', component_property='value')],)
def get_row_names(column_name='component'):
row_names = df[column_name].unique().tolist()
lst = [{'label': i, 'value': i} for i in row_names]
print(lst)
return lst
The print statement in the callback returns a list of dictionaries that I can use for options:
[{'label': 'mod_glossary', 'value': 'mod_glossary'}, {'label': 'mod_quiz', 'value': 'mod_quiz'},...]
Don't pay attention to lambda function in the original dropdown id='dropdown_students_cols', I just needed to exclude this column from selection.
When you make selection in the original dropdown id='dropdown_students_cols', it invokes the callback function with the selection as an argument.
This selection gives me a required column in the df and then I build a list of unique rows in this column.
Then I iterate over this list to build a list of dictionaries with label:value
The print statement confirms that the list is built successfully
Then I return that list as options for the child dropdown id='dropdown_row_names'
So you can use another trigger as Input, in your case Upload, pass values into the callback, process the data as you need and then return a list if dictionaries as options for the dropdown.
I suggest you trim your code to a vary basic working example and then slowly build up from it.

putting HTML output from SHAP into the Dash output layout callback

I am trying to make a dashboard where the output from shap forceplot is illustrated. Shap.forceplot is HTML decorated with json. The example is here
I made a very simple dashboard using the tutorial which should plot the desirable figure after clicking the submit
here is the code
# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import pandas as pd
from sqlalchemy import create_engine
import shap
from sources import *
import xgboost
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Input(id='input-cvr-state', type='text', value='12'),
html.Button(id='submit-button', n_clicks=0, children='Submit'),
html.Div(id='output-state'),
html.Div(id='output-shap')
])
#app.callback(Output('output-shap', 'children'),
[Input('submit-button', 'n_clicks')],
[State('input-cvr-state', 'value')])
def update_shap_figure(n_clicks, input_cvr):
shap.initjs()
# train XGBoost model
X,y = shap.datasets.boston()
model = xgboost.train({"learning_rate": 0.01}, xgboost.DMatrix(X, label=y), 100)
# explain the model's predictions using SHAP values(same syntax works for LightGBM, CatBoost, and scikit-learn models)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X)
# visualize the first prediction's explanation
return(shap.force_plot(explainer.expected_value, shap_values[0,:], X.iloc[0,:])) # matplotlib=True
if __name__ == '__main__':
app.run_server(debug=True)
I managed it by following steps:
import shap
from shap.plots._force_matplotlib import draw_additive_plot
# ... class dashApp
# ... callback as method
# matplotlib=False => retrun addaptativevisualizer,
# if set to True the visualizer will render the result is the stdout directly
# x is index of wanted input
# class_1 is ma class to draw
force_plot = shap.force_plot(
self.explainer.expected_value[class_1],
self.shap_values[class_1][x[0], :],
self.data.iloc[x, :].drop(columns=["TARGET"], errors="ignore"),
matplotlib=False
)
# set show=False to force the figure to be returned
force_plot_mpl = draw_additive_plot(force_plot.data, (30, 7), show=False)
return figure_to_html_img(force_plot_mpl)
def figure_to_html_img(figure):
""" figure to html base64 png image """
try:
tmpfile = io.BytesIO()
figure.savefig(tmpfile, format='png')
encoded = base64.b64encode(tmpfile.getvalue()).decode('utf-8')
shap_html = html.Img(src=f"data:image/png;base64, {encoded}")
return shap_html
except AttributeError:
return ""
The result will be like it
An alternative is to use html.IFrame which will produce a better looking and fully interactive plot.
Here's an example that can be used directly as an Output
def _force_plot_html(*args):
force_plot = shap.force_plot(*args, matplotlib=False)
shap_html = f"<head>{shap.getjs()}</head><body>{force_plot.html()}</body>"
return html.Iframe(srcDoc=shap_html,
style={"width": "100%", "height": "200px", "border": 0})