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.
Related
I would like to create a heat map for different type of users ("segment"), and different day (date)
My code doesn't work and I don't know why. This is the error:
An object was provided as `children` instead of a component, string, or number (or list of those). Check the children property that looks something like:
{
"label": "Mieszkaniec",
"value": "Mieszkaniec"
}
I think the problem is in the layout. Can someone explain me how exactly children work in this example? Maybe it is impossible to do a chained callback for map
import pandas as pd import dash import plotly.express as px import plotly.graph_objects as go from dash import Dash, dcc, html, Input, Output
dane = pd.read_csv("C:\\Users\\fickar00\\Desktop\\Dane\\test2.csv",
encoding = "unicode_escape", sep=";", na_values='-') dane = dane.fillna(0) dane["c_users"] = [float(str(i).replace(",", "")) for i in dane["c_users"]] dane = dane.astype({"c_users": float})
app = Dash(__name__)
app.layout = html.Div([
html.H1('Dobowe przemieszcznie się ludności w Łodzi w interwale godzinowym'),
html.Label("Wybierz segment:"),
dcc.Dropdown(id="wybierz_segment",
options=[
{"label": "Mieszkaniec", "value": "Mieszkaniec"},
{"label": "Regularny gość", "value": "Regularny_gosc"},
{"label": "Turysta", "value": "Turysta"}],
multi=False,
value="Mieszkaniec",
clearable=False,
style={'width': "40%"}
),
html.Label("Wybierz date:"),
dcc.Dropdown(id='wybierz_date',
options=[{'label': s, 'value': s} for s in sorted(dane.day.unique())],
value="01.09.2022",
style={'width': "40%"},
multi=False),
dcc.Graph(id='mapa', figure={})
]) #app.callback(
Output('wybierz_date', 'options'),
Input('wybierz_segment', 'value') ) def ustaw_opcje_segmentu(wybrany_segment):
dff = dane[dane.segment==wybrany_segment]
return [{'label': c, 'value': c} for c in sorted(dff.segment.unique())]
# populate initial values of counties dropdown #app.callback(
Output('wybierz_date', 'value'),
Input('wybierz_date', 'options') ) def ustaw_wartosci_segmentu(dostepne_opcje):
return [x['value'] for x in dostepne_opcje]
#app.callback(
Output('mapa', 'figure'),
Input('wybierz_date', 'value'),
Input('wybierz_segment', 'value') ) def update_grpah(wytypowany_segment, wytypowana_data):
if len(wytypowana_data) == 0:
return dash.no_update
else:
dff = dane[(dane.segment==wytypowany_segment) & (dane.day==wytypowana_data)]
fig = px.density_mapbox(dff,
lat='loc_wgs84_lat',
lon='loc_wgs84_lon',
animation_frame='hour',
animation_group='c_users',
zoom=10,
z='c_users',
height=650,
width=1400,
mapbox_style="carto-darkmatter")
return fig
#fig.write_html("C:\\Karol\\Python\\file.html")
if __name__ == '__main__':
app.run_server(debug=True)
I am new to Python Dash, so I am not sure what is possible. What I would like to accomplish is making a datatable where the user is able to manipulate data in the datatable which would result in changes for other graphs.
Here is an example of what I am trying to do:
I have information about people’s grocery lists and I would like to be able to change what is in their list
The data that I have is structured as such
Name
Item
Amount
Bob
Apple
1
Bob
Banana
2
Anna
Apple
2
Anna
Banana
1
I would like to be able to have a datatable where there is a drop down to select the person’s list so the datatable would just have their items and amounts. In the selected datatable, they should be able to add and delete rows, change the item with a dropdown in the cell, and change the amount by typing in the desired amount.
I have been able to create this somewhat where you can select a person’s list, and the item can be changed based on a list of foods given, but I cannot figure out how to do the rest. Here is what I have been able to put together for my example
from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import pandas as pd
df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
'Amount' : [1, 2, 2, 1]})
app = dash.Dash(__name__)
names = df['Name'].unique().tolist()
app.layout = html.Div(
children=[
dcc.Dropdown(
id="filter_dropdown",
options=[{"label": name, "value": name} for name in names],
placeholder="-Select a Person-",
multi=False,
value=df['Name'].values,
),
dt.DataTable(
id="table-container",
columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
{'name': 'Amount', 'id': 'Amount'}],
data=df.to_dict("records"),
editable=True,
dropdown={
'Item' : {
'options': [
{'label': i, 'value': i}
for i in list(df['Item'].unique())
]
}
}
)
]
)
#app.callback(
Output("table-container", "data"),
Input("filter_dropdown", "value"))
def display_table(name):
dff = df[df['Name'].isin([name])]
return dff.to_dict("records")
if __name__ == "__main__":
app.run_server(debug=True)
Here is what it looks like in the browser
I can select an item from the dropdown in the cell and it will appear to change it, but when I switch between people it loses its change that I made. I also cannot change the amounts. The next step would be is if I can use all the data as well as the changes in the data in another graph (for example to show how many people have a certain item). If someone would be able to help, I would appreciate it.
Thanks
After doing some work, I do have it working. It is by means not the optimal or right way to do this probably, but it does what I want it to do. I ended up using a global variable for my data frame and whenever there was a change to the data table, I would filter out the data from the data frame and add the modified data from the data table.
To get a visual to work, I listen for changes to the data table. I know that there is probably a race between updating the global variable and returning the new visual, but I did not have to delay the visual being updated.
Below is the code that I came up with. I hope this helps others somehow if they wanted to make a simple "spreadsheet" that people can manipulate and result in a responsive visual related to the data.
import dash
import pandas as pd
from dash import dash_table as dt
from dash import dcc
from dash import html
from dash.dependencies import Input
from dash.dependencies import Output
import plotly.express as px
df = pd.DataFrame({'Name' : ['Bob', 'Bob', 'Anna', 'Anna'],
'Item' : ['Apple', 'Banana', 'Apple', 'Banana'],
'Amount' : [1, 2, 2, 1]})
app = dash.Dash(__name__)
names = df['Name'].unique().tolist()
app.layout = html.Div(
children=[
dcc.Dropdown(
id="filter_dropdown",
options=[{"label": name, "value": name} for name in names],
placeholder="-Select a Person-",
multi=False,
value=df['Name'].values,
),
dt.DataTable(
id="table-container",
columns=[{'name': 'Item', 'id': 'Item', 'presentation':'dropdown'},
{'name': 'Amount', 'id': 'Amount'}],
data=df.to_dict("records"),
editable=True,
row_deletable=True,
dropdown={
'Item' : {
'options': [
{'label': i, 'value': i}
for i in list(df['Item'].unique())
]
}
}
),
html.Button('Add', id='add_btn', n_clicks = 0),
dcc.Graph(id='visual')
]
)
#app.callback(
Output("table-container", "data"),
Input("filter_dropdown", "value"),
Input("table-container", "data"),
Input("table-container", "columns"),
Input("add_btn", 'n_clicks'))
def display_table(name, rows, columns, n_clicks):
e = dash.callback_context.triggered[0]['prop_id']
global df
if e in ["table-container.data", "table-container.columns"]:
temp = pd.DataFrame(rows, columns=[c['name'] for c in columns])
temp['Name'] = name
df = df[~df['Name'].isin([name])]
df = df.append(temp)
elif e == 'add_btn.n_clicks':
if n_clicks > 0:
df = df.append(pd.DataFrame({'Name':name, 'Item': '', 'Amount': 0}, index=[0]))
dff = df[df['Name'].isin([name])].to_dict("records")
return dff
#app.callback(Output('visual', 'figure'),
Input("table-container", "data"),
Input("table-container", "columns"))
def display_graph(rows, columns):
fig = px.bar(df, x='Item', y='Amount')
return fig
if __name__ == "__main__":
app.run_server(debug=True)
I am very new to Dash. I have made a dataTable that includes several columns. These columns can be filtered and sorted. However, one problem with the filtering is that I cannot filter based on a list (like pandas .loc) e.g. if I want to filter the countries based on a list (say, ['India', 'United States']), the filter does not work. I have previously checked the advanced filtering here and found that I can use || operators; however,this would not be a good choice if the list is more than 4 or 5.
Here's the code:
import dash
from dash.dependencies import Input, Output
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import json
df = pd.read_csv(
'https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div(id='heading-users', children='Users\' Country details', style={
'textAlign': 'center', 'font-family': 'Helvetica'}),
dash_table.DataTable( # users
id='datatable-users',
columns=[
{"name": i, "id": i, "deletable": True, "selectable": True} for i in df.columns
],
data=df.to_dict('records'),
editable=True,
filter_action="native",
sort_action="native",
sort_mode="multi",
column_selectable="single",
row_selectable="multi",
row_deletable=True,
selected_columns=[],
selected_rows=[],
page_action="native",
page_current=0,
page_size=20,
export_format='csv'
),
html.Div(id='datatable-users-container')
])
#app.callback(
Output('datatable-users-container', "children"),
Input('datatable-users', "derived_virtual_data"),
Input('datatable-users', "derived_virtual_selected_rows"))
def update_graphs(rows, derived_virtual_selected_rows):
if derived_virtual_selected_rows is None:
derived_virtual_selected_rows = []
dff = df if rows is None else pd.DataFrame(rows)
colors = ['#7FDBFF' if i in derived_virtual_selected_rows else '#0074D9'
for i in range(len(dff))]
return [
dcc.Graph(
id=column,
figure={
"data": [
{
"x": dff["country"],
"y": dff[column],
"type": "bar",
"marker": {"color": colors},
}
],
"layout": {
"xaxis": {"automargin": True},
"yaxis": {
"automargin": True,
"title": {"text": column}
},
"height": 250,
"margin": {"t": 10, "l": 10, "r": 10},
},
},
)
# check if column exists - user may have deleted it
# If `column.deletable=False`, then you don't
# need to do this check.
for column in ["pop", "lifeExp", "gdpPercap"] if column in dff
]
if __name__ == '__main__':
app.run_server(debug=True)
From that link - The 'native' filter function doesn't support 'OR' operations within a single column. Assign filter_action="custom" then create a callback to update the table children. See the 'Back-End Filtering section of that link.
You'll need to grab the filter query string in a callback and decompose to extract the column name and query string. With that you can query the pandas dataframe and return the results in a callback. I don't have the code for "OR" functionality but found some I used that can query pandas once you have the input values
def filter_df(df, filter_column, value_list):
conditions = []
for val in value_list:
conditions.append(f'{filter_column} == "{val}"')
query = ' or '.join(conditions)
print(f'querying with: {query}')
return df.query(query_expr)
filter_df(df, 'country', ['Albania', 'Algeria'])
I want to change a color of a cell of a Dash dataTable based on a value. I have tried a minimal example:
html.Div(
children=[
dash_table.DataTable(
id='table_1',
data=df.to_dict('records'),
columns=[{"name": i, "id": i} for i in df.columns],
#conditional cell formating
style_data_conditional=[
{
'if': {
'column_id': 'col1',
'filter': 'col1 > num(15)'
},
'backgroundColor': '#3D9970',
'color': 'white',
},
],
n_fixed_rows=2,
filtering=True,
sorting=True,
sorting_type="multi"
)],
)
After adding the style_conditional, the table is not showing at all, and there is no error message thrown. The table is embedded in html component, and after looking into forums and on github, I am stiil not sure if I have missed anything here, and if I need to write a callback for this. The example provided in the mentioned tutorial is not suggesting the need for a callback.
Update:
Tried to run minimal version with the same code and different data, with same results, that is no change in cell colors. My libraries are up to date, but something in the environment may still be causing problems.
Full code:
import dash
import dash_table
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
# Having trouble with conditional when column name has spaces
df = df.rename({'Number of Solar Plants': 'Plants'}, axis='columns')
app = dash.Dash(__name__)
app.layout = dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns],
style_data_conditional=[{
"if": {'column_id': 'State',
'filter': 'State eq "Nevada"'
},
"backgroundColor": "#3D9970",
"color": "white"
}],
data=df.to_dict('records'),
)
if __name__ == '__main__':
app.run_server(debug=True)
I do not think you will need a callback for this like said in the tutorial. According to the last example of the tutorial I think you have a typo (one ' to much).
Change this line
'filter': 'col1' > num(15)'
to:
'filter': 'col1 > num(15)'
I had the same issue and i found that giving the index directly rather than a condition was a lot easier.
style_data_conditional = [{'if': {'column_id': 'col1',"row_index": x},'backgroundColor': '#3D9970','color': 'white'} for x in df[df['col1']>15].index ]
it's ugly as it has been hard coded but it did the trick for me when the direct filter did not.
I don't know how it was back in 2019, but with recent releases of Dash Datatables:
Column names in filter expressions must be wrapped in curly braces
filter has been renamed to filter_query
style_data_conditional=[{
"if": {
'column_id': 'State',
'filter_query': '{State} eq "Nevada"'
# ^ ^ <-- required braces
},
"backgroundColor": "#3D9970",
"color": "white"
}]
BTW: You can conveniently highlight the entire row by removing the column_id line.
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()