update information cards with dash and csv - python

I have a problem, I create a dashboard with dash and python, the cards are fed with the information from a csv, when the information is updated in the file, the dashboard is not updated, if I go to the code and save changes in the code, the web is refreshed and at that moment if the card data is updated in the dashboard, I don't know how to do it.
dashboard
data.cant[4]
data.cant[4] is the variable that gets the data from the csv
#app.callback(
Output("page-content", "children"),
[Input("url", "pathname")]
)
def render_page_content(pathname):
if pathname == "/":
return [
html.A(html.Button('Refresh Page'),href='/'),
#html.H1('The time is: ' + str(datetime.datetime.now())),
html.Div([
#-----------------------
html.Div([
#---------------------
html.Div([
html.H6(children='New',
style={'textAlign': 'center',
'color': 'white',
'fontSize': 22,
'margin-top':'-5px'}),
html.H3("Vulnerabilities",
style={'textAlign': 'center',
'color': 'orange',
'fontSize': 20,
'margin-top':'-10px'}),
html.P(f"{data.cant[4]:,.0f}",
style={'textAlign': 'center',
'color': 'orange',
'fontSize': 60,
'margin-top':'-18px',
'margin-bottom':'-18px'}),
], className='tarjeta_dato two columns'),
], id= 'contenedorMain', style={'display': 'flex', 'flex-direction': 'column' })
]
status.csv
type,status,cant
vul,active,201
I need the information on the dashboard to be updated when the csv information changes
I found that a parameter should be used within the callback but no matter how much I tried or managed to make it work
dcc.Interval(
id='graph-update',
interval=1*1000
),
I understand that this is for graphics not information cards like the ones I have
I also read that you could use something like use_reloader as a parameter but it doesn't work, thanks for your attention

Related

How to register a separate page in python dash multipage app?

I am working on a multipage app and registered all the pages correctly (script 1 below).
I would now like to add a button on each page which goes to an Explanatory page, so this button should be excluded from the CSS inline-block styling. I just cant get the HREF of this page specific button to redirect to the correct page content. It does change to the /name but shows 404 not found content...
THIS IS THE SCRIPT OF THE MAIN PAGE MULTIPAGE APP
import dash
import os
app = Dash(__name__, use_pages=True)
app.layout = html.Div([
html.Img(src=r'EXAMPLE_LINK.png',
style={'height': '100px', 'width': '450px'}),
html.H3('TITLE OF THE DASHBOARD',
style={'color': '#005aa0', 'font-size': '20px', 'font-family': 'sans-serif'}),
html.Div(
[
html.Div(
dcc.Link(
html.Button(children=page['name'][:][7:8].upper() + page['name'][:][8:].lower(),
style={"width": "200px", "height": "40px", "marginLeft": "5%", "color": "#005aa0",
'border': '1.5px black solid', 'font-size': '15px'}), href=page["relative_path"]),
style={"display": "inline-block",
"width": "10%",
"margin-left": "12%",
"verticalAlign": "top"}
)
for page in dash.page_registry.values()
],
),
html.H1(children=LOGGED IN AS {os.getlogin()}',
style={'color': '#005aa0', 'font-size': '15px', 'font-family': 'sans-serif'}),
dash.page_container
])
if __name__ == '__main__':
app.run_server(debug=True)
AND THIS IS THE BUTTON WHICH I WANT TO IMPEMENT ON THE PAGES
html.Div([
dcc.Link(html.Button(children='?', id='p1_roll1', n_clicks=1,
style={"width": "150px", "height": "60px", "marginLeft": "0%",
"background-color": "#ffffff", 'color': '005aa0',
'font-size': '40px', "display": "inline-block"}), href='ExplanationPage.py'),
html.H1(children='TITLE EXAMPLE',
style={'text-align': 'center', 'color': '#005aa0', 'font-size': '60px', 'marginLeft': '33%',
'font-family': 'sans-serif', "display": "inline-block"})
]),
So my question is: How can i redirect the HREF= (in the pages link button: href='ExplanationPage.py') to a python script which shouldnt be among the ones registered in the page['name'] inblock styling.

A nonexistent object was used in an `Input` of a Dash callback an multipage app

I am trying to build a basic webpage in Dash and Plotly, using callbacks to deliver stock graphs based on the pathname.
I would also like the graph to have a callback with radio buttons that specify the time period of data to display.
The graphs work fine however I would also like other pages to do other things and return different html but because I have specified the callback_layout in the #app.layout, it shows up on every page.
However, If I remove the callback_layout from #app.layout, I get an error in Dash saying "A nonexistent object was used in an Input of a Dash callback an multipage app".
How would I solve this? I want the callback radio buttons under 'stock-period' to only show up when I need to pull up the Plotly graph
import dash
import dash_bootstrap_components as dbc
from dash import html
from dash import dcc
import plotly.express as px
from dash.dependencies import Input, Output, State
import pandas as pd
import yfinance as yf
external_stylesheets = [dbc.themes.CYBORG] #DARKLY
app = dash.Dash(__name__, external_stylesheets=external_stylesheets, suppress_callback_exceptions=True)
# styling the sidebar
SIDEBAR_STYLE = {
"position": "fixed",
"top": 0,
"left": 0,
"bottom": 0,
"width": "16rem",
"padding": "2rem 1rem",
# "background-color": "#4C4C4C",
}
# padding for the page content
CONTENT_STYLE = {
"margin-left": "18rem",
"margin-right": "2rem",
"padding": "2rem 1rem",
"background-color": "#060606"
}
sidebar = html.Div(
[
html.H2("Sidebard / Stocks", className="display-4"),
html.Hr(),
html.P(
"Number of students per education level", className="lead"
),
dbc.Nav(
[
dbc.NavLink("Home", href="/", active="exact"),
dbc.NavLink("Test", href="/test", active="exact"),
dbc.NavLink("NVDA", href="/NVDA", active="exact"),
dbc.NavLink("MSFT", href="/MSFT", active="exact"),
dbc.NavLink("AAPL", href="/AAPL", active="exact"),
],
vertical=True,
pills=True,
),
],
style=SIDEBAR_STYLE,
)
content = html.Div(id="page-content", children=[], style=CONTENT_STYLE) # Graph goes inside children via call back
callback_layout = html.Div([
dcc.RadioItems(
id='period-selector',
options = [
{'label': '1 mth', 'value': '1mo'},
{'label': '3 mth', 'value': '3mo'},
{'label': '6 mth', 'value': '6mo'},
{'label': '1 yr', 'value': '1y'},
{'label': '5 yr', 'value': '5y'}
],
value = "6mo",
#labelStyle is done in CSS
labelStyle={
'textAlign': 'center',
'display': 'inline-block',
'color': 'white',
'font-family': 'Arial',
'font-size' : '15px',
'padding-right': '30px'
},
style={
'textAlign': 'center'
}
)
])
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
sidebar,
content,
callback_layout
])
homepage_layout = html.Div([
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
])
""" Plotly graph """
#app.callback(
Output("page-content", "children"),
Input("url", "pathname"),
Input('period-selector', 'value')
)
def render_page_content(pathname, period_selector):
""" App routing from sidebar to content depending on path """
if pathname == '/':
return homepage_layout
if pathname == '/test':
return 'Test'
else: # If pathname isn't defined above, assume is stock ticker and calls graph below
interval = '1d'
quote = yf.Ticker(pathname)
hist = quote.history(period_selector, interval)
df = hist.round(decimals=2)
last_price = df.iloc[-1, 0] # return first row (-1)
#Performance over period calc
performance_calc = ((df.iloc[-1, 0] / df.iloc[0, 0]) - 1) * 100
if performance_calc >= 0:
performance = '+' + str(round(performance_calc,2))
else:
performance = str(round(performance_calc,2))
#Define color of performance %
performance_int = (round(performance_calc,2))
if performance_int >= 0:
perf_color = 'lime'
else:
perf_color = 'red'
fig = px.line(df,
x=df.index, y=df["Close"],
title='NVDA',
template="plotly_dark",
color_discrete_sequence=['lime'],
labels = {'Date': ''}
)
if interval == '1mo':
d_tick='604800000' #7 days in milliseconds. Datetime format requires ms input.
elif interval == '5y':
d_tick = 'M12'
else:
d_tick='M1'
fig.update_xaxes(
dtick=d_tick,
showgrid=False,
)
fig.update_yaxes(
showgrid=False,
visible=False
)
#Last price annotation
fig.add_annotation(dict(font=dict(color='white',size=40, family='Arial Black'),
x=0,
y=1.2,
showarrow=False,
text=str(last_price),
textangle=0,
xanchor='left',
xref="paper",
yref="paper")
)
#Performance annotation
fig.add_annotation(dict(font=dict(color=perf_color,size=25, family='Arial'),
x=0.14,
y=1.12,
showarrow=False,
text=str(performance) + '%',
textangle=0,
xanchor='left',
xref="paper",
yref="paper")
)
fig.update_traces(hovertemplate=None)
fig.update_layout(
plot_bgcolor='#060606',
paper_bgcolor='#060606',
font_color='white',
title=dict(
y = 0.82, #Adjust location of stock ticker title
x = 0.92,
xanchor = 'center',
yanchor = 'top',
font=dict(
family="Arial",
size=25,
color='white'
)
),
hovermode = 'x',
# xaxis_tickformat = '%b'
)
# Return below into children tag above
return [
html.H1(pathname,
style={'textAlign':'center'}),
dcc.Graph(id='stock-chart',
figure = fig
)
]
if __name__=='__main__':
app.run_server(debug=True, port=3000)
To get the desired behavior you can just hide the chart radio buttons for all endpoints that do not build the stock price graph. To do this add the style attribute of the period-selector element as an output to your main callback as shown below.
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
sidebar,
content,
callback_layout # Leave this here, do not move.
])
#app.callback(
Output("page-content", "children"),
Output("period-selector", "style"), # Add new output here
Input("url", "pathname"),
Input('period-selector', 'value')
)
def render_page_content(pathname, period_selector):
""" App routing from sidebar to content depending on path """
hidden_style = {'display': 'none'}
# It would be best to make visible_style a module level variable.
# Use it for both the app layout and this callback function.
# That way if you want to change its location or look you only have to edit one location.
visible_style = {'textAlign': 'center'}
if pathname == '/':
return homepage_layout, hidden_style
if pathname == '/test':
return 'Test', hidden_style
else:
# Your other build code here.
return [
html.H1(pathname,
style={'textAlign': 'center'}),
dcc.Graph(id='stock-chart',
figure=fig
)
], visible_style
The MultiplexerTransform from dash-extensions was designed to solve this problem. To enable it, replace you imports from Dash with their equivalents from dash-extensions.enrich,
from dash import Dash, Input, Output, html, ... # before
from dash_extensions.enrich import Dash, Input, Output, html, ... # after
and the error should disappear. You can read more about the details in the documentation.

