Dash dataTable conditional cell formatting isn't working - python

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.

Related

How to keep a Dash datatable inside its container?

I have a Dash datatable inside a dbc.Col (which is inside a dbc.Row), and I can not figure out how to keep the table inside its container when the screen size is smaller (trying to make it responsive).
If the table is too big for its container, it should have a scroll bar, so I added overflowX:auto to its style, but it still won't work.
Right now it looks like this:
The code is something like:
dbc.Col(dt.DataTable(
id='tabla_req',
style_table={
'overflowX': 'auto',
'width':'100%',
'margin':'auto'},
row_deletable=False,
row_selectable="single",
page_current= 0,
page_size= 10,
fill_width=False,
style_data={
'whiteSpace': 'normal',
'height': 'auto',
},
#editable=False,
data=df.to_dict('records'),
columns=[{"name": i, "id": i} for i in df.columns.tolist()]),
width=12
)
I had similar issue, adding these parameter was helpful
virtualization=True

Filtering a column on Dash dataTable based on a list

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'])

How can I enable editing on a specific row on a Dash DataTable

I want to enable modifications on a specific row on a dash Datatable, so far we can enable editing by column, but I don't want to enable editing on the whole column, I just want to enable it on a specific row.
I can highlight the row using style_data_conditional:
style_data_conditional = [
{
"if": {"column_id": x, "row_index": 2},
"backgroundColor": 'red',
"cursor": "pointer",
}
for x in df.columns[2:]
]
But I want to make this row also editable.
One approach it so make the whole DataTable editable and make specific rows not editable using css.
import dash
import dash_table
import pandas as pd
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/solar.csv")
app = dash.Dash(__name__)
app.layout = dash_table.DataTable(
id="table",
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict("records"),
editable=True,
)
if __name__ == "__main__":
app.run_server(debug=True)
When DataTable is not editable a td contains a div with the cell text inside. When it's editable the div with cell text has its opacity set to 0 and an input with the cell value is added.
We can add some css to remove this input and set the opacity of the div with the cell value to 1 for td elements where the data-dash-row attribute does not equal a certain row number:
td:not([data-dash-row="2"]) div.input-cell-value-shadow {
opacity: 1 !important
}
td:not([data-dash-row="2"]) input {
display: none;
}
So the styles above make it so only the third row of the DataTable is editable.

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.

Conditional styling a row, based on column value

I would like to style row based on a value of a column, I currently have this code:
import dash
import dash_table
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
app = dash.Dash(__name__)
print([{"name": i, "id": i} for i in df.columns])
app.layout = dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict("rows"),
style_data_conditional=[
{
'if': {
'column_id': 'Number of Solar Plants',
'filter': '"Number of Solar Plants" > num(100)'
},
'backgroundColor': '#3D9970',
'color': 'white',
}
],
)
if __name__ == '__main__':
app.run_server(debug=True)
Which produces the following result:
But what I really want is for the rows (tr 1 and 8 in this case) to be styled with a green background, not just the cells.
What can I do to achieve this?
to fix your issue you just have to remove the column_id parameter in your style_data_conditional. So all the row will be colored in green.
You should do this:
style_data_conditional=[
{
'if': {
'filter': '"Number of Solar Plants" > num(100)'
},
'backgroundColor': '#3D9970',
'color': 'white',
}
]

Categories