DataTable
includes several features for modifying and transforming
the view of the data. These include:
sort_action='native'
)filter_action='native'
)editable=True
)row_deletable=True
)columns[i].deletable=True
)row_selectable='single' | 'multi'
)column_selectable='single' | 'multi'
and columns[i].selectable=True
)page_action='native'
)hidden_columns=[]
)A quick note on filtering. We have defined our own
syntax for performing filtering operations. Here are some
examples for this particular dataset:
Asia
in the “continent” column> 5000
in the “gdpPercap” column< 80
in the lifeExp
columnNote: simple strings can be entered plain, but if you have
spaces or special characters (including-
, particularly in dates)
you need to wrap them in quotes.
Single quotes'
, double quotes"
, or backticks\\
all work.
Full filter syntax reference
By default, these transformations are done clientside.
Your Dash callbacks can respond to these modifications
by listening to the data
property as an Input
.
Note that if data
is an Input
then the entire
data
will be passed over the network: if your dataframe is large,
then this will become slow. For large dataframes, you can perform the
sorting or filtering in Python instead.
from dash import Dash, dash_table, dcc, html, Input, Output, callback
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
app = Dash(__name__)
app.layout = html.Div([
dash_table.DataTable(
id='datatable-interactivity',
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= 10,
),
html.Div(id='datatable-interactivity-container')
])
@callback(
Output('datatable-interactivity', 'style_data_conditional'),
Input('datatable-interactivity', 'selected_columns')
)
def update_styles(selected_columns):
return [{
'if': { 'column_id': i },
'background_color': '#D2F3FF'
} for i in selected_columns]
@callback(
Output('datatable-interactivity-container', "children"),
Input('datatable-interactivity', "derived_virtual_data"),
Input('datatable-interactivity', "derived_virtual_selected_rows"))
def update_graphs(rows, derived_virtual_selected_rows):
# When the table is first rendered, `derived_virtual_data` and
# `derived_virtual_selected_rows` will be `None`. This is due to an
# idiosyncrasy in Dash (unsupplied properties are always None and Dash
# calls the dependent callbacks when the component is first rendered).
# So, if `rows` is `None`, then the component was just rendered
# and its value will be the same as the component's dataframe.
# Instead of setting `None` in here, you could also set
# `derived_virtual_data=df.to_rows('dict')` when you initialize
# the component.
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(debug=True)
When using transformations - sorting, filtering, pagination - it can be
difficult to match up rows - visible rows, selected rows, active rows -
to the original data, because row indices may not have their original
meaning. To simplify this logic we’ve added support for Row IDs.
Each row of data can have an 'id'
key, which should contain a string
or a number.
If you want to display these values you can include a column with
id='id'
, but normally they stay hidden.
All properties that list certain rows by index also have variants
listing row IDs:
- derived_virtual_indices
/ derived_virtual_row_ids
: the order of
rows across all pages (for front-end paging) after filtering and
sorting.
- derived_viewport_indices
/ derived_viewport_row_ids
: the order of
rows on the currently visible page.
- selected_rows
/ selected_row_ids
: when row_selectable
is
enabled and there is a checkbox next to each row, these are the
selected rows. Note that even filtered-out or paged-out rows can remain
selected.
- derived_virtual_selected_rows
/ derived_virtual_selected_row_ids
:
the set of selected rows after filtering and sorting, across all pages
- derived_viewport_selected_rows
/
derived_viewport_selected_row_ids
: the set of selected rows on the
currently visible page.
Often several of these properties contain the same data, but in other
cases it’s important to choose the right one for the specific user
interaction you have in mind. Do you want to respond to selected rows
even when they’re not on the current page? Even when they’re filtered
out?
There are also properties that reference specific cells in the table.
Along with the row and column indices, these include the row and
column IDs of the cell:
- active_cell
: this is the data cell the user has put the cursor on,
by clicking and/or arrow keys. It’s a dictionary with keys:
- row
: the row index (integer) - may be affected by sorting,
filtering, or paging transformations.
- column
: the column index (integer)
- row_id
: the id
field of the row, which always stays with it
during transformations.
- column_id
: the id
field of the column.
- start_cell
: if the user selects multiple cells, by shift-click or
shift-arrow-keys, this is where the selection was initiated. Has the
same form as active_cell
, and usually the same value although after
selecting a region the user can change active_cell
by pressing
<tab>
or <enter>
to cycle through the selected cells.
- end_cell
: the corner of the selected region opposite start_cell
.
Also has the same form as active_cell
.
- selected_cells
: an array of dicts, each one with the form of
active_cell
, listing each selected cell.
Here’s the same example, plus active cell highlighting, implemented
using row IDs.
One advantage here is that we don’t need to pass the entire data set
back, we can just pass the IDs.
Even the full set of IDs is only necessary in order to sync with
sorting and filtering.
from dash import Dash, dash_table, dcc, html, Input, Output, callback
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv')
# add an id column and set it as the index
# in this case the unique ID is just the country name, so we could have just
# renamed 'country' to 'id' (but given it the display name 'country'), but
# here it's duplicated just to show the more general pattern.
df['id'] = df['country']
df.set_index('id', inplace=True, drop=False)
app = Dash(__name__)
app.layout = html.Div([
dash_table.DataTable(
id='datatable-row-ids',
columns=[
{'name': i, 'id': i, 'deletable': True} for i in df.columns
# omit the id column
if i != 'id'
],
data=df.to_dict('records'),
editable=True,
filter_action="native",
sort_action="native",
sort_mode='multi',
row_selectable='multi',
row_deletable=True,
selected_rows=[],
page_action='native',
page_current= 0,
page_size= 10,
),
html.Div(id='datatable-row-ids-container')
])
@callback(
Output('datatable-row-ids-container', 'children'),
Input('datatable-row-ids', 'derived_virtual_row_ids'),
Input('datatable-row-ids', 'selected_row_ids'),
Input('datatable-row-ids', 'active_cell'))
def update_graphs(row_ids, selected_row_ids, active_cell):
# When the table is first rendered, `derived_virtual_data` and
# `derived_virtual_selected_rows` will be `None`. This is due to an
# idiosyncrasy in Dash (unsupplied properties are always None and Dash
# calls the dependent callbacks when the component is first rendered).
# So, if `rows` is `None`, then the component was just rendered
# and its value will be the same as the component's dataframe.
# Instead of setting `None` in here, you could also set
# `derived_virtual_data=df.to_rows('dict')` when you initialize
# the component.
selected_id_set = set(selected_row_ids or [])
if row_ids is None:
dff = df
# pandas Series works enough like a list for this to be OK
row_ids = df['id']
else:
dff = df.loc[row_ids]
active_row_id = active_cell['row_id'] if active_cell else None
colors = ['#FF69B4' if id == active_row_id
else '#7FDBFF' if id in selected_id_set
else '#0074D9'
for id in row_ids]
return [
dcc.Graph(
id=column + '--row-ids',
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(debug=True)
This feature is available in Dash 2.7 and later.
By default, the filter fields, located above each column, display the placeholder text filter data...
.
Update filter placeholder text by setting placeholder_text
on filter_options
on the table or individual columns.
In this example, we update the placeholder_text
for the table to be Filter column...
:
from dash import Dash, dash_table
import pandas as pd
df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv"
)[["country", "pop", "continent", "lifeExp"]]
app = Dash(__name__)
app.layout = dash_table.DataTable(
df.to_dict("records"),
[{"name": i, "id": i} for i in df.columns],
filter_action="native",
filter_options={"placeholder_text": "Filter column..."},
page_size=10,
)
if __name__ == "__main__":
app.run(debug=True)