Skip to content

Commit 4a059a8

Browse files
authored
Implement an ITable component for Dash (#358)
1 parent 08df6e2 commit 4a059a8

File tree

24 files changed

+5625
-17
lines changed

24 files changed

+5625
-17
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
pandas-version: pre
5454
polars: true
5555
- python-version: "3.13"
56-
uninstall_jinja2: true
56+
uninstall_jinja2_and_dash: true
5757
runs-on: ubuntu-20.04
5858
steps:
5959
- name: Checkout
@@ -91,9 +91,9 @@ jobs:
9191
if: matrix.python-version != '3.7'
9292
run: pip install "shiny>=1.0"
9393

94-
- name: Uninstall jinja2
95-
if: matrix.uninstall_jinja2
96-
run: pip uninstall jinja2 -y
94+
- name: Uninstall jinja2 and dash
95+
if: matrix.uninstall_jinja2_and_dash
96+
run: pip uninstall jinja2 dash -y
9797

9898
- name: Install a Jupyter Kernel
9999
run: python -m ipykernel install --name itables --user

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ node_modules
2828
dt_bundle.js
2929
dt_bundle.css
3030

31-
# Streamlit package
31+
# Dash component
32+
src/itables_for_dash/*.js*
33+
src/itables_for_dash/_imports_.py
34+
35+
# Streamlit component
3236
src/itables/itables_for_streamlit
3337

3438
# Jupyter Widget

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Install pre-commit hooks via
22
# pre-commit install
33

4+
exclude: ^src/itables_for_dash/ITable.py$ # auto-generated
45
repos:
56

67
- repo: https://github.com/pre-commit/pre-commit-hooks

apps/dash/1_display_only.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from dash import Dash, html
2+
3+
from itables.dash import ITable
4+
from itables.sample_dfs import get_countries
5+
6+
app = Dash(__name__)
7+
8+
df = get_countries(html=False)
9+
10+
app.layout = html.Div(
11+
[
12+
html.H1("ITables in a Dash application"),
13+
ITable(id="my_dataframe", df=df, caption="A DataFrame displayed with ITables"),
14+
]
15+
)
16+
17+
if __name__ == "__main__":
18+
app.run(debug=True)

apps/dash/2_selected_rows.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from dash import Dash, Input, Output, callback, html
2+
3+
from itables.dash import ITable
4+
from itables.sample_dfs import get_countries
5+
6+
app = Dash(__name__)
7+
8+
df = get_countries(html=False)
9+
10+
app.layout = html.Div(
11+
[
12+
html.H1("ITables in a Dash application"),
13+
ITable(
14+
id="my_dataframe",
15+
df=df,
16+
caption="A DataFrame displayed with ITables",
17+
select=True,
18+
),
19+
html.Div(id="output"),
20+
]
21+
)
22+
23+
24+
@callback(
25+
Output("output", "children"),
26+
Input("my_dataframe", "selected_rows"),
27+
)
28+
def show_selection(selected_rows):
29+
return f"Selected rows: {selected_rows}"
30+
31+
32+
if __name__ == "__main__":
33+
app.run(debug=True)

apps/dash/3_update_table.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
This is an example Dash application that uses the ITable component.
3+
4+
Launch the app by running `python app.py`.
5+
"""
6+
7+
import logging
8+
9+
from dash import Dash, Input, Output, State, callback, callback_context, dcc, html
10+
11+
from itables.dash import ITable, ITableOutputs, updated_itable_outputs
12+
from itables.sample_dfs import get_countries
13+
14+
logging.basicConfig(level=logging.INFO)
15+
logger = logging.getLogger(__name__)
16+
logger.setLevel(logging.DEBUG)
17+
18+
app = Dash(__name__)
19+
20+
df = get_countries(html=False)
21+
22+
23+
app.layout = html.Div(
24+
[
25+
html.H1("DataTable in a Dash application"),
26+
dcc.Checklist(
27+
["Select", "Buttons", "HTML"],
28+
["Select"],
29+
id="checklist",
30+
),
31+
dcc.Input(id="caption", value="table caption"),
32+
ITable(id="my_dataframe", df=df),
33+
html.Div(id="output"),
34+
]
35+
)
36+
37+
38+
@callback(
39+
ITableOutputs("my_dataframe"),
40+
[
41+
Input("checklist", "value"),
42+
Input("caption", "value"),
43+
State("my_dataframe", "selected_rows"),
44+
State("my_dataframe", "dt_args"),
45+
],
46+
)
47+
def update_table(checklist, caption, selected_rows, dt_args):
48+
if checklist is None:
49+
checklist = []
50+
51+
kwargs = {}
52+
53+
# When df=None and when the dt_args don't change, the table is not updated
54+
if callback_context.triggered_id == "checklist":
55+
kwargs["df"] = get_countries(html="HTML" in checklist)
56+
57+
kwargs["select"] = "Select" in checklist
58+
if "Buttons" in checklist:
59+
kwargs["buttons"] = ["copyHtml5", "csvHtml5", "excelHtml5"]
60+
61+
return updated_itable_outputs(
62+
caption=caption, selected_rows=selected_rows, current_dt_args=dt_args, **kwargs
63+
)
64+
65+
66+
@callback(
67+
Output("output", "children"),
68+
Input("my_dataframe", "selected_rows"),
69+
)
70+
def show_selection(selected_rows):
71+
return f"Selected rows: {selected_rows}"
72+
73+
74+
if __name__ == "__main__":
75+
app.run(debug=True)

docs/changelog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ ITables ChangeLog
44
2.3.0-dev
55
---------
66

7+
**Added**
8+
- ITable now has a component for Dash! You can render your Python DataFrames in your Dash application with `from itables.dash import ITable` ([#245](https://github.com/mwouts/itables/issues/245))
9+
10+
711
**Changed**
812
- We have upgraded `datatables.net-dt==2.2.2` and `datatables.net-select-dt==3.0.0`, and the dependency of both the Jupyter widget and the Streamlit component.
913

docs/dash.md

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,76 @@
1+
---
2+
jupytext:
3+
formats: md:myst
4+
notebook_metadata_filter: -jupytext.text_representation.jupytext_version
5+
text_representation:
6+
extension: .md
7+
format_name: myst
8+
format_version: 0.13
9+
kernelspec:
10+
display_name: itables
11+
language: python
12+
name: itables
13+
---
14+
115
# Dash
216

3-
ITables does not have a [Dash](https://dash.plotly.com/) component, but might get one in the future.
4-
You are welcome to subscribe or contribute to [#245](https://github.com/mwouts/itables/issues/245).
17+
ITables includes a Dash component since v2.3.0.
18+
19+
## Displaying a DataFrame
20+
21+
If you wish to display a DataFrame which content is fixed (not reacting to the other controls in the application), you just need to import `ITable` from `itables.dash` and add it to your layout like here:
22+
23+
```{include} ../apps/dash/1_display_only.py
24+
:code: python
25+
```
26+
27+
## Selected rows
28+
29+
Listening to the selected rows is simply done by adding `select=True` to the `ITable` call, and then implementing a callback on `Input("my_dataframe", "selected_rows")`.
30+
31+
```{include} ../apps/dash/2_selected_rows.py
32+
:code: python
33+
```
34+
35+
## Updating the DataFrame
36+
37+
The `ITable` fonction returns an `ITableComponent` that has many properties. These properties (data, columns, selected rows etc) need to be updated in a consistent way. Therefore we recommend that you list the outputs with `ITableOutputs("my_dataframe")` in your callback, and update them with `updated_itable_outputs` which takes the same arguments as `show`, e.g. `df`, `caption`,`selected_rows`, etc, like in the below (extracted from this [example app](https://github.com/mwouts/itables/tree/main/apps/dash/3_update_table.py)):
38+
39+
```python
40+
from itables.dash import ITable, ITableOutputs, updated_itable_outputs
41+
42+
# (...)
43+
44+
@callback(
45+
ITableOutputs("my_dataframe"),
46+
[
47+
Input("checklist", "value"),
48+
Input("caption", "value"),
49+
State("my_dataframe", "selected_rows"),
50+
State("my_dataframe", "dt_args"),
51+
],
52+
)
53+
def update_table(checklist, caption, selected_rows, dt_args):
54+
if checklist is None:
55+
checklist = []
56+
57+
kwargs = {}
58+
59+
# When df=None and when the dt_args don't change, the table is not updated
60+
if callback_context.triggered_id == "checklist":
61+
kwargs["df"] = get_countries(html="HTML" in checklist)
62+
63+
kwargs["select"] = "Select" in checklist
64+
if "Buttons" in checklist:
65+
kwargs["buttons"] = ["copyHtml5", "csvHtml5", "excelHtml5"]
66+
67+
return updated_itable_outputs(
68+
caption=caption, selected_rows=selected_rows, current_dt_args=dt_args, **kwargs
69+
)
70+
```
71+
72+
## Limitations
73+
74+
Compared to `show`, the `ITable` component has the same limitations as the [Jupyter Widget](widget.md#limitations)
75+
or the [Streamlit component](streamlit.md#limitations),
76+
e.g. structured headers are not available, you can't pass JavaScript callback, etc.

docs/references.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,38 @@
11
# References
22

3-
## DataTables
3+
ITables is a wrapper for the [datatables-net](https://datatables.net) Javascript library, for Python. That library is developped by [SpryMedia](https://sprymedia.co.uk/) and made available under a MIT license. It has an extensive [documentation](https://datatables.net/manual/), as well as a large set of [examples](https://datatables.net/examples/index).
44

5-
- DataTables is a plug-in for the jQuery Javascript library. It has a great [documentation](https://datatables.net/manual/), and a large set of [examples](https://datatables.net/examples/index).
6-
- The R package [DT](https://rstudio.github.io/DT/) uses [DataTables](https://datatables.net/) as the underlying library for both R notebooks and Shiny applications. In addition to the standard functionalities of the library (display, sort, filtering and row selection), RStudio seems to have implemented cell edition.
5+
## ITables and its alternatives
76

8-
## Alternatives
7+
### Jupyter Widgets
98

10-
ITables uses basic Javascript. It is not a Jupyter widget, which means that it does not allows you to **edit** the content of the dataframe.
9+
In Jupyter or VS Code, ITables can render your dataframes using either HTML (with `init_notebook_mode(all_interactive=True)` or `show`) or the [`ITable` widget](widget.md).
1110

12-
If you are looking for Jupyter widgets, have a look at
11+
Other Jupyter widgets that let you render a dataframe in Jupyter are
1312
- [QGrid](https://github.com/quantopian/qgrid) by Quantopian
1413
- [IPyaggrid](https://dgothrek.gitlab.io/ipyaggrid/) by Louis Raison and Olivier Borderies
1514
- [IPySheet](https://github.com/QuantStack/ipysheet) by QuantStack.
1615

17-
If you are looking for a table component that will fit in Dash applications, see [datatable by Dash](https://github.com/plotly/dash-table/).
16+
### Dash component
1817

19-
Please also checkout [D-Tale](https://github.com/man-group/dtale) for exploring your Python DataFrames in the browser, using a local server.
18+
Since ITables v2.3.0 you can use our [`ITable` component](dash.md) in Dash applications.
19+
20+
Alternatives for rendering DataFrames in Dash are
21+
- [Dash DataTable](https://dash.plotly.com/datatable)
22+
- [Dash AG Grid](https://dash.plotly.com/dash-ag-grid).
23+
24+
### Streamlit
25+
26+
In ITables v2.1.0 we added the [`interactive_table` component](streamlit.md) that can be used in Streamlit applications.
27+
28+
Alternative for rendering DataFrames in Streamlit are
29+
- [`st.dataframe`](https://docs.streamlit.io/develop/api-reference/data/st.dataframe)
30+
- [`streamlit-aggrid`](https://github.com/PablocFonseca/streamlit-aggrid).
31+
32+
### DT in R
33+
34+
The R package [DT](https://rstudio.github.io/DT/) is a wrapper for [DataTables](https://datatables.net/) that you can use both in R notebooks and R Shiny applications.
35+
36+
### D-Tale
37+
38+
[D-Tale](https://github.com/man-group/dtale) lets you explore your Python DataFrames in the browser, using a local server.

packages/itables_for_dash/.babelrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"presets": ["@babel/preset-env", "@babel/preset-react"],
3+
"env": {
4+
"production": {
5+
"plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel"]
6+
},
7+
"development": {
8+
"plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel"]
9+
},
10+
"test": {
11+
"plugins": ["@babel/plugin-proposal-object-rest-spread", "styled-jsx/babel-test"]
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)