Dash range slider with input on each side

I am quite new to front-end development and HTML and I'm struggling to understand how to order a range slider with two inputs in the same line when using Dash
I have tried to separate the html.Div, Putting the components in the same Div without separating them
adjust the different parameters and some morebut I still can't get it to appear the way I want
what I want:
what I have:
My code (that reproduce what I have):
import dash
import dash_core_components as dcc
import dash_html_components as html
app.layout = html.Div([
html.Div([
html.Div([dcc.Input(
id='slider-min-value',
placeholder=str(min_value))],
style={'width': '10%'}
),
html.Div([dcc.RangeSlider(
id='condition-range-slider',
min=0,
max=30,
value=[10, 15],
allowCross=False)],
style={'width': '25%'}
),
html.Div([dcc.Input(
id='slider-max-value',
placeholder=str(max_value))],
style={'width': '10%'}
),
],
style={'display': 'inline-block'})])
if __name__ == '__main__':
app.run_server(debug=True)
what do I need to do in order to get the rangeslider and the inputs to appear the way I want?
Ok, using {"display": "grid", "grid-template-columns": "10% 40% 10%"} gave me what I wanted.
layout:
app.layout = html.Div(
html.Div(
[
dcc.Input(type='text', value=min_value),
dcc.RangeSlider(
id='condition-range-slider',
min=0,
max=30,
value=[10, 15],
allowCross=False
),
dcc.Input(type='text', value=max_value)
],
style={"display": "grid", "grid-template-columns": "10% 40% 10%"}),
style={'width': '20%'}
)
what I got:
You can try updating the html.Div style with 'float': 'left',
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash(__name__)
app.layout = html.Div(
[
html.Div(
style={'width':'10%', 'float':'left', 'marginLeft': 20, 'marginRight': 20},
children=[
dcc.Input(id='slider-min-value')
]
),
html.Div(
style={'width':'50%', 'float':'left','marginLeft': 20, 'marginRight': 20},
children=[
dcc.RangeSlider(
id='condition-range-slider',
min=0,
max=30,
value=[10, 15],
allowCross=False
)
]
),
html.Div(
style={'width':'10%', 'float':'left','marginLeft': 20, 'marginRight': 20},
children=[
dcc.Input(id='slider-max-value')
]
),
])
if __name__ == '__main__':
app.run_server(debug=True)

