Skip to content

Commit b6c9959

Browse files
committed
Implement a Jupyter Widget for ITables (#319)
Selected rows in the widget, in Streamlit and in Shiny (#250) Version 2.2.0
1 parent 20546b2 commit b6c9959

38 files changed

+1275
-111
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ jobs:
8787
if: matrix.polars
8888
run: pip install -e .[polars]
8989

90+
- name: Install shiny
91+
if: matrix.python-version != '3.7'
92+
run: pip install "shiny>=1.0"
93+
9094
- name: Uninstall jinja2
9195
if: matrix.uninstall_jinja2
9296
run: pip uninstall jinja2 -y

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ dt_bundle.css
3030

3131
# Streamlit package
3232
src/itables/itables_for_streamlit
33+
34+
# Jupyter Widget
35+
src/itables/widget/static

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ repos:
3232
rev: v1.16.2
3333
hooks:
3434
- id: jupytext
35-
exclude: dt_for_itables/
35+
exclude: packages/
3636
types: ["markdown"]
3737
args: ["--pipe", "isort {} --treat-comment-as-code '# %%' --profile black", "--pipe", "black", "--check", "ruff check {} --ignore E402"]
3838
additional_dependencies:

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[![Conda Version](https://img.shields.io/conda/vn/conda-forge/itables.svg)](https://anaconda.org/conda-forge/itables)
77
[![pyversions](https://img.shields.io/pypi/pyversions/itables.svg)](https://pypi.python.org/pypi/itables)
88
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9+
[![Jupyter Widget](https://img.shields.io/badge/Jupyter-Widget-F37626.svg?style=flat&logo=Jupyter)](https://mwouts.github.io/itables/ipywidgets.html)
910
[![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_red.svg)](https://itables.streamlit.app)
1011

1112
This packages changes how Pandas and Polars DataFrames are rendered in Jupyter Notebooks.
@@ -48,7 +49,7 @@ and then render any DataFrame as an interactive table that you can sort, search
4849
If you prefer to render only selected DataFrames as interactive tables, use `itables.show` to show just one Series or DataFrame as an interactive table:
4950
![show](docs/show_df.png)
5051

51-
Since `itables==1.0.0`, the [jQuery](https://jquery.com/) and [DataTables](https://datatables.net/) libraries and CSS
52+
Since ITables v1.0, the [jQuery](https://jquery.com/) and [DataTables](https://datatables.net/) libraries and CSS
5253
are injected in the notebook when you execute `init_notebook_mode` with its default argument `connected=False`.
5354
Thanks to this the interactive tables will work even without a connection to the internet.
5455

@@ -63,6 +64,7 @@ You can also use ITables in [Quarto](https://mwouts.github.io/itables/quarto.htm
6364

6465
ITables works well in VS Code, both in Jupyter Notebooks and in interactive Python sessions.
6566

66-
Last but not least, ITables is also available in
67-
[Streamlit](https://mwouts.github.io/itables/streamlit.html) or
68-
[Shiny](https://mwouts.github.io/itables/shiny.html) applications.
67+
Last but not least, ITables is also available as
68+
- a [Jupyter Widget](https://mwouts.github.io/itables/ipywidgets.html)
69+
- a [Streamlit](https://mwouts.github.io/itables/streamlit.html) component,
70+
- and it also works in [Shiny](https://mwouts.github.io/itables/shiny.html) applications.

docs/changelog.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
ITables ChangeLog
22
=================
33

4+
2.2.0 (2024-09-22)
5+
------------------
6+
7+
**Added**
8+
- ITables has a Jupyter Widget ([#267](https://github.com/mwouts/itables/issues/267)). Our widget was developed and packaged using [AnyWidget](https://anywidget.dev/) which I highly recommend!
9+
- The selected rows are now available in the apps. Use either the `selected_rows` attribute of the `ITable` widget, the returned value of the Streamlit `interactive_table` component, or the `{table_id}_selected_rows` input in Shiny ([#208](https://github.com/mwouts/itables/issues/208), [#250](https://github.com/mwouts/itables/issues/250))
10+
- ITables works offline in Shiny applications too - just add `ui.HTML(init_itables())` to your application
11+
12+
**Changed**
13+
- The `tableId` argument of `to_html_datatable` has been renamed to `table_id`
14+
15+
**Fixed**
16+
- The dependencies of the Streamlit component have been updated ([#320](https://github.com/mwouts/itables/issues/320))
17+
18+
419
2.1.5 (2024-09-08)
520
------------------
621

@@ -10,7 +25,7 @@ ITables ChangeLog
1025
- We have improved the function that determines whether a dark theme is being used ([#294](https://github.com/mwouts/itables/issues/294))
1126
- We have adjusted the generation of the Polars sample dataframes to fix the CI ([Polars-18130](https://github.com/pola-rs/polars/issues/18130))
1227
- The test on the Shiny app fallbacks to `ui.nav_panel` when `ui.nav` is not available
13-
- The dependencies of the streamlit component have been updated ([#313](https://github.com/mwouts/itables/issues/313), [#315](https://github.com/mwouts/itables/issues/315))
28+
- The dependencies of the Streamlit component have been updated ([#313](https://github.com/mwouts/itables/issues/313), [#315](https://github.com/mwouts/itables/issues/315))
1429

1530

1631
2.1.4 (2024-07-03)
@@ -35,7 +50,7 @@ ITables ChangeLog
3550
an automatic horizontal scrolling in Jupyter, Jupyter Book and also Streamlit if the table is too wide ([#282](https://github.com/mwouts/itables/pull/282)).
3651

3752
**Fixed**
38-
- The dependencies of the streamlit components have been updated to fix a vulnerability in `ws` ([Alert 1](https://github.com/mwouts/itables/security/dependabot/1))
53+
- The dependencies of the Streamlit components have been updated to fix a vulnerability in `ws` ([Alert 1](https://github.com/mwouts/itables/security/dependabot/1))
3954

4055

4156
2.1.1 (2024-06-08)

docs/extensions.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,12 @@ only the selected rows are exported
269269
```{code-cell}
270270
:tags: [full-width]
271271
272-
show(df, select=True, buttons=["copyHtml5", "csvHtml5", "excelHtml5"])
272+
show(
273+
df,
274+
select=True,
275+
selected_rows=[2, 4, 5],
276+
buttons=["copyHtml5", "csvHtml5", "excelHtml5"],
277+
)
273278
```
274279

275280
```{tip}
@@ -283,8 +288,11 @@ however cell selection is not taken into account when exporting the data.
283288
```
284289

285290
```{tip}
286-
At the moment it is not possible to get the selected rows back in Python. Please subscribe to
287-
[#250](https://github.com/mwouts/itables/issues/250) to get updates on this topic.
291+
It is possible to get the updated `selected_rows` back in Python but for this you will have to use,
292+
instead of `show`, either
293+
- the `ITable` [Jupyter Widget](ipywidgets.md)
294+
- the `interactive_table` [Streamlit component](streamlit.md)
295+
- or `DT` in a [Shiny app](shiny.md).
288296
```
289297

290298
## RowGroup

docs/ipywidgets.md

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,118 @@
1-
# IPyWidgets
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+
---
214

3-
ITables does not come as a [Jupyter Widget](https://ipywidgets.readthedocs.io) at the moment.
4-
You are welcome to subscribe or contribute to [#267](https://github.com/mwouts/itables/issues/267).
15+
# Jupyter Widget
16+
17+
ITables is available as a [Jupyter Widget](https://ipywidgets.readthedocs.io) since v2.2.
18+
19+
## The `ITable` widget
20+
21+
The `ITable` widget has a few dependencies (essentially [AnyWidget](https://anywidget.dev),
22+
a great widget development framework!) that you can install with
23+
```bash
24+
pip install itables[widget]
25+
```
26+
27+
The `ITable` class accepts the same arguments as the `show` method, but
28+
the `df` argument is optional.
29+
30+
```{code-cell}
31+
from itables.sample_dfs import get_dict_of_test_dfs
32+
from itables.widget import ITable
33+
34+
df = get_dict_of_test_dfs()["int_float_str"]
35+
36+
table = ITable(df, selected_rows=[0, 2, 5], select=True)
37+
table
38+
```
39+
40+
## The `selected_rows` traits
41+
42+
The `selected_rows` attribute of the `ITable` object provides a view on the
43+
rows that have been selected in the table (remember to pass `select=True`
44+
to activate the row selection). You can use it to either retrieve
45+
or change the current row selection:
46+
47+
```{code-cell}
48+
table.selected_rows
49+
```
50+
51+
```{code-cell}
52+
table.selected_rows = [3, 4]
53+
```
54+
55+
## The `df` property
56+
57+
Use it to retrieve the table data:
58+
59+
```{code-cell}
60+
table.df.iloc[table.selected_rows]
61+
```
62+
63+
or to update it
64+
65+
```{code-cell}
66+
table.df = df.head(6)
67+
```
68+
69+
```{tip}
70+
`ITable` will raise an `IndexError` if the `selected_rows` are not consistent with the
71+
updated data. If you need to update the two simultaneously, use `table.update(df, selected_rows=...)`, see below.
72+
```
73+
74+
## The `caption`, `style` and `classes` traits
75+
76+
You can update these traits from Python, e.g.
77+
78+
```{code-cell}
79+
table.caption = "numbers and strings"
80+
```
81+
82+
## The `update` method
83+
84+
Last but not least, you can update the `ITable` arguments simultaneously using the `update` method:
85+
86+
```{code-cell}
87+
table.update(df.head(20), selected_rows=[7, 8])
88+
```
89+
90+
## Limitations
91+
92+
Compared to `show`, the `ITable` widget has the same limitations as the [Streamlit component](streamlit.md#limitations),
93+
e.g. structured headers are not available, you can't pass JavaScript callback, etc.
94+
95+
The good news is that if you only want to _display_ the table, you do not need
96+
the `ITables` widget. Below is an example in which we use `show` to display a different
97+
table depending on the value of a drop-down component:
98+
99+
```python
100+
import ipywidgets as widgets
101+
from itables import show
102+
from itables.sample_dfs import get_dict_of_test_dfs
103+
104+
def use_show_in_interactive_output(table_name: str):
105+
show(
106+
sample_dfs[table_name],
107+
caption=table_name,
108+
)
109+
110+
sample_dfs = get_dict_of_test_dfs()
111+
table_selector = widgets.Dropdown(options=sample_dfs.keys(), value="int_float_str")
112+
113+
out = widgets.interactive_output(
114+
use_show_in_interactive_output, {"table_name": table_selector}
115+
)
116+
117+
widgets.VBox([table_selector, out])
118+
```

docs/quick_start.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ kernelspec:
2020
[![Conda Version](https://img.shields.io/conda/vn/conda-forge/itables.svg)](https://anaconda.org/conda-forge/itables)
2121
[![pyversions](https://img.shields.io/pypi/pyversions/itables.svg)](https://pypi.python.org/pypi/itables)
2222
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
23+
[![Jupyter Widget](https://img.shields.io/badge/Jupyter-Widget-F37626.svg?style=flat&logo=Jupyter)](ipywidgets.md)
2324
[![Streamlit App](https://static.streamlit.io/badges/streamlit_badge_black_red.svg)](https://itables.streamlit.app)
2425
<a class="github-button" href="https://github.com/mwouts/itables" data-icon="octicon-star" data-show-count="true"></a>
2526
<script src="https://buttons.github.io/buttons.js"></script>

docs/shiny.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ You can use ITables in Web applications generated with [Shiny](https://shiny.rst
55
from shiny import ui
66

77
from itables.sample_dfs import get_countries
8-
from itables.shiny import DT
8+
from itables.shiny import DT, init_itables
99

10-
df = get_countries(html=False)
11-
ui.HTML(DT(df))
10+
# Load the datatables library and css from the ITables package
11+
# (use connected=True if you prefer to load it from the internet)
12+
ui.HTML(init_itables(connected=False))
13+
14+
# Render the table with DT
15+
ui.HTML(DT(get_countries(html=False)))
1216
```
1317

18+
If you enable row selection and set an id on your table, e.g. `DT(df, table_id="my_table", select=True)` then
19+
ITables will provide the list of selected rows at `input.my_table_selected_rows()` (replace `my_table` with your
20+
own table id).
21+
1422
See also our [tested examples](https://github.com/mwouts/itables/tree/main/tests/sample_python_apps).

docs/streamlit.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ We have a sample application available at https://itables.streamlit.app (source
1313
style="height: 600px; width: 100%;"></iframe>
1414
```
1515

16+
## Selected rows
17+
18+
This feature was added in ITables v2.2.0.
19+
20+
Use the `selected_rows: list[int]` argument from `interactive_table` to
21+
select rows when the table is first displayed. Add `select=True` to let the user modify the selection. Then, the `interactive_table` component returns a dict, with a key `"selected_rows"` that points to the updated selection.
22+
1623
## Limitations
1724

1825
In most cases, you will be able to use `interactive_table` in a
@@ -42,9 +49,3 @@ A sample application is available at https://to-html-datatable.streamlit.app (so
4249
<iframe src="https://to-html-datatable.streamlit.app?embed=true"
4350
style="height: 600px; width: 100%;"></iframe>
4451
```
45-
46-
## Future developments
47-
48-
ITables' Streamlit component might see the following developments in the future
49-
- Return the selected cells
50-
- Make the table editable (will require a DataTable [editor license](https://editor.datatables.net/purchase/))

0 commit comments

Comments
 (0)