Conditional formatting is provided through the style_data_conditional
property. The if
keyword provides a set of conditional formatting
statements and the rest of the keywords are camelCased CSS properties.
The if
syntax supports several operators, row_index
, column_id
,
filter_query
, column_type
, column_editable
, and state
.
filter_query
is the most flexible option when dealing with data.
Here is an example of all operators:
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
df['id'] = df.index
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[
{'name': 'Date', 'id': 'Date', 'type': 'datetime', 'editable': False},
{'name': 'Delivery', 'id': 'Delivery', 'type': 'datetime'},
{'name': 'Region', 'id': 'Region', 'type': 'text'},
{'name': 'Temperature', 'id': 'Temperature', 'type': 'numeric'},
{'name': 'Humidity', 'id': 'Humidity', 'type': 'numeric'},
{'name': 'Pressure', 'id': 'Pressure', 'type': 'any'},
],
editable=True,
style_data_conditional=[
{
'if': {
'column_id': 'Region',
},
'backgroundColor': 'dodgerblue',
'color': 'white'
},
{
'if': {
'filter_query': '{Humidity} > 19 && {Humidity} < 41',
'column_id': 'Humidity'
},
'backgroundColor': 'tomato',
'color': 'white'
},
{
'if': {
'column_id': 'Pressure',
# since using .format, escape { with {{
'filter_query': '{{Pressure}} = {}'.format(df['Pressure'].max())
},
'backgroundColor': '#85144b',
'color': 'white'
},
{
'if': {
'row_index': 5, # number | 'odd' | 'even'
'column_id': 'Region'
},
'backgroundColor': 'hotpink',
'color': 'white'
},
{
'if': {
'filter_query': '{id} = 4', # matching rows of a hidden column with the id, `id`
'column_id': 'Region'
},
'backgroundColor': 'RebeccaPurple'
},
{
'if': {
'filter_query': '{Delivery} > {Date}', # comparing columns to each other
'column_id': 'Delivery'
},
'backgroundColor': '#3D9970'
},
{
'if': {
'column_editable': False # True | False
},
'backgroundColor': 'rgb(240, 240, 240)',
'cursor': 'not-allowed'
},
{
'if': {
'column_type': 'text' # 'text' | 'any' | 'datetime' | 'numeric'
},
'textAlign': 'left'
},
{
'if': {
'state': 'active' # 'active' | 'selected'
},
'backgroundColor': 'rgba(0, 116, 217, 0.3)',
'border': '1px solid rgb(0, 116, 217)'
}
]
)
if __name__ == '__main__':
app.run(debug=True)
Notes:
- filter_query
supports different operators depending on the data type
of the column:
=
, >
, >=
, <
, <=
, and contains
are supported bynumeric
, text
, datetime
, and any
)contains
, the right-hand-side needs to be a string,{Date} contains "01"
will work but {Date} contains 1
will not.datestartswith
is supported by datetime
is nil
is supported by all data typesis blank
is supported by all data types
A column’s default data type is any
column_type
refers to the data type of the column (numeric
, text
, datetime
, and any
)column_editable
can be equal to True
or False
(new in Dash 1.12.0)state
can be equal to 'active'
or 'selected'
(new in Dash 1.12.0). Use this to changerow_index
is absolute - if you filter or sort your table,column_id
, row_index
, and header_index
can be equal to a scalar'column_id': ['Region', 'Pressure']
DataTable
filtering & conditional formattingif
blocks.'filter_query': '{Delivery} > {Date}'
: Filter queries can compare columnsid
is a special hidden column that can be used as an alternativerow_index
for highlighting data by index. Since each row has aid
, the conditional formatting associated with this id
RebeccaPurple
, hotpink
, DodgerBlue
… These arered
, blue
, green
as they look very outdated. For other color.format
, we escape {
with \{{
and }
with \}}
.column_id
. To highlight a particular cell, include column_id
.style_cell_conditional
(all cells, including headers),style_header_conditional
(header cells),style_filter_conditional
(filter input boxes)style_data_conditional
via a callback whenever derived_virtual_data
changes.Instead of highlighting the background cell, you can change the color
of the text, bold the text, add underlines, or style it using any
other CSS property.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
from dash.dash_table.Format import Format, Sign
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[
{"name": i, "id": i} for i in df.columns
],
style_data_conditional=[
{
'if': {
'filter_query': '{Humidity} > 19 && {Humidity} < 41',
'column_id': 'Humidity'
},
'color': 'tomato',
'fontWeight': 'bold'
},
{
'if': {
'filter_query': '{Pressure} > 19',
'column_id': 'Pressure'
},
'textDecoration': 'underline'
}
]
)
if __name__ == '__main__':
app.run(debug=True)
You can copy and paste emoji unicode characters directly into your code.
We recommend copying values from emojipedia, e.g.
https://emojipedia.org/star/.
New unicode emoji characters are released every year and may not be
available in the character sets of your audience’s machines.
The appearance of these icons differs on most operating systems.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
df['Rating'] = df['Humidity'].apply(lambda x:
'⭐⭐⭐' if x > 30 else (
'⭐⭐' if x > 20 else (
'⭐' if x > 10 else ''
)))
df['Growth'] = df['Temperature'].apply(lambda x: '↗️' if x > 0 else '↘️')
df['Status'] = df['Temperature'].apply(lambda x: '🔥' if x > 0 else '🚒')
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{"name": i, "id": i} for i in df.columns
],
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{"name": i, "id": i} for i in df.columns
],
style_data_conditional=[
{
'if': {
'filter_query': '{{Pressure}} = {}'.format(df['Pressure'].max()),
'column_id': 'Pressure'
},
'backgroundColor': '#FF4136',
'color': 'white'
},
]
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{"name": i, "id": i} for i in df.columns
],
style_data_conditional=[
{
'if': {
'filter_query': '{{Temperature}} = {}'.format(df['Temperature'].min()),
},
'backgroundColor': '#FF4136',
'color': 'white'
},
]
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{"name": i, "id": i} for i in df.columns
],
style_data_conditional=(
[
{
'if': {
'filter_query': '{{Temperature}} = {}'.format(i),
'column_id': 'Temperature',
},
'backgroundColor': '#0074D9',
'color': 'white'
}
for i in df['Temperature'].nlargest(3)
] +
[
{
'if': {
'filter_query': '{{Pressure}} = {}'.format(i),
'column_id': 'Pressure',
},
'backgroundColor': '#7FDBFF',
'color': 'white'
}
for i in df['Pressure'].nsmallest(3)
]
)
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
def highlight_max_row(df):
df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
return [
{
'if': {
'filter_query': '{{id}} = {}'.format(i),
'column_id': col
},
'backgroundColor': '#3D9970',
'color': 'white'
}
# idxmax(axis=1) finds the max indices of each row
for (i, col) in enumerate(
df_numeric_columns.idxmax(axis=1)
)
]
df['id'] = df.index
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns if i != 'id'],
style_data_conditional=highlight_max_row(df)
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
def style_row_by_top_values(df, nlargest=2):
numeric_columns = df.select_dtypes('number').drop(['id'], axis=1).columns
styles = []
for i in range(len(df)):
row = df.loc[i, numeric_columns].sort_values(ascending=False)
for j in range(nlargest):
styles.append({
'if': {
'filter_query': '{{id}} = {}'.format(i),
'column_id': row.keys()[j]
},
'backgroundColor': '#39CCCC',
'color': 'white'
})
return styles
df['id'] = df.index
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns if i != 'id'],
style_data_conditional=style_row_by_top_values(df)
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
def style_table_by_max_value(df):
if 'id' in df:
numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
else:
numeric_columns = df.select_dtypes('number')
max_across_numeric_columns = numeric_columns.max()
max_across_table = max_across_numeric_columns.max()
styles = []
for col in max_across_numeric_columns.keys():
if max_across_numeric_columns[col] == max_across_table:
styles.append({
'if': {
'filter_query': '{{{col}}} = {value}'.format(
col=col, value=max_across_table
),
'column_id': col
},
'backgroundColor': '#39CCCC',
'color': 'white'
})
return styles
df['id'] = df.index
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns if i != 'id'],
style_data_conditional=style_table_by_max_value(df)
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=[
{
'if': {
'filter_query': '{2018} >= 5 && {2018} < 10',
'column_id': '2018'
},
'backgroundColor': '#B10DC9',
'color': 'white'
}
]
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=[
{
'if': {
'filter_query': '{{{col}}} >= 5 && {{{col}}} < 10'.format(col=col),
'column_id': col
},
'backgroundColor': '#B10DC9',
'color': 'white'
} for col in df.columns
]
)
if __name__ == '__main__':
app.run(debug=True)
Let’s break down \{{\{col}}}
. We want the final expression to look something like
{2017} > 5 & {2017} < 10
where 2017 is the name of the column.
Since we’re using .format()
, we need to escape the brackets,
so {2017}
would be {{2017}}
. Then, we need to replace 2017
with {col}
for the find-and-replace, so becomes\{{\{col}}}
.format(col=col)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=[
{
'if': {
'filter_query': '{{{}}} >= {}'.format(col, value),
'column_id': col
},
'backgroundColor': '#B10DC9',
'color': 'white'
} for (col, value) in df.quantile(0.9).iteritems()
]
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns if i != 'id'],
style_data_conditional=[
{
'if': {
'filter_query': '{{{}}} <= {}'.format(col, value),
'column_id': col
},
'backgroundColor': '#B10DC9',
'color': 'white'
} for (col, value) in df.quantile(0.1).iteritems()
]
)
if __name__ == '__main__':
app.run(debug=True)
Here, the highlighting is done per column.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=(
[
{
'if': {
'filter_query': '{{{}}} > {}'.format(col, value),
'column_id': col
},
'backgroundColor': '#3D9970',
'color': 'white'
} for (col, value) in df.quantile(0.1).iteritems()
] +
[
{
'if': {
'filter_query': '{{{}}} <= {}'.format(col, value),
'column_id': col
},
'backgroundColor': '#FF4136',
'color': 'white'
} for (col, value) in df.quantile(0.5).iteritems()
]
)
)
if __name__ == '__main__':
app.run(debug=True)
Here, the highlighting is done per table.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
def highlight_above_and_below_max(df):
df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
mean = df_numeric_columns.mean().mean()
return (
[
{
'if': {
'filter_query': '{{{}}} > {}'.format(col, mean),
'column_id': col
},
'backgroundColor': '#3D9970',
'color': 'white'
} for col in df_numeric_columns.columns
] +
[
{
'if': {
'filter_query': '{{{}}} <= {}'.format(col, mean),
'column_id': col
},
'backgroundColor': '#FF4136',
'color': 'white'
} for col in df_numeric_columns.columns
]
)
df['id'] = df.index
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns if 'id' not in df],
style_data_conditional=highlight_above_and_below_max(df)
)
if __name__ == '__main__':
app.run(debug=True)
Three filter queries help with empty or blank values:
- {my_column} is nil
will match None
values
- {my_column} is blank
will match None
values and empty strings
- {my_column} = ""
will match empty strings
from dash import Dash, dash_table, html
import pandas as pd
import numpy as np
data_with_none = [
{'Firm': 'Acme', '2017': '', '2018': 5, '2019': 10, '2020': 4},
{'Firm': 'Olive', '2017': None, '2018': 3, '2019': 13, '2020': 3},
{'Firm': 'Barnwood', '2017': np.NaN, '2018': 7, '2019': 3, '2020': 6},
{'Firm': 'Henrietta', '2017': 14, '2018': 1, '2019': 13, '2020': 1},
]
df = pd.DataFrame(data_with_none)
app = Dash()
app.layout = html.Div([
html.Pre(repr(df)),
dash_table.DataTable(
data=df.to_dict('records'),
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=(
[
{
'if': {
'filter_query': '{{{}}} is blank'.format(col),
'column_id': col
},
'backgroundColor': 'tomato',
'color': 'white'
} for col in df.columns
]
)
)
])
if __name__ == '__main__':
app.run(debug=True)
Firm 2017 2018 2019 2020 0 Acme 5 10 4 1 Olive None 3 13 3 2 Barnwood NaN 7 3 6 3 Henrietta 14 1 13 1
NaN
or None
Valuesfrom dash import Dash, dash_table, html
import pandas as pd
import numpy as np
from dash.dash_table.Format import Format
data_with_none = [
{'Firm': 'Acme', '2017': '', '2018': 5, '2019': 10, '2020': 4},
{'Firm': 'Olive', '2017': None, '2018': 3, '2019': 13, '2020': 3},
{'Firm': 'Barnwood', '2017': np.NaN, '2018': 7, '2019': 3, '2020': 6},
{'Firm': 'Henrietta', '2017': 14, '2018': 1, '2019': 13, '2020': 1},
]
df = pd.DataFrame(data_with_none)
app = Dash()
app.layout = html.Div([
html.Pre(repr(df)),
dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{
'name': i,
'id': i,
'type': 'numeric',
'format': Format(
nully='N/A'
)
} for i in df.columns
],
editable=True
)
])
if __name__ == '__main__':
app.run(debug=True)
Firm 2017 2018 2019 2020 0 Acme 5 10 4 1 Olive None 3 13 3 2 Barnwood NaN 7 3 6 3 Henrietta 14 1 13 1
Limitations:
- Format(nully=)
does not yet match for empty strings, only
None
values. See plotly/dash-table#763
for updates.
- 'type': 'numeric'
needs to be set, see plotly/dash-table#762
for updates.
An alternative method would be to fill in e.g. “N/A” in the data before rendering:
from dash import Dash, dash_table, html
import pandas as pd
import numpy as np
from dash.dash_table.Format import Format
data_with_none = [
{'Firm': 'Acme', '2017': '', '2018': 5, '2019': 10, '2020': 4},
{'Firm': 'Olive', '2017': None, '2018': 3, '2019': 13, '2020': 3},
{'Firm': 'Barnwood', '2017': np.NaN, '2018': 7, '2019': 3, '2020': 6},
{'Firm': 'Henrietta', '2017': 14, '2018': 1, '2019': 13, '2020': 1},
]
df = pd.DataFrame(data_with_none)
df = df.fillna('N/A').replace('', 'N/A')
app = Dash()
app.layout = html.Div([
html.Pre(repr(df)),
dash_table.DataTable(
data=df.to_dict('records'),
columns=[{'name': i, 'id': i} for i in df.columns],
editable=True,
style_data_conditional=[
{
'if': {
'filter_query': '{{{col}}} = "N/A"'.format(col=col),
'column_id': col
},
'backgroundColor': 'tomato',
'color': 'white'
} for col in df.columns
]
)
])
if __name__ == '__main__':
app.run(debug=True)
Firm 2017 2018 2019 2020 0 Acme N/A 5 10 4 1 Olive N/A 3 13 3 2 Barnwood N/A 7 3 6 3 Henrietta 14 1 13 1
Limitation: If your table is editable, then if a user deletes the
contents of a cell, “N/A” will no longer be displayed.
This is unlike the example with Format
where the DataTable
will
automatically display N/A
for any empty cells, even after editing.
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{'name': i, 'id': i} for i in df.columns
],
style_data_conditional=[
{
'if': {
'filter_query': '{Region} contains "New"'
},
'backgroundColor': '#0074D9',
'color': 'white'
}
]
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{'name': i, 'id': i} for i in df.columns
],
style_data_conditional=[
{
'if': {
'filter_query': '{Region} = "San Francisco"'
},
'backgroundColor': '#0074D9',
'color': 'white'
}
]
)
if __name__ == '__main__':
app.run(debug=True)
This recipe shades cells with style_data_conditional
and creates a
legend with HTML components. You’ll need to pip install colorlover
to get the colorscales.
from dash import Dash, dash_table, html
import pandas as pd
from collections import OrderedDict
wide_data = [
{'Firm': 'Acme', '2017': 13, '2018': 5, '2019': 10, '2020': 4},
{'Firm': 'Olive', '2017': 3, '2018': 3, '2019': 13, '2020': 3},
{'Firm': 'Barnwood', '2017': 6, '2018': 7, '2019': 3, '2020': 6},
{'Firm': 'Henrietta', '2017': -3, '2018': -10, '2019': -5, '2020': -6},
]
df = pd.DataFrame(wide_data)
app = Dash()
def discrete_background_color_bins(df, n_bins=5, columns='all'):
import colorlover
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
if columns == 'all':
if 'id' in df:
df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
else:
df_numeric_columns = df.select_dtypes('number')
else:
df_numeric_columns = df[columns]
df_max = df_numeric_columns.max().max()
df_min = df_numeric_columns.min().min()
ranges = [
((df_max - df_min) * i) + df_min
for i in bounds
]
styles = []
legend = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
backgroundColor = colorlover.scales[str(n_bins)]['seq']['Blues'][i - 1]
color = 'white' if i > len(bounds) / 2. else 'inherit'
for column in df_numeric_columns:
styles.append({
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'backgroundColor': backgroundColor,
'color': color
})
legend.append(
html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
html.Div(
style={
'backgroundColor': backgroundColor,
'borderLeft': '1px rgb(50, 50, 50) solid',
'height': '10px'
}
),
html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
])
)
return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))
(styles, legend) = discrete_background_color_bins(df)
app.layout = html.Div([
html.Div(legend, style={'float': 'right'}),
dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=styles
),
])
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table, html
import pandas as pd
from collections import OrderedDict
wide_data = [
{'Firm': 'Acme', '2017': 13, '2018': 5, '2019': 10, '2020': 4},
{'Firm': 'Olive', '2017': 3, '2018': 3, '2019': 13, '2020': 3},
{'Firm': 'Barnwood', '2017': 6, '2018': 7, '2019': 3, '2020': 6},
{'Firm': 'Henrietta', '2017': -3, '2018': -10, '2019': -5, '2020': -6},
]
df = pd.DataFrame(wide_data)
app = Dash()
def discrete_background_color_bins(df, n_bins=5, columns='all'):
import colorlover
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
if columns == 'all':
if 'id' in df:
df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
else:
df_numeric_columns = df.select_dtypes('number')
else:
df_numeric_columns = df[columns]
df_max = df_numeric_columns.max().max()
df_min = df_numeric_columns.min().min()
ranges = [
((df_max - df_min) * i) + df_min
for i in bounds
]
styles = []
legend = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
backgroundColor = colorlover.scales[str(n_bins)]['seq']['Blues'][i - 1]
color = 'white' if i > len(bounds) / 2. else 'inherit'
for column in df_numeric_columns:
styles.append({
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'backgroundColor': backgroundColor,
'color': color
})
legend.append(
html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
html.Div(
style={
'backgroundColor': backgroundColor,
'borderLeft': '1px rgb(50, 50, 50) solid',
'height': '10px'
}
),
html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
])
)
return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))
(styles, legend) = discrete_background_color_bins(df, columns=['2018'])
app.layout = html.Div([
legend,
dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=styles
)
])
if __name__ == '__main__':
app.run(debug=True)
These recipes display a creative use of background linear-gradient
colors to display horizontal bar charts within the table.
Your mileage may vary! Feel free to modify these recipes for your own
use.
from dash import Dash, dash_table
import pandas as pd
df_gapminder = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
df = df_gapminder[:500]
app = Dash()
def data_bars(df, column):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
ranges = [
((df[column].max() - df[column].min()) * i) + df[column].min()
for i in bounds
]
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
max_bound_percentage = bounds[i] * 100
styles.append({
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'background': (
"""
linear-gradient(90deg,
#0074D9 0%,
#0074D9 {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(max_bound_percentage=max_bound_percentage)
),
'paddingBottom': 2,
'paddingTop': 2
})
return styles
def data_bars_diverging(df, column, color_above='#3D9970', color_below='#FF4136'):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
col_max = df[column].max()
col_min = df[column].min()
ranges = [
((col_max - col_min) * i) + col_min
for i in bounds
]
midpoint = (col_max + col_min) / 2.
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
min_bound_percentage = bounds[i - 1] * 100
max_bound_percentage = bounds[i] * 100
style = {
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'paddingBottom': 2,
'paddingTop': 2
}
if max_bound > midpoint:
background = (
"""
linear-gradient(90deg,
white 0%,
white 50%,
{color_above} 50%,
{color_above} {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(
max_bound_percentage=max_bound_percentage,
color_above=color_above
)
)
else:
background = (
"""
linear-gradient(90deg,
white 0%,
white {min_bound_percentage}%,
{color_below} {min_bound_percentage}%,
{color_below} 50%,
white 50%,
white 100%)
""".format(
min_bound_percentage=min_bound_percentage,
color_below=color_below
)
)
style['background'] = background
styles.append(style)
return styles
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=(
data_bars(df, 'lifeExp') +
data_bars(df, 'gdpPercap')
),
style_cell={
'width': '100px',
'minWidth': '100px',
'maxWidth': '100px',
'overflow': 'hidden',
'textOverflow': 'ellipsis',
},
page_size=20
)
if __name__ == '__main__':
app.run(debug=True)
Display the data bars without text by creating a new column and making
the text transparent.
from dash import Dash, dash_table
import pandas as pd
df_gapminder = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
df = df_gapminder[:500]
df['gdpPercap relative values'] = df['gdpPercap']
app = Dash()
def data_bars(df, column):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
ranges = [
((df[column].max() - df[column].min()) * i) + df[column].min()
for i in bounds
]
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
max_bound_percentage = bounds[i] * 100
styles.append({
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'background': (
"""
linear-gradient(90deg,
#0074D9 0%,
#0074D9 {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(max_bound_percentage=max_bound_percentage)
),
'paddingBottom': 2,
'paddingTop': 2
})
return styles
def data_bars_diverging(df, column, color_above='#3D9970', color_below='#FF4136'):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
col_max = df[column].max()
col_min = df[column].min()
ranges = [
((col_max - col_min) * i) + col_min
for i in bounds
]
midpoint = (col_max + col_min) / 2.
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
min_bound_percentage = bounds[i - 1] * 100
max_bound_percentage = bounds[i] * 100
style = {
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'paddingBottom': 2,
'paddingTop': 2
}
if max_bound > midpoint:
background = (
"""
linear-gradient(90deg,
white 0%,
white 50%,
{color_above} 50%,
{color_above} {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(
max_bound_percentage=max_bound_percentage,
color_above=color_above
)
)
else:
background = (
"""
linear-gradient(90deg,
white 0%,
white {min_bound_percentage}%,
{color_below} {min_bound_percentage}%,
{color_below} 50%,
white 50%,
white 100%)
""".format(
min_bound_percentage=min_bound_percentage,
color_below=color_below
)
)
style['background'] = background
styles.append(style)
return styles
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=(
data_bars(df, 'gdpPercap relative values') + [{
'if': {'column_id': 'gdpPercap relative values'},
'color': 'transparent'
}]
),
style_cell={
'width': '100px',
'minWidth': '100px',
'maxWidth': '100px',
'overflow': 'hidden',
'textOverflow': 'ellipsis',
},
page_size=20
)
if __name__ == '__main__':
app.run(debug=True)
The data_bars_diverging
function splits up the data into two quadrants
by the midpoint.
Alternative representations of data bars may split up the data by
positive and negative numbers or by the average values.
Your mileage may vary! Feel free to modify the data_bars_diverging
function to your own visualization needs. If you create something new,
please share your work on the Dash Community Forum.
from dash import Dash, dash_table
import pandas as pd
df_gapminder = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
df = df_gapminder[:500]
app = Dash()
def data_bars(df, column):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
ranges = [
((df[column].max() - df[column].min()) * i) + df[column].min()
for i in bounds
]
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
max_bound_percentage = bounds[i] * 100
styles.append({
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'background': (
"""
linear-gradient(90deg,
#0074D9 0%,
#0074D9 {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(max_bound_percentage=max_bound_percentage)
),
'paddingBottom': 2,
'paddingTop': 2
})
return styles
def data_bars_diverging(df, column, color_above='#3D9970', color_below='#FF4136'):
n_bins = 100
bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
col_max = df[column].max()
col_min = df[column].min()
ranges = [
((col_max - col_min) * i) + col_min
for i in bounds
]
midpoint = (col_max + col_min) / 2.
styles = []
for i in range(1, len(bounds)):
min_bound = ranges[i - 1]
max_bound = ranges[i]
min_bound_percentage = bounds[i - 1] * 100
max_bound_percentage = bounds[i] * 100
style = {
'if': {
'filter_query': (
'{{{column}}} >= {min_bound}' +
(' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
).format(column=column, min_bound=min_bound, max_bound=max_bound),
'column_id': column
},
'paddingBottom': 2,
'paddingTop': 2
}
if max_bound > midpoint:
background = (
"""
linear-gradient(90deg,
white 0%,
white 50%,
{color_above} 50%,
{color_above} {max_bound_percentage}%,
white {max_bound_percentage}%,
white 100%)
""".format(
max_bound_percentage=max_bound_percentage,
color_above=color_above
)
)
else:
background = (
"""
linear-gradient(90deg,
white 0%,
white {min_bound_percentage}%,
{color_below} {min_bound_percentage}%,
{color_below} 50%,
white 50%,
white 100%)
""".format(
min_bound_percentage=min_bound_percentage,
color_below=color_below
)
)
style['background'] = background
styles.append(style)
return styles
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
sort_action='native',
columns=[{'name': i, 'id': i} for i in df.columns],
style_data_conditional=(
data_bars_diverging(df, 'lifeExp') +
data_bars_diverging(df, 'gdpPercap')
),
style_cell={
'width': '100px',
'minWidth': '100px',
'maxWidth': '100px',
'overflow': 'hidden',
'textOverflow': 'ellipsis',
},
page_size=20
)
if __name__ == '__main__':
app.run(debug=True)
from dash import Dash, dash_table
import pandas as pd
from collections import OrderedDict
data = OrderedDict(
[
("Date", ["2015-01-01", "2015-10-24", "2016-05-10", "2017-01-10", "2018-05-10", "2018-08-15"]),
("Region", ["Montreal", "Toronto", "New York City", "Miami", "San Francisco", "London"]),
("Temperature", [1, -20, 3.512, 4, 10423, -441.2]),
("Humidity", [10, 20, 30, 40, 50, 60]),
("Pressure", [2, 10924, 3912, -10, 3591.2, 15]),
]
)
df = pd.DataFrame(data)
app = Dash()
app.layout = dash_table.DataTable(
data=df.to_dict('records'),
columns=[
{'name': i, 'id': i}
if i != 'Date' else
{'name': 'Date', 'id': 'Date', 'type': 'datetime'}
for i in df.columns
],
style_data_conditional=[{
'if': {'filter_query': '{Date} datestartswith "2015-10"'},
'backgroundColor': '#85144b',
'color': 'white'
}]
)
if __name__ == '__main__':
app.run(debug=True)