Dash: Creating a dropdown per column, instead of a dropdown per table

I have a data set that looks like this:
cat_id author year publisher country value (dollars)
name1 kunga 1998 D and D Australia 10
name2 siba 2001 D and D UK 20
name3 siba 2001 D and D US 20
name3 shevara 2001 D and D UK 10
name3 dougherty 1992 D and D Australia 20
name4 ken 2011 S and K Australia 10
The aim, to produce a Multi-Select Dropdown menu PER COLUMN, not of all columns (because the per-column filter that i currently do is not sufficient, I need to be able to filter by multiple items per column in one go).
Using the above example, this would add a cell just under cat_id with name1,2,3,4; author would have a dropdown with kunga, siba, shevara and dougherty; year would have a dropdown with 1992,1998,2001,2011 etc.
I wrote the below code:
import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
app = dash.Dash(__name__)
df = pd.read_excel('dash_test_doc.xlsx')
app.layout = html.Div([
html.Div([
dcc.Input(
id='adding-rows-name',
placeholder='Enter a column name...',
value='',
style={'padding': 10},),html.Button('Add Column', id='adding-rows-button', n_clicks=0)], style={'height': 50}),
#Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns],
value = [i for i in df.columns],
multi=True),
dash_table.DataTable(
id='adding-rows-table',
columns=[{"name": i, "id": i,"deletable":True} for i in df.columns],
# column_conditional_dropdowns=[{'id':i,'dropdowns':df[i]} for i in df.columns],
data = df.to_dict('rows'),
editable=True,
filtering=True,
sorting=True,
sorting_type="multi",
row_selectable="multi",
row_deletable=True,
selected_rows=[],
pagination_mode="fe",
style_cell_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(230, 255, 230)'
}
] + [
{
'if': {'column_id': c},
'textAlign': 'left'
} for c in ['Date', 'Region']
],
style_header={
'backgroundColor': 'white',
'fontWeight': 'bold'
}
),
html.Button('Add Row', id='editing-rows-button', n_clicks=0),
dcc.Graph(id='adding-rows-graph')
])
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, rows, columns):
if n_clicks > 0:
rows.append({c['id']: '' for c in columns})
return rows
#app.callback(
Output('adding-rows-table', 'columns'),
[Input('adding-rows-button', 'n_clicks')],
[State('adding-rows-name', 'value'),
State('adding-rows-table', 'columns')])
def update_columns(n_clicks, value, existing_columns):
if n_clicks > 0:
existing_columns.append({
'id': value, 'name': value,
'editable_name': True, 'deletable': True
})
return existing_columns
#app.callback(
Output('adding-rows-graph', 'figure'),
[Input('adding-rows-table', 'data'),
Input('adding-rows-table', 'columns')])
def display_output(rows, columns):
return {
'data': [{
'type': 'heatmap',
'z': [[row.get(c['id'], None) for c in columns] for row in rows],
'x': [c['name'] for c in columns]
}]
}
if __name__ == '__main__':
app.run_server(debug=True)
There are two specific issues with this method:
The dropdown is being done at the table level, not the per-column level.
I want to then filter the table to only display what is in the dropdown bar, which is currently not being done.
I know that the parts of the code that are the issue are:
#Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(options=[{'label':i,'value':i} for i in df.columns],
value = [i for i in df.columns],
multi=True),
and that there is no callback, but I'm new to dash, and I'm struggling to understand what specifically I should do. If someone could show me how to fix these two issues in this code, I would appreciate it.
As a side note/disclaimer, I had originally asked this question as part of a much bigger question here, but I have realised that I need to make my questions more code-specific, and more specific in general, so I'm going to work through each issue individually.
Thanks, Kela, for making this question a little more specific. This still a good size bite, so I'll see if I can help with all of it.
First thing is you need to change the column defintion in the table to have 'presentation': 'dropdown' in the dictionary for each column you want to show up as a dropdown. The current dropdown isn't working inside the table because you've used the dcc.Dropdown component, which is a separate component from the table. You'll need to use column_static_dropdown or column_conditional_dropdowns (which I see you've commented out) if you want them to work inside the table.
I'm not sure that the dropdowns inside the table can be used as part of a callback, though. I tried to hook one up, and the ID of the dropdown was not recognized as part of the layout. To do what you want would - I think - require setting up multiple dropdowns using dcc.Dropdown and hooking those values up as input to the callback that updates the table's data prop.
Here is a little example that I've confirmed works, with some limitations:
app = dash.Dash(__name__)
df = pd.DataFrame(np.arange(30).reshape(5, 6))
app.layout = html.Div([
html.Div([
dcc.Input(
id='adding-rows-name',
placeholder='Enter a column name...',
value='',
style={'padding': 10}, ),
html.Button('Add Column', id='adding-rows-button', n_clicks=0)], style={'height': 50}),
# Need to move this to being within each column?
html.Label('Multi-Select Dropdown'),
dcc.Dropdown(
id='test-dropdown',
options=[{'label': i, 'value': i} for i in df[0]],
value=[i for i in df[0]],
multi=True),
dash_table.DataTable(
id='adding-rows-table',
columns=[{"name": i, "id": i, "deletable": True} for i in df.columns],
data=df.to_dict('rows'),
editable=True,
filtering=True,
sorting=True,
sorting_type="multi",
row_selectable="multi",
row_deletable=True,
selected_rows=[],
pagination_mode="fe",
style_cell_conditional=[
{
'if': {'row_index': 'odd'},
'backgroundColor': 'rgb(230, 255, 230)'
}
] + [
{
'if': {'column_id': c},
'textAlign': 'left'
} for c in ['Date', 'Region']
],
style_header={
'backgroundColor': 'white',
'fontWeight': 'bold'
}
),
html.Button('Add Row', id='editing-rows-button', n_clicks=0),
dcc.Graph(id='adding-rows-graph'),
])
#app.callback(
Output('adding-rows-table', 'data'),
[Input('editing-rows-button', 'n_clicks'),
Input('test-dropdown', 'value')],
[State('adding-rows-table', 'data'),
State('adding-rows-table', 'columns')])
def add_row(n_clicks, dropdown_value, rows, columns):
if n_clicks > 0:
rows.append({c['id']: '' for c in columns})
df = pd.DataFrame.from_dict(rows)
try:
df_filtered = df[df['0'].isin(dropdown_value)]
except TypeError:
df_filtered = df[df['0'].eq(dropdown_value)]
return df_filtered.to_dict(orient='records')
There is only one dropdown right now, and it works with the values in the first column. You would need to add more, and it would get complicated quickly. You could probably style the dropdowns so they're more clearly associated with the related columns .
There is also the problem of what to do with rows once you've filtered them out. The callback pulls in the State data from the table but, if a row was filtered out, then it's not in the state and can't be added back in by making that selection in the dropdown again. You would need a different way to handle storing the data for the table so that rows aren't erased this way.

Dash - Dynamic layout does not propagate resized graph dimensions until window is resized

In the sample Dash application below, I am attempting to create a dynamic layout with a variable number of rows and columns. This dynamic grid-style layout will be populated with various graphs that can be modified by dropdowns, etc.
The main issue I have run into thus far pertains to viewport-units and attempting to style the individual graphs appropriately to accommodate the dynamic layout. For example, I am modifying the style of the dcc.Graph() components via viewport-units, where the dimensions (e.g. height and width may be either 35vw or 23vw depending on the number of columns). When I change the number of columns from 3 to 2, for example, the height and width of the dcc.Graph() component are clearly changed, however this change is not reflected in the actual rendered layout until the window is physically resized (see the images below the sample code).
How do I force the dcc.Graph() components to propagate these changes without having to resize the window?
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config.suppress_callback_exceptions = True
app.layout = html.Div([
html.Div(className='row', children=[
html.Div(className='two columns', style={'margin-top': '2%'}, children=[
html.Div(className='row', style={'margin-top': 30}, children=[
html.Div(className='six columns', children=[
html.H6('Rows'),
dcc.Dropdown(
id='rows',
options=[{
'label': i,
'value': i
} for i in [1,2,3,4]],
placeholder='Select number of rows...',
clearable=False,
value=2
),
]),
html.Div(className='six columns', children=[
html.H6('Columns'),
dcc.Dropdown(
id='columns',
options=[{
'label': i,
'value': i
} for i in [1,2,3]],
placeholder='Select number of columns...',
clearable=False,
value=3
),
])
]),
]),
html.Div(className='ten columns', id='layout-div', style={'border-style': 'solid', 'border-color': 'gray'}, children=[])
])
])
#app.callback(
Output('layout-div', 'children'),
[Input('rows', 'value'),
Input('columns', 'value')])
def configure_layout(rows, cols):
mapping = {1: 'twelve columns', 2: 'six columns', 3: 'four columns', 4: 'three columns'}
sizing = {1: '40vw', 2: '35vw', 3: '23vw'}
layout = [html.Div(className='row', children=[
html.Div(className=mapping[cols], children=[
dcc.Graph(
id='test{}'.format(i+1+j*cols),
config={'displayModeBar': False},
style={'width': sizing[cols], 'height': sizing[cols]}
),
]) for i in range(cols)
]) for j in range(rows)]
return layout
#Max layout is 3 X 4
for k in range(1,13):
#app.callback(
[Output('test{}'.format(k), 'figure'),
Output('test{}'.format(k), 'style')],
[Input('columns', 'value')])
def create_graph(cols):
sizing = {1: '40vw', 2: '35vw', 3: '23vw'}
style = {
'width': sizing[cols],
'height': sizing[cols],
}
fig = {'data': [], 'layout': {}}
return [fig, style]
if __name__ == '__main__':
app.server.run()
Relevant screenshots (Image 1 - page load, Image 2 - change columns to 2):
Here is how to proceed:
app.py must import:
from dash.dependencies import Input, Output, State, ClientsideFunction
let’s include the below Div somewhere in the Dash layout:
html.Div(id="output-clientside"),
asset folder must include either your own script, or the default script resizing_script.js, which contains:
if (!window.dash_clientside) {
window.dash_clientside = {};
}
window.dash_clientside.clientside = {
resize: function(value) {
console.log("resizing..."); // for testing
setTimeout(function() {
window.dispatchEvent(new Event("resize"));
console.log("fired resize");
}, 500);
return null;
},
};
Among your callbacks, put this one, without #:
app.clientside_callback(
ClientsideFunction(namespace="clientside", function_name="resize"),
Output("output-clientside", "children"),
[Input("yourGraph_ID", "figure")],
)
At this point, when you manually resize the window, in your browser, the resize function is triggered.
We aim to achieve the same result, but without manual window resizing. For instance, the trigger could be a className update.
So, we apply the following changes:
Step 1: unchanged
Step 2: unchanged
Step 3: let’s add a “resize2” function inside our javascript file, which takes 2 arguments:
if (!window.dash_clientside) {
window.dash_clientside = {};
}
window.dash_clientside.clientside = {
resize: function(value) {
console.log("resizing..."); // for testing
setTimeout(function() {
window.dispatchEvent(new Event("resize"));
console.log("fired resize");
}, 500);
return null;
},
resize2: function(value1, value2) {
console.log("resizingV2..."); // for testing
setTimeout(function() {
window.dispatchEvent(new Event("resize"));
console.log("fired resizeV2");
}, 500);
return value2; // for testing
}
};
Function “resize2” now takes 2 arguments, one for each Input defined in the below callback. It will return the value of “value2” in the Output, specified in this very same callback. You can set it back to “null”, it’s just to illustrate.
Step4: our callback now becomes:
app.clientside_callback(
ClientsideFunction(namespace="clientside", function_name="resize2"),
Output("output-clientside", "children"),
[Input("yourGraph_ID", "figure"), Input("yourDivContainingYourGraph_ID", "className")],
)
Finally, you need a button to trigger the event which will change the className of your container.
let’s say your have:
daq.ToggleSwitch(
id='switchClassName',
label={
'label':['Option1', 'Option2'],
},
value=False,
),
And the following callback:
#app.callback(Output("yourDivContainingYourGraph_ID", "className"),
[Input("switchClassName","value")]
)
def updateClassName(value):
if value==False:
return "twelve columns"
else:
return "nine columns"
Now, if you save everything, refresh, everytime you press on your toggleSwitch,it resizes the container, triggers the function, and refreshes the figure.
Given the way it’s done, I assume it must also be possible to run more Javascript functions, the same way, but I didnt check yet.
Hope it will help some
The behavior looks like a Plotly bug to me.
Here is a possible workaround/short-termin solution.
There is a nice library visdcc which allows callbacks with Javascript. You can install it via
pip install visdcc
Add it to your div:
visdcc.Run_js(id='javascript'),
and add a callback
#app.callback(
Output('javascript', 'run'),
[Input('rows', 'value'),
Input('columns', 'value')])
def resize(_, __):
return "console.log('resize'); window.dispatchEvent(new Event('resize'));"
Plotly will throw an error in the console after the resize event (this also happens when the windows is manually resized) but the plots are shown correctly.
Full code
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import visdcc
SIZING = {1: '40vw', 2: '35vw', 3: '23vw'}
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config.suppress_callback_exceptions = True
app.layout = html.Div([
visdcc.Run_js(id='javascript'),
html.Div(className='row', children=[
html.Div(className='two columns', style={'margin-top': '2%'}, children=[
html.Div(className='row', style={'margin-top': 30}, children=[
html.Div(className='six columns', children=[
html.H6('Rows'),
dcc.Dropdown(
id='rows',
options=[{
'label': i,
'value': i
} for i in [1,2,3,4]],
placeholder='Select number of rows...',
clearable=False,
value=2
),
]),
html.Div(className='six columns', children=[
html.H6('Columns'),
dcc.Dropdown(
id='columns',
options=[{
'label': i,
'value': i
} for i in [1,2,3]],
placeholder='Select number of columns...',
clearable=False,
value=3
),
])
]),
]),
html.Div(className='ten columns', id='layout-div', style={'border-style': 'solid', 'border-color': 'gray'}, children=[])
])
])
#app.callback(
Output('layout-div', 'children'),
[Input('rows', 'value'),
Input('columns', 'value')])
def configure_layout(rows, cols):
mapping = {1: 'twelve columns', 2: 'six columns', 3: 'four columns', 4: 'three columns'}
layout = [html.Div(className='row', children=[
html.Div(className=mapping[cols], style={'width': SIZING[cols], 'height': SIZING[cols]}, children=[
dcc.Graph(
id='test{}'.format(i+1+j*cols),
config={'displayModeBar': False},
style={'width': SIZING[cols], 'height': SIZING[cols]}
),
]) for i in range(cols)
]) for j in range(rows)]
return layout
#app.callback(
Output('javascript', 'run'),
[Input('rows', 'value'),
Input('columns', 'value')])
def resize(_, __):
return "console.log('resize'); window.dispatchEvent(new Event('resize'));"
#Max layout is 3 X 4
for k in range(1,13):
#app.callback(
[Output('test{}'.format(k), 'figure'),
Output('test{}'.format(k), 'style')],
[Input('columns', 'value')])
def create_graph(cols):
style = {
'width': SIZING[cols],
'height': SIZING[cols],
}
fig = {'data': [], 'layout': {}}
return [fig, style]
if __name__ == '__main__':
app.server.run()

Categories