├── .gitattributes ├── .github ├── codecov.yml └── workflows │ ├── comment-pr.yml │ ├── continuous-integration.yml │ ├── publish-book.yml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── LICENSE ├── README.md ├── apps ├── dash │ ├── 1_display_only.py │ ├── 2_selected_rows.py │ ├── 3_update_table.py │ └── assets │ │ └── style.css ├── marimo │ ├── html.py │ └── widget.py ├── shiny │ ├── README.md │ ├── itable_widget │ │ ├── app-core.py │ │ ├── app.py │ │ ├── requirements.txt │ │ └── rsconnect-python │ │ │ └── itable_widget.json │ └── itables_DT │ │ ├── app-core.py │ │ ├── app.py │ │ ├── requirements.txt │ │ └── rsconnect-python │ │ └── itables_DT.json └── streamlit │ ├── itables_app.py │ └── requirements.txt ├── binder ├── labconfig │ └── default_setting_overrides.json ├── postBuild └── requirements.txt ├── docs ├── _config.yml ├── _toc.yml ├── apps │ ├── dash.md │ ├── html.md │ ├── marimo.md │ ├── notebook.md │ ├── quarto.md │ ├── shiny.md │ ├── streamlit.md │ └── widget.md ├── changelog.md ├── contributing.md ├── css.md ├── custom_extensions.md ├── dark_mode.md ├── developing.md ├── df_example.png ├── downsampling.md ├── formatting.md ├── images │ ├── code.png │ ├── colab.png │ ├── html.png │ ├── lab.png │ ├── notebook.png │ └── pycharm.png ├── options │ ├── allow_html.md │ ├── buttons.md │ ├── caption.md │ ├── classes.md │ ├── column_defs.md │ ├── column_filters.md │ ├── colvis.md │ ├── fixed_columns.md │ ├── footer.md │ ├── keys.md │ ├── layout.md │ ├── length_menu.md │ ├── options.md │ ├── order.md │ ├── paging.md │ ├── row_group.md │ ├── scroll_x.md │ ├── scroll_y.md │ ├── search.md │ ├── search_builder.md │ ├── search_panes.md │ ├── select.md │ ├── show_index.md │ ├── state_save.md │ ├── style.md │ └── text_in_header_can_be_selected.md ├── pandas_dataframes.md ├── pandas_style.md ├── polars_dataframes.md ├── py │ ├── README.md │ ├── apps │ │ ├── dash.py │ │ ├── html.py │ │ ├── marimo.py │ │ ├── notebook.py │ │ └── widget.py │ ├── contributing.py │ ├── css.py │ ├── custom_extensions.py │ ├── dark_mode.py │ ├── downsampling.py │ ├── formatting.py │ ├── options │ │ ├── allow_html.py │ │ ├── buttons.py │ │ ├── caption.py │ │ ├── classes.py │ │ ├── column_defs.py │ │ ├── column_filters.py │ │ ├── colvis.py │ │ ├── fixed_columns.py │ │ ├── footer.py │ │ ├── keys.py │ │ ├── layout.py │ │ ├── length_menu.py │ │ ├── options.py │ │ ├── order.py │ │ ├── paging.py │ │ ├── row_group.py │ │ ├── scroll_x.py │ │ ├── scroll_y.py │ │ ├── search.py │ │ ├── search_builder.py │ │ ├── search_panes.py │ │ ├── select.py │ │ ├── show_index.py │ │ ├── state_save.py │ │ ├── style.py │ │ └── text_in_header_can_be_selected.py │ ├── pandas_dataframes.py │ ├── pandas_style.py │ ├── polars_dataframes.py │ ├── quick_start.py │ └── troubleshooting.py ├── quarto │ ├── quarto_html.qmd │ └── quarto_revealjs.qmd ├── quick_start.md ├── references.md ├── show_df.png ├── supported_editors.md └── troubleshooting.md ├── environment.yml ├── packages ├── dt_for_itables │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── index.css │ │ ├── index.d.ts │ │ └── index.js ├── itables_anywidget │ ├── README.md │ ├── js │ │ ├── widget.css │ │ └── widget.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── itables_for_dash │ ├── .babelrc │ ├── package-lock.json │ ├── package.json │ ├── src │ │ └── lib │ │ │ ├── LazyLoader.js │ │ │ ├── components │ │ │ └── ITable.react.js │ │ │ ├── fragments │ │ │ └── ITable.react.js │ │ │ └── index.js │ └── webpack.config.js ├── itables_for_streamlit │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ └── index.tsx │ └── tsconfig.json ├── package-lock.json └── package.json ├── pyproject.toml ├── src ├── itables │ ├── __init__.py │ ├── dash.py │ ├── datatables_format.py │ ├── downsample.py │ ├── html │ │ ├── datatables_template.html │ │ └── init_datatables.html │ ├── interactive.py │ ├── javascript.py │ ├── logo │ │ ├── loading.svg │ │ ├── logo.svg │ │ ├── text.svg │ │ ├── wide.svg │ │ └── with_text.svg │ ├── options.py │ ├── py.typed │ ├── sample_dfs.py │ ├── samples │ │ ├── countries.csv │ │ ├── indicators.csv │ │ └── population.csv │ ├── shiny.py │ ├── streamlit.py │ ├── typing.py │ ├── utils.py │ ├── version.py │ └── widget │ │ └── __init__.py └── itables_for_dash │ ├── ITable.py │ ├── __init__.py │ ├── properties.py │ └── py.typed └── tests ├── __init__.py ├── conftest.py ├── test_changelog.py ├── test_connected_notebook_is_small.py ├── test_datatables_format.py ├── test_documentation_notebooks_run.py ├── test_downsample.py ├── test_extension_arguments.py ├── test_html_in_table_header.py ├── test_init.py ├── test_itables_for_dash.py ├── test_javascript.py ├── test_keys_to_be_evaluated.py ├── test_marimo.py ├── test_notebook.ipynb ├── test_pandas_style.py ├── test_polars.py ├── test_sample_dfs.py ├── test_shiny.py ├── test_streamlit.py ├── test_typing.py ├── test_update_samples.py └── test_widget.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-documentation 2 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | token: cc72f10a-bed8-4475-a793-62b028610aa3 3 | notify: 4 | wait_for_ci: true 5 | 6 | coverage: 7 | status: 8 | project: 9 | default: false 10 | tests: 11 | paths: 12 | - "tests/" 13 | target: 99% 14 | source: 15 | paths: 16 | - "src/" 17 | target: 93% 18 | threshold: 0.002 19 | patch: 20 | default: 21 | target: 80% 22 | -------------------------------------------------------------------------------- /.github/workflows/comment-pr.yml: -------------------------------------------------------------------------------- 1 | name: Comment PR 2 | 3 | on: 4 | pull_request_target: 5 | 6 | permissions: 7 | pull-requests: write 8 | 9 | jobs: 10 | comment-pr: 11 | runs-on: ubuntu-latest 12 | name: Comment PR 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Comment PR 18 | uses: thollander/actions-comment-pull-request@v2 19 | with: 20 | message: | 21 | Thank you for making this pull request. 22 | 23 | Did you know? You can try it on Binder: [![Binder:lab](https://img.shields.io/badge/binder-jupyterlab-0172B2.svg)](https://mybinder.org/v2/gh/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}?urlpath=lab/tree/docs/quick_start.md). 24 | 25 | Also, the version of ITables developed in this PR can be installed with `pip`: 26 | ``` 27 | pip install git+${{ github.event.pull_request.head.repo.clone_url }}@${{ github.event.pull_request.head.ref }} 28 | ``` 29 | (this requires `nodejs`, see more at [Developing ITables](https://mwouts.github.io/itables/developing.html)) 30 | comment_tag: binder_link 31 | -------------------------------------------------------------------------------- /.github/workflows/publish-book.yml: -------------------------------------------------------------------------------- 1 | # Only run this when the main branch changes 2 | on: 3 | push: 4 | branches: 5 | - main 6 | # If your git repository has the Jupyter Book within some-subfolder next to 7 | # unrelated files, you can make this run only if a file within that specific 8 | # folder has been modified. 9 | # 10 | # paths: 11 | # - docs/** 12 | 13 | # This job installs dependencies, builds the book, and pushes it to `gh-pages` 14 | jobs: 15 | deploy-book: 16 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 17 | permissions: 18 | pages: write # to deploy to Pages 19 | id-token: write # to verify the deployment originates from an appropriate source 20 | 21 | # Deploy to the github-pages environment 22 | environment: 23 | name: github-pages 24 | url: ${{ steps.deployment.outputs.page_url }} 25 | 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Set up Node 31 | uses: actions/setup-node@v4 32 | 33 | - name: Set up Python 3.11 34 | uses: actions/setup-python@v5 35 | with: 36 | python-version: 3.11 37 | 38 | - name: Install dependencies 39 | run: | 40 | pip install jupyter-book sphinxext-rediraffe 41 | pip install matplotlib # Pandas style 42 | pip install .[all] 43 | 44 | - name: Create a kernel 45 | run: | 46 | pip install ipykernel 47 | python -m ipykernel install --name itables --user 48 | 49 | - name: Install Quarto 50 | uses: quarto-dev/quarto-actions/setup@v2 51 | 52 | - name: Render the quarto examples 53 | run: | 54 | for qmd_file in `ls docs/quarto/*.qmd`; do quarto render ${qmd_file}; done 55 | 56 | # Build the book 57 | - name: Build the book 58 | run: | 59 | jupyter-book build docs 60 | 61 | # Upload the book's HTML as an artifact 62 | - name: Upload artifact 63 | uses: actions/upload-pages-artifact@v3 64 | with: 65 | path: "docs/_build/html" 66 | 67 | # Deploy the book's HTML to GitHub Pages 68 | - name: Deploy to GitHub Pages 69 | id: deployment 70 | uses: actions/deploy-pages@v4 71 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | on: 3 | push: 4 | tags: 5 | - "v[0-9]+.[0-9]+.[0-9]+*" 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | environment: 11 | name: pypi 12 | url: https://pypi.org/p/itables 13 | permissions: 14 | id-token: write 15 | steps: 16 | - name: Checkout source 17 | uses: actions/checkout@v4 18 | - name: Set up Node 19 | uses: actions/setup-node@v4 20 | - name: Set up Python 21 | uses: actions/setup-python@v5 22 | with: 23 | python-version: 3.11 24 | - name: Build package 25 | run: | 26 | python -m pip install hatch 27 | python -m hatch build 28 | - name: Publish 29 | uses: pypa/gh-action-pypi-publish@release/v1 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # PyCharm & VS Code 2 | .idea 3 | .vscode 4 | 5 | # Python 6 | *.egg-info 7 | *.pyc 8 | build 9 | dist 10 | 11 | # Notebooks 12 | *.ipynb 13 | .ipynb_checkpoints 14 | !tests/test_notebook.ipynb 15 | 16 | # Jupyter Book 17 | _build 18 | 19 | # Quarto 20 | .jupyter_cache 21 | docs/quarto/*.html 22 | docs/quarto/*_files/ 23 | 24 | # Node packages 25 | node_modules 26 | 27 | # DataTables bundle 28 | dt_bundle.js 29 | dt_bundle.css 30 | 31 | # Dash component 32 | src/itables_for_dash/*.js* 33 | src/itables_for_dash/_imports_.py 34 | 35 | # Streamlit component 36 | src/itables/itables_for_streamlit 37 | 38 | # Jupyter Widget 39 | src/itables/widget/static 40 | 41 | # On-going research 42 | research 43 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Install pre-commit hooks via 2 | # pre-commit install 3 | 4 | exclude: ^src/itables_for_dash/ITable.py$ # auto-generated 5 | repos: 6 | 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v5.0.0 9 | hooks: 10 | - id: check-json 11 | - id: check-yaml 12 | - id: end-of-file-fixer 13 | - id: trailing-whitespace 14 | 15 | - repo: https://github.com/timothycrosley/isort 16 | rev: 6.0.1 17 | hooks: 18 | - id: isort 19 | args: ["--profile", "black", "--filter-files"] 20 | 21 | - repo: https://github.com/psf/black 22 | rev: 25.1.0 23 | hooks: 24 | - id: black 25 | 26 | - repo: https://github.com/astral-sh/ruff-pre-commit 27 | rev: v0.11.7 28 | hooks: 29 | - id: ruff 30 | args: ["--fix", "--show-fixes"] 31 | 32 | - repo: https://github.com/mwouts/jupytext 33 | rev: v1.17.1 34 | hooks: 35 | - id: jupytext 36 | files: "^docs/" 37 | args: ["--sync"] 38 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | max-line-length=127 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Marc Wouts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/dash/1_display_only.py: -------------------------------------------------------------------------------- 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) 19 | -------------------------------------------------------------------------------- /apps/dash/2_selected_rows.py: -------------------------------------------------------------------------------- 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) 34 | -------------------------------------------------------------------------------- /apps/dash/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 0; 4 | padding: 0; 5 | background-color: #f5f5f5; 6 | } 7 | .container { 8 | display: flex; 9 | min-height: 100vh; 10 | } 11 | .sidebar { 12 | width: 250px; 13 | background-color: #f0f0f0; 14 | padding: 20px; 15 | box-shadow: 2px 0 5px rgba(0,0,0,0.1); 16 | } 17 | .main-content { 18 | flex: 1; 19 | padding: 20px; 20 | } 21 | -------------------------------------------------------------------------------- /apps/marimo/html.py: -------------------------------------------------------------------------------- 1 | import marimo 2 | 3 | __generated_with = "0.13.6" 4 | app = marimo.App() 5 | 6 | 7 | @app.cell(hide_code=True) 8 | def _(mo): 9 | mo.md( 10 | r""" 11 | # Using the ITable HTML representation in Marimo 12 | 13 | The `show` method of ITables does not work in Marimo. We recommend 14 | that you use the `ITable` widget instead. 15 | 16 | However you can also use the `to_html_datatable` method in combination 17 | with `mo.iframe` to display a table in Marimo. You need to pass 18 | `connected=True` as the HTML snippet is not able to access the `init_notebook_mode` cell. 19 | """ 20 | ) 21 | return 22 | 23 | 24 | @app.cell 25 | def _(): 26 | import marimo as mo 27 | 28 | from itables import to_html_datatable 29 | from itables.sample_dfs import get_dict_of_test_dfs 30 | 31 | df = get_dict_of_test_dfs()["int_float_str"] 32 | 33 | html = to_html_datatable(df, selected_rows=[0, 2, 5], select=True, connected=True) 34 | mo.iframe(html) 35 | 36 | 37 | if __name__ == "__main__": 38 | app.run() 39 | -------------------------------------------------------------------------------- /apps/shiny/README.md: -------------------------------------------------------------------------------- 1 | # ITables in Shiny for Python: a Demo 2 | 3 | This directory contains two example Python for Shiny applications that render 4 | Pandas DataFrames using [ITables](https://mwouts.github.io/itables). 5 | 6 | ## The ITable Widget 7 | 8 | This is the recommended way to use ITable in a Shiny application. 9 | 10 | See the source code at [`app.py`](itable_widget/app.py) (Shiny Express) or [`app-core.py`](itable_widget/app-core.py) (Shiny Core). 11 | 12 | Install the requirements with 13 | ``` 14 | pip install -r itable_widget/requirements.txt 15 | ``` 16 | and launch the app with 17 | ``` 18 | shiny run itable_widget/app-express.py # or app-core.py 19 | ``` 20 | 21 | Or access the app online at https://itables.shinyapps.io/itable_widget/ 22 | 23 | ## Using DT 24 | 25 | This is an alternative way to use ITable in a Shiny application. 26 | 27 | See the source code at [`app.py`](itables_DT/app.py) (Shiny Express) or [`app-core.py`](itables_DT/app-core.py) (Shiny Core). 28 | 29 | Install the requirements with 30 | ``` 31 | pip install -r itables_DT/requirements.txt 32 | ``` 33 | and launch the app with 34 | ``` 35 | shiny run itables_DT/app-express.py # or app-core.py 36 | ``` 37 | 38 | Or access the app online at https://itables.shinyapps.io/itables_DT/ 39 | -------------------------------------------------------------------------------- /apps/shiny/itable_widget/app-core.py: -------------------------------------------------------------------------------- 1 | from shiny import App, reactive, render, ui 2 | from shinywidgets import output_widget, reactive_read, render_widget 3 | 4 | from itables.sample_dfs import get_dict_of_test_dfs 5 | from itables.widget import ITable 6 | 7 | dfs = get_dict_of_test_dfs() 8 | 9 | app_ui = ui.page_sidebar( 10 | ui.sidebar( 11 | ui.input_select( 12 | "table_selector", 13 | "Table", 14 | choices=list(dfs.keys()), 15 | selected="int_float_str", 16 | ) 17 | ), 18 | output_widget("my_table"), 19 | ui.markdown("Selected rows"), 20 | ui.output_code("selected_rows"), 21 | title="Using the ITable Widget in a Shiny App", 22 | fillable=True, 23 | ) 24 | 25 | 26 | def server(input, output, session): 27 | @render_widget 28 | def my_table(): 29 | """ 30 | This function creates the "my_table" widget. While we could 31 | pass the df or the other arguments here, it's nicer 32 | to use a "reactive.effect" to update the widget 33 | """ 34 | return ITable(caption="A table rendered with ITable", select=True) 35 | 36 | @reactive.effect 37 | def _(): 38 | """ 39 | This "reactive.effect" uses the "update" method of the ITable widget 40 | to update the widget with the new inputs. 41 | """ 42 | # Get the new inputs 43 | df = dfs[input.table_selector()] 44 | selected_rows = list(range(0, len(df), 3)) 45 | 46 | # Update the widget 47 | assert isinstance( 48 | my_table.widget, ITable 49 | ) # otherwise pyright complains it might be None 50 | my_table.widget.update(df, selected_rows=selected_rows) 51 | 52 | ui.markdown("Selected rows") 53 | 54 | @render.code 55 | def selected_rows(): 56 | """ 57 | Here we read the "selected_rows" attribute of the ITable widget 58 | """ 59 | assert isinstance( 60 | my_table.widget, ITable 61 | ) # otherwise pyright complains it might be None 62 | return str(reactive_read(my_table.widget, "selected_rows")) 63 | 64 | 65 | app = App(app_ui, server) 66 | -------------------------------------------------------------------------------- /apps/shiny/itable_widget/app.py: -------------------------------------------------------------------------------- 1 | from shiny import reactive 2 | from shiny.express import input, render, ui 3 | from shinywidgets import reactive_read, render_widget 4 | 5 | from itables.sample_dfs import get_dict_of_test_dfs 6 | from itables.widget import ITable 7 | 8 | dfs = get_dict_of_test_dfs() 9 | 10 | ui.page_opts(title="Using the ITable Widget in a Shiny App", fillable=True) 11 | 12 | with ui.card(): 13 | with ui.layout_sidebar(): 14 | with ui.sidebar(): 15 | ui.input_select( 16 | "table_selector", 17 | "Table", 18 | choices=list(dfs.keys()), 19 | selected="int_float_str", 20 | ) 21 | 22 | @render_widget 23 | def my_table(): 24 | """ 25 | This function creates the "my_table" widget. While we could 26 | pass the df or the other arguments here, it's nicer 27 | to use a "reactive.effect" to update the widget 28 | """ 29 | return ITable(caption="A table rendered with ITable", select=True) 30 | 31 | @reactive.effect 32 | def _(): 33 | """ 34 | This "reactive.effect" uses the "update" method of the ITable widget 35 | to update the widget with the new inputs. 36 | """ 37 | # Get the new inputs 38 | df = dfs[input.table_selector()] 39 | selected_rows = list(range(0, len(df), 3)) 40 | 41 | # Update the widget 42 | assert isinstance( 43 | my_table.widget, ITable 44 | ) # otherwise pyright complains it might be None 45 | my_table.widget.update(df, selected_rows=selected_rows) 46 | 47 | ui.markdown("Selected rows") 48 | 49 | @render.code 50 | def selected_rows(): 51 | """ 52 | Here we read the "selected_rows" attribute of the ITable widget 53 | """ 54 | assert isinstance( 55 | my_table.widget, ITable 56 | ) # otherwise pyright complains it might be None 57 | return str(reactive_read(my_table.widget, "selected_rows")) 58 | -------------------------------------------------------------------------------- /apps/shiny/itable_widget/requirements.txt: -------------------------------------------------------------------------------- 1 | itables>=2.2 2 | shiny 3 | shinywidgets 4 | anywidget 5 | -------------------------------------------------------------------------------- /apps/shiny/itable_widget/rsconnect-python/itable_widget.json: -------------------------------------------------------------------------------- 1 | { 2 | "https://api.shinyapps.io": { 3 | "server_url": "https://api.shinyapps.io", 4 | "filename": "/home/marc/GitHub/itables/apps/shiny/itable_widget", 5 | "app_url": "https://itables.shinyapps.io/itable_widget/", 6 | "app_id": 12865888, 7 | "app_guid": null, 8 | "title": "itable_widget", 9 | "app_mode": "python-shiny", 10 | "app_store_version": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/shiny/itables_DT/app-core.py: -------------------------------------------------------------------------------- 1 | from shiny import App, render, ui 2 | 3 | from itables.sample_dfs import get_dict_of_test_dfs 4 | from itables.shiny import DT, init_itables 5 | 6 | dfs = get_dict_of_test_dfs() 7 | 8 | app_ui = ui.page_sidebar( 9 | ui.sidebar( 10 | ui.input_select( 11 | "table_selector", 12 | "Table", 13 | choices=list(dfs.keys()), 14 | selected="int_float_str", 15 | ) 16 | ), 17 | ui.HTML(init_itables()), 18 | ui.output_ui("my_table"), 19 | ui.markdown("Selected rows"), 20 | ui.output_code("selected_rows"), 21 | title="Using DT in a Shiny App", 22 | fillable=True, 23 | ) 24 | 25 | 26 | def server(input, output, session): 27 | @render.ui 28 | def my_table(): 29 | """ 30 | This function renders the table using "DT". 31 | """ 32 | df = dfs[input.table_selector()] 33 | 34 | return ui.HTML(DT(df)) 35 | 36 | 37 | app = App(app_ui, server) 38 | -------------------------------------------------------------------------------- /apps/shiny/itables_DT/app.py: -------------------------------------------------------------------------------- 1 | from shiny.express import input, render, ui 2 | 3 | from itables.sample_dfs import get_dict_of_test_dfs 4 | from itables.shiny import DT, init_itables 5 | 6 | dfs = get_dict_of_test_dfs() 7 | 8 | ui.page_opts(title="Using DT in a Shiny App", fillable=True) 9 | 10 | with ui.card(): 11 | with ui.layout_sidebar(): 12 | with ui.sidebar(): 13 | ui.input_select( 14 | "table_selector", 15 | "Table", 16 | choices=list(dfs.keys()), 17 | selected="int_float_str", 18 | ) 19 | 20 | ui.HTML(init_itables()) 21 | 22 | @render.ui 23 | def my_table(): 24 | """ 25 | This function renders the table using "DT". 26 | """ 27 | df = dfs[input.table_selector()] 28 | return ui.HTML(DT(df, caption="A table rendered with ITable")) 29 | -------------------------------------------------------------------------------- /apps/shiny/itables_DT/requirements.txt: -------------------------------------------------------------------------------- 1 | itables>=2.2 2 | shiny 3 | -------------------------------------------------------------------------------- /apps/shiny/itables_DT/rsconnect-python/itables_DT.json: -------------------------------------------------------------------------------- 1 | { 2 | "https://api.shinyapps.io": { 3 | "server_url": "https://api.shinyapps.io", 4 | "filename": "/home/marc/GitHub/itables/apps/shiny/itables_DT", 5 | "app_url": "https://itables.shinyapps.io/itables_dt/", 6 | "app_id": 12865873, 7 | "app_guid": null, 8 | "title": "itables_DT", 9 | "app_mode": "python-shiny", 10 | "app_store_version": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/streamlit/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/apps/streamlit/requirements.txt -------------------------------------------------------------------------------- /binder/labconfig/default_setting_overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "@jupyterlab/docmanager-extension:plugin": { 3 | "defaultViewers": { 4 | "markdown": "Jupytext Notebook", 5 | "myst": "Jupytext Notebook", 6 | "r-markdown": "Jupytext Notebook", 7 | "quarto": "Jupytext Notebook", 8 | "julia": "Jupytext Notebook", 9 | "python": "Jupytext Notebook", 10 | "r": "Jupytext Notebook" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /binder/postBuild: -------------------------------------------------------------------------------- 1 | # Stop everything if one command fails 2 | set -e 3 | 4 | # Install from sources 5 | pip install .[all] 6 | 7 | # Open .md notebooks with the Notebook editor 8 | mkdir -p ${HOME}/.jupyter/labconfig 9 | cp binder/labconfig/* ${HOME}/.jupyter/labconfig 10 | 11 | # Install a kernel called itables 12 | python -m ipykernel install --name itables --user 13 | 14 | # Use that kernel for the documentation notebooks 15 | jupytext docs/*.md --set-kernel itables 16 | jupytext README.md --set-kernel itables 17 | -------------------------------------------------------------------------------- /binder/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyterlab>=4.0.0a16 2 | jupytext 3 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Interactive Tables 2 | author: Marc Wouts 3 | copyright: "2019-2025" 4 | logo: "../src/itables/logo/logo.svg" 5 | execute: 6 | execute_notebooks: force 7 | sphinx: 8 | config: 9 | html_extra_path: ['quarto'] 10 | rediraffe_branch: main 11 | rediraffe_redirects: 12 | advanced_parameters.md: options/options.md 13 | extensions.md: options/buttons.md 14 | select.md: options/select.md 15 | custom_css.md: css.md 16 | dash.md: apps/dash.md 17 | html_export.md: apps/html.md 18 | shiny.md: apps/shiny.md 19 | quarto.md: apps/quarto.md 20 | streamlit.md: apps/streamlit.md 21 | widget.md: apps/widget.md 22 | sample_dataframes.md: pandas_dataframes.md 23 | extra_extensions: 24 | - sphinxext.rediraffe 25 | repository: 26 | url: https://github.com/mwouts/itables 27 | path_to_book: docs 28 | branch: main 29 | html: 30 | use_edit_page_button: true 31 | use_repository_button: true 32 | use_issues_button: true 33 | launch_buttons: 34 | notebook_interface: "jupyterlab" 35 | binderhub_url: "https://mybinder.org" 36 | -------------------------------------------------------------------------------- /docs/_toc.yml: -------------------------------------------------------------------------------- 1 | format: jb-book 2 | root: quick_start 3 | parts: 4 | - caption: Using ITables 5 | chapters: 6 | - file: apps/notebook 7 | - file: apps/html 8 | - file: apps/widget 9 | - file: apps/marimo 10 | - file: apps/dash 11 | - file: apps/streamlit 12 | - file: apps/shiny 13 | - file: apps/quarto 14 | - caption: Options 15 | chapters: 16 | - file: options/options 17 | - file: options/classes 18 | - file: options/style 19 | - file: options/caption 20 | - file: options/layout 21 | - file: options/column_defs 22 | - file: options/search 23 | - file: options/length_menu 24 | - file: options/paging 25 | - file: options/scroll_y 26 | - file: options/scroll_x 27 | - file: options/footer 28 | - file: options/column_filters 29 | - file: options/order 30 | - file: options/state_save 31 | - file: options/select 32 | - file: options/buttons 33 | - file: options/colvis 34 | - file: options/search_panes 35 | - file: options/search_builder 36 | - file: options/fixed_columns 37 | - file: options/row_group 38 | - file: options/keys 39 | - file: options/allow_html 40 | - file: options/show_index 41 | - file: options/text_in_header_can_be_selected 42 | - caption: More about ITables 43 | chapters: 44 | - file: downsampling 45 | - file: formatting 46 | - file: dark_mode 47 | - file: css 48 | - file: custom_extensions 49 | - file: references 50 | - file: supported_editors 51 | - caption: Contributing to ITables 52 | chapters: 53 | - file: contributing 54 | - file: developing 55 | - file: troubleshooting 56 | - file: changelog 57 | - caption: Examples 58 | chapters: 59 | - file: pandas_dataframes 60 | - file: pandas_style 61 | - file: polars_dataframes 62 | -------------------------------------------------------------------------------- /docs/apps/dash.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Dash 16 | 17 | 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: 18 | 19 | ```{include} ../../apps/dash/1_display_only.py 20 | :code: python 21 | ``` 22 | 23 | ## Selected rows 24 | 25 | 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")`. 26 | 27 | ```{include} ../../apps/dash/2_selected_rows.py 28 | :code: python 29 | ``` 30 | 31 | ## Updating the DataFrame 32 | 33 | The `ITable` component has many properties. These properties (table content, 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)): 34 | 35 | ```python 36 | from itables.dash import ITable, ITableOutputs, updated_itable_outputs 37 | 38 | # (...) 39 | 40 | @callback( 41 | ITableOutputs("my_dataframe"), 42 | [ 43 | Input("checklist", "value"), 44 | Input("caption", "value"), 45 | State("my_dataframe", "selected_rows"), 46 | State("my_dataframe", "dt_args"), 47 | ], 48 | ) 49 | def update_table(checklist, caption, selected_rows, dt_args): 50 | if checklist is None: 51 | checklist = [] 52 | 53 | kwargs = {} 54 | 55 | # When df=None and when the dt_args don't change, the table is not updated 56 | if callback_context.triggered_id == "checklist": 57 | kwargs["df"] = get_countries(html="HTML" in checklist) 58 | 59 | kwargs["select"] = "Select" in checklist 60 | if "Buttons" in checklist: 61 | kwargs["buttons"] = ["copyHtml5", "csvHtml5", "excelHtml5"] 62 | 63 | return updated_itable_outputs( 64 | caption=caption, selected_rows=selected_rows, current_dt_args=dt_args, **kwargs 65 | ) 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/apps/html.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # pyright: reportUnknownVariableType=false 19 | ``` 20 | 21 | # HTML export 22 | 23 | To get the HTML representation of a Pandas DataFrame `df` as an interactive [DataTable](https://datatables.net/), you can use `to_html_datatable` as below: 24 | 25 | ```{code-cell} ipython3 26 | from IPython.display import HTML, display 27 | 28 | import itables 29 | 30 | df = itables.sample_dfs.get_countries(html=False) 31 | html = itables.to_html_datatable(df.head(3), display_logo_when_loading=False) 32 | ``` 33 | 34 | You can then save the `html` variable to a text file (note: if you're writing an HTML application, you could consider using [Shiny](shiny.md) or [Streamlit](streamlit.md) instead), or print it: 35 | 36 | ```{code-cell} ipython3 37 | :tags: [scroll-output] 38 | 39 | print(html) 40 | ``` 41 | 42 | or display it, like `show` does: 43 | 44 | ```{code-cell} ipython3 45 | display(HTML(html)) 46 | ``` 47 | 48 | ~~~{admonition} The `connected` argument 49 | :class: tip 50 | The `to_html_datatable` function has a `connected` argument which defaults to what you set in `init_notebook_mode` (it's `True` if you didn't call it). 51 | 52 | - With `connected=True` you get an autonomous HTML fragment that loads `dt_for_itables` from the Internet 53 | - With `connected=False`, the HTML snippet works only after you add the output of `generate_init_offline_itables_html()` to your HTML document 54 | ~~~ 55 | -------------------------------------------------------------------------------- /docs/apps/marimo.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Marimo 16 | 17 | ```{warning} 18 | The `init_notebook_mode` and the `show` function do not work in Marimo. This is because they both use `IPython.display` to display the HTML representation of the table, which is not a good fit for Marimo. 19 | ``` 20 | 21 | In Marimo the recommended way to use ITable is through the `ITable` [widget](widget.md): 22 | 23 | ```{code-cell} ipython3 24 | import pandas as pd 25 | 26 | from itables.widget import ITable 27 | 28 | df = pd.DataFrame({"x": [2, 1, 3]}) 29 | 30 | ITable(df) 31 | ``` 32 | 33 | A Sample Marimo application is available at [`apps/marimo/widget.py`](https://github.com/mwouts/itables/tree/main/apps/marimo/widget.py). 34 | 35 | 36 | ## Using HTML 37 | 38 | You can also use `to_html_datatable` in combination with `mo.iframe` like in this example - but, as there is no `init_notebook_mode` cell, you will have to use the connected mode: 39 | 40 | ```python 41 | import marimo as mo 42 | 43 | from itables import to_html_datatable 44 | 45 | html = to_html_datatable(df, connected=True) 46 | mo.iframe(html) 47 | ``` 48 | 49 | A Sample Marimo application that uses `to_html_datatable` is available at [`apps/marimo/html.py`](https://github.com/mwouts/itables/tree/main/apps/marimo/html.py). 50 | -------------------------------------------------------------------------------- /docs/apps/notebook.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Notebook Mode 16 | 17 | Activate ITables in a Jupyter environment for all your tables with `init_notebook_mode`: 18 | 19 | ```{code-cell} ipython3 20 | import itables 21 | 22 | itables.init_notebook_mode() 23 | ``` 24 | 25 | You can go back to the standard HTML representation of Pandas DataFrames with `init_notebook_mode(all_interactive=False)`. 26 | 27 | Note that the `init_connected_mode` function also activates ITable's offline mode, unless you call it with a `connected=False` argument. 28 | 29 | 30 | ## Offline mode 31 | 32 | By default `init_connected_mode` configures ITables to work offline (except in Colab). No internet connection is required as the JavaScript code is embedded into the notebook itself when you execute `init_notebook_mode`. 33 | 34 | In some contexts (Jupyter Book, Google Colab, etc...) you might 35 | prefer to load the libraries dynamically from the internet. 36 | To do so, add the argument `connected=True` when you 37 | execute `init_notebook_mode`. This will also make your notebook lighter by 38 | about [700kB](https://github.com/mwouts/itables/blob/main/tests/test_connected_notebook_is_small.py). Note that, in Google Colab, `connected=True` is the only working option. 39 | 40 | ## Show 41 | 42 | If you prefer to render only certain tables using `itables`, or want to set additional options, use `show`: 43 | 44 | ```{code-cell} ipython3 45 | df = itables.sample_dfs.get_countries(html=False) 46 | 47 | itables.show( 48 | df, 49 | caption="A DataFrame rendered with ITables", 50 | lengthMenu=[2, 5, 10, 25, 50, 100, 250], 51 | ) 52 | ``` 53 | 54 | ## HTML 55 | 56 | The `show` function simply displays the HTML snippet for the table, which is obtained with `to_html_datatable`. See more in the section on [HTML export](html.md). 57 | -------------------------------------------------------------------------------- /docs/apps/quarto.md: -------------------------------------------------------------------------------- 1 | # Quarto 2 | 3 | ITables works well with the `revealjs` and `html` outputs formats of [Quarto](https://quarto.org). 4 | 5 | ## HTML Presentations 6 | 7 | See this revealjs presentation, 8 | or download the source file and render it with `quarto render`: 9 | 10 | 11 | 12 | ## HTML reports 13 | 14 | See this HTML report, 15 | or download the source file and render it with `quarto render`: 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/apps/streamlit.md: -------------------------------------------------------------------------------- 1 | # Streamlit 2 | 3 | To display Python DataFrames as interactive DataTables in Streamlit applications, use 4 | 5 | ``` 6 | from itables.streamlit import interactive_table 7 | ``` 8 | 9 | We have a sample application available at https://itables.streamlit.app (source code [here](https://github.com/mwouts/demo_itables_in_streamlit/blob/main/itables_app.py)) 10 | 11 | ```{div} full-width 12 | 14 | ``` 15 | 16 | ## Selected rows 17 | 18 | Use the `selected_rows: list[int]` argument from `interactive_table` to 19 | 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. 20 | 21 | ## Using HTML 22 | 23 | Before ITables v2.4.0, the streamlit component was missing a few options compared to the HTML implementation. A possible fallback was to use `to_html_datatable` in combination with Streamlit's `html` function - but you should not need that anymore with the latest version of ITables. 24 | 25 | Please note that: 26 | - you will have to specify the table height manually, 27 | - an internet connection is required when using `to_html_datatable`, 28 | - the app/table might take longer to display. 29 | 30 | A sample application is available at https://to-html-datatable.streamlit.app (source code [here](https://github.com/mwouts/to_html_datatable_in_streamlit/blob/main/itables_app.py)) 31 | 32 | ```{div} full-width 33 | 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # pyright: reportUnknownVariableType=false 19 | ``` 20 | 21 | # Contributing 22 | 23 | Thanks for considering making a contribution to ITables. There are 24 | many ways you can help! 25 | 26 | ## Report an issue 27 | 28 | If you see an issue, a possible improvement, or if you can't find 29 | the answer to your question, then you are very welcome to create 30 | an issue on this project. Please provide enough detail so that 31 | we can reproduce the issue. 32 | 33 | ## Improve the documentation 34 | 35 | If you would like to add a new example, 36 | or improve the documentation, feel free to make a pull request! 37 | 38 | You can render the documentation locally - see the section on 39 | [Jupyter Book](developing.md#jupyter-book) in the developer guide. 40 | 41 | ## Give credit to ITables 42 | 43 | It's always great to see new stars coming to ITables! 44 | 45 | 46 | If you wanted to share a link to ITables and DataTables (no obligation whatsoever), you could use something like this: 47 | 48 | ```{code-cell} ipython3 49 | from IPython.display import HTML, display 50 | 51 | display( 52 | HTML( 53 | """ 54 | Tables displayed with ITables, 55 | a Python wrapper for DataTables 56 | """ 57 | ) 58 | ) 59 | ``` 60 | 61 | ## Support DataTables 62 | 63 | Allan Jardine, the main developer of DataTables, has done a fantastic work on [DataTables](https://datatables.net/). 64 | 65 | If you enjoy his library, you could become a 66 | [supporter](https://datatables.net/supporters/) - 67 | contributions range from 9 to 99$/year before VAT. 68 | Or you could take a subscription for DataTable's [Editor](https://editor.datatables.net) 69 | that ITables might support in the future (please subscribe to [#243](https://github.com/mwouts/itables/issues/243) for updates). 70 | 71 | ## Develop a new feature 72 | 73 | It is generally a good idea to get in touch with us first - e.g. 74 | open an issue and let us know what you'd like to do. 75 | 76 | But you can also simply clone the project and test your ideas. 77 | A guide on how to set up a development environment, and how to 78 | run some tests, is available [here](developing.md). 79 | -------------------------------------------------------------------------------- /docs/css.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # ruff: noqa: E402 19 | # pyright: reportUnknownVariableType=false 20 | ``` 21 | 22 | # CSS 23 | 24 | You can use CSS to alter how tables are rendered. 25 | 26 | For instance, we change the 27 | [font size](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size) 28 | for all the tables in the document with this code: 29 | 30 | ```{code-cell} ipython3 31 | from IPython.display import HTML, display 32 | 33 | css = """ 34 | .dt-container { 35 | font-size: small; 36 | } 37 | """ 38 | display(HTML(f"" "")) 39 | ``` 40 | 41 | This is helpful for instance in the context of 42 | [Quarto presentations](apps/quarto.md). 43 | 44 | With this over CSS, we change _every datatable_ table header 45 | in the notebook to bold/italic. 46 | 47 | ```{code-cell} ipython3 48 | css = """ 49 | .dataTable th { 50 | font-weight: bolder; 51 | font-style: italic; 52 | } 53 | """ 54 | display(HTML(f"" "")) 55 | ``` 56 | 57 | You might also want to alter the style of specific tables only. 58 | To do this, add a new class to the target tables, as 59 | in the example below: 60 | 61 | ```{code-cell} ipython3 62 | class_specific_css = ".table_with_monospace_font { font-family: courier, monospace }" 63 | display(HTML(f"" "")) 64 | ``` 65 | 66 | ```{code-cell} ipython3 67 | :tags: [full-width] 68 | 69 | import itables 70 | 71 | itables.init_notebook_mode() 72 | 73 | df = itables.sample_dfs.get_countries(html=False) 74 | 75 | itables.show(df, classes="display nowrap table_with_monospace_font") 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/dark_mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Dark Themes 16 | 17 | When a notebook or application is rendered using a dark theme, DataTable requires that a `dark` 18 | class be added to the HTML document. This can be done with the following Javascript snippet: 19 | ```javascript 20 | document.documentElement.classList.add('dark'); 21 | ``` 22 | 23 | When ITables is used in a notebook, this is handled by 24 | `init_notebook_mode` which displays the [`init_datatables.html`](https://github.com/mwouts/itables/blob/main/src/itables/html/init_datatables.html) snippet. 25 | 26 | Please open a PR if you see how to further improve the 27 | support of light vs dark themes, and e.g. set the `dark` 28 | class dynamically when the theme is changed. 29 | -------------------------------------------------------------------------------- /docs/developing.md: -------------------------------------------------------------------------------- 1 | # Developing ITables 2 | 3 | In this page you will find instructions on how to 4 | create a development environment and how 5 | to test your changes. 6 | 7 | ## How to create a development environment 8 | 9 | Create a conda environment with 10 | ```shell 11 | mamba env create --file environment.yml 12 | ``` 13 | (use `conda` if you don't have `mamba`) or update it with 14 | ```shell 15 | mamba env update --file environment.yml 16 | ``` 17 | 18 | Then, activate that environment with 19 | ```shell 20 | conda activate itables 21 | ``` 22 | 23 | Install the pre-commit hooks with 24 | ```shell 25 | pre-commit install 26 | ``` 27 | 28 | and finally, install the development version of `itables` with 29 | ```shell 30 | pip install -e . 31 | ``` 32 | 33 | ## How to run the Python test suite 34 | 35 | The Python code can be tested with just 36 | 37 | ```shell 38 | pytest 39 | ``` 40 | 41 | Running the `pytest` test suite is not enough to guaranty that a change won't 42 | break `itables`. You also need to test that the tables are well rendered in the 43 | different contexts like Jupyter Book, Jupyter Lab, VS Code. 44 | 45 | ## Jupyter Book 46 | 47 | The `itables` documentation uses [Jupyter Book](https://jupyterbook.org/). 48 | 49 | To build the documentation locally, 50 | you need to create a Jupyter kernel named `itables` with 51 | ```shell 52 | python -m ipykernel install --name itables --user 53 | ``` 54 | Then you can build the documentation with 55 | ``` 56 | jupyter book build docs 57 | ``` 58 | 59 | This will give you a link to a local version of the documentation. 60 | 61 | If you make any significant change then you should go through 62 | the updated documentation and make sure all the examples 63 | still work properly. 64 | 65 | ## Jupyter Lab 66 | 67 | In the `itables` conda environment, you can start Jupyter with 68 | ``` 69 | jupyter lab 70 | ``` 71 | 72 | You should test at least this code snippet: 73 | ```python 74 | import itables 75 | 76 | # try both connected=False (the default) and connected=True 77 | itables.init_notebook_mode(connected=False) 78 | 79 | itables.sample_dfs.get_countries() 80 | ``` 81 | 82 | You can do this using for instance the notebook at `tests/test_notebook.ipynb`. 83 | 84 | Note that you can also open the documentation in Jupyter: 85 | go to the `docs` folder and open any `.md` file _as a notebook_ (using a right click). 86 | 87 | ## Other notebook editors 88 | 89 | If you change anything related to the Javascript/HTML code, 90 | you should test the [supported editors](supported_editors.md) 91 | in both the connected and offline mode. 92 | 93 | For the online editors like [Google Colab](https://colab.research.google.com/), 94 | you might have to install your development version there with e.g. 95 | ``` 96 | !pip uninstall itables -y 97 | !pip install git+https://github.com/mwouts/itables.git@branch 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/df_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/df_example.png -------------------------------------------------------------------------------- /docs/downsampling.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # pyright: reportUnusedExpression=false 19 | ``` 20 | 21 | # Downsampling 22 | 23 | When an interactive table is displayed by `itables`, the table data is embedded in the notebook output. As we don't want your notebook to become super heavy just because you displayed a large table, we have a downsampling mechanism in place. 24 | 25 | When the data in a table is larger than `maxBytes`, which is equal to 64KB by default, `itables` will display only a subset of the table - one that fits into `maxBytes`, and display a warning that points to the `itables` documentation. 26 | 27 | If you wish, you can increase the value of `maxBytes` or even deactivate the limit (with `maxBytes=0`). Similarly, you can set a limit on the number of rows (`maxRows`, defaults to 0) or columns (`maxColumns`, defaults to `200`). 28 | 29 | ```{code-cell} ipython3 30 | import itables 31 | 32 | itables.init_notebook_mode() 33 | ``` 34 | 35 | ```{code-cell} ipython3 36 | itables.options.lengthMenu = [2, 5, 10, 20, 50, 100, 200, 500] 37 | itables.options.maxBytes = "8KB" 38 | 39 | df = itables.sample_dfs.get_indicators() 40 | itables.downsample.as_nbytes(itables.options.maxBytes), itables.downsample.nbytes(df) 41 | ``` 42 | 43 | ```{code-cell} ipython3 44 | :tags: [full-width] 45 | 46 | df 47 | ``` 48 | 49 | To show the table in full, we can modify the value of `maxBytes` either locally: 50 | 51 | ```{code-cell} ipython3 52 | :tags: [full-width] 53 | 54 | itables.show(df, maxBytes=0) 55 | ``` 56 | 57 | or globally: 58 | 59 | ```{code-cell} ipython3 60 | :tags: [full-width] 61 | 62 | itables.options.maxBytes = "1MB" 63 | df 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/images/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/code.png -------------------------------------------------------------------------------- /docs/images/colab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/colab.png -------------------------------------------------------------------------------- /docs/images/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/html.png -------------------------------------------------------------------------------- /docs/images/lab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/lab.png -------------------------------------------------------------------------------- /docs/images/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/notebook.png -------------------------------------------------------------------------------- /docs/images/pycharm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/images/pycharm.png -------------------------------------------------------------------------------- /docs/options/buttons.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Buttons 16 | 17 | The DataTables [buttons](https://datatables.net/extensions/buttons/) let you copy the table data, or export it as CSV or Excel files. 18 | 19 | To display the buttons, you need to pass a `buttons` argument to the `show` function: 20 | 21 | ```{code-cell} ipython3 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | 26 | df = itables.sample_dfs.get_countries(html=False) 27 | 28 | itables.show(df, buttons=["pageLength", "copyHtml5", "csvHtml5", "excelHtml5"]) 29 | ``` 30 | 31 | You can also specify a [`layout`](layout) modifier that will decide 32 | the location of the buttons (the default is `layout={"topStart": "buttons"}`). And if you want to keep the pagination control too, you can add `"pageLength"` to the list of buttons - as done above. 33 | 34 | As always, it is possible to set default values for these parameters by setting these on `itables.options`. For instance, set 35 | ```python 36 | itables.options.buttons = ["copyHtml5", "csvHtml5", "excelHtml5"] 37 | ``` 38 | to get the buttons for all your tables. 39 | 40 | 41 | By default, the exported file name is the name of the HTML page. To change it, set a 42 | [`title` option](https://datatables.net/extensions/buttons/examples/html5/filename.html) on the buttons, like 43 | here: 44 | 45 | ```{code-cell} ipython3 46 | :tags: [full-width] 47 | 48 | itables.show( 49 | df, 50 | buttons=[ 51 | "pageLength", 52 | {"extend": "csvHtml5", "title": "download_filename"}, 53 | {"extend": "excelHtml5", "title": "download_filename"}, 54 | ], 55 | ) 56 | ``` 57 | 58 | ```{tip} 59 | Only the filtered or selected rows are exported to CSV/Excel. To filter the rows you can use the simple search box, the [SearchPanes](search_panes) and [SearchBuilder](search_builder) options, or the [select](select.md) extension. 60 | ``` 61 | 62 | ```{warning} 63 | At the moment, the CSV and Excel buttons don't work well with large tables in some browsers. 64 | Please subscribe to [#251](https://github.com/mwouts/itables/issues/251) if you wish to receive updates on this. 65 | ``` 66 | 67 | ```{warning} 68 | The PDF button is not included in ITables' DataTable bundle. This is because the required PDF libraries have a large footprint on the bundle size. Still, you can add it to your custom bundle, see our page on how to bundle [custom extensions](../custom_extensions.md). 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/options/caption.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Caption 16 | 17 | ```{code-cell} ipython3 18 | :tags: [full-width] 19 | 20 | import itables 21 | 22 | itables.init_notebook_mode() 23 | 24 | df = itables.sample_dfs.get_countries(html=False) 25 | itables.show(df, "Countries from the World Bank Database") 26 | ``` 27 | 28 | The caption appears at the bottom of the table by default: this is governed by `caption-side:bottom` 29 | in the [`style` option](style) (but for some reason this is not effective in Jupyter Book 🤔). 30 | -------------------------------------------------------------------------------- /docs/options/classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Classes 16 | 17 | Select how your table looks like with the `classes` argument (defaults to `"display nowrap"`) of the `show` function, or by changing `itables.options.classes`. 18 | 19 | Add `"compact"` if you want a denser table: 20 | 21 | ```{code-cell} ipython3 22 | :tags: [full-width] 23 | 24 | import itables 25 | 26 | itables.init_notebook_mode() 27 | 28 | df = itables.sample_dfs.get_countries(html=False) 29 | 30 | itables.show(df, classes="display nowrap compact") 31 | ``` 32 | 33 | Remove `"nowrap"` if you want the cell content to be wrapped: 34 | 35 | ```{code-cell} ipython3 36 | :tags: [full-width] 37 | 38 | itables.show(df, classes="display") 39 | ``` 40 | 41 | [More options](https://datatables.net/manual/styling/classes#Table-classes) like `"cell-border"` are available: 42 | 43 | ```{code-cell} ipython3 44 | :tags: [full-width] 45 | 46 | itables.show(df, classes="display nowrap cell-border") 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/options/column_defs.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # pyright: reportUnusedExpression=false 19 | ``` 20 | 21 | # Column Definitions 22 | 23 | The [`columnDefs.width`](https://datatables.net/reference/option/columns.width) argument let you adjust the column widths. 24 | 25 | Note that the default value of `style`, or of `autoWidth` (defaults to `True`), might override custom column widths, 26 | so you might have to change their values as in the examples below. 27 | 28 | You can set a fixed width for all the columns with `"targets": "_all"`: 29 | 30 | ```{code-cell} ipython3 31 | :tags: [full-width] 32 | 33 | import itables 34 | 35 | itables.init_notebook_mode() 36 | 37 | df = itables.sample_dfs.get_countries(html=False) 38 | 39 | itables.show( 40 | df, 41 | columnDefs=[{"width": "120px", "targets": "_all"}], 42 | style="width:1200px", 43 | autoWidth=False, 44 | ) 45 | ``` 46 | 47 | You can also adjust the width of selected columns only: 48 | 49 | ```{code-cell} ipython3 50 | :tags: [full-width] 51 | 52 | itables.show( 53 | df, 54 | columnDefs=[{"width": "30%", "targets": [2, 3]}], 55 | style="width:100%;margin:auto", 56 | ) 57 | ``` 58 | 59 | If you wish you can also set a value for `columnDefs` permanently in `itables.options` as demonstrated in the cell alignment example below. 60 | 61 | You can use the DataTables [cell classes](https://datatables.net/manual/styling/classes#Cell-classes) like `dt-left`, `dt-center`, `dt-right` etc. to set the cell alignment. Specify it for one table by using the `columnDefs` argument of `show` 62 | 63 | ```{code-cell} ipython3 64 | :tags: [full-width] 65 | 66 | itables.show(df, columnDefs=[{"className": "dt-center", "targets": "_all"}]) 67 | ``` 68 | 69 | or globally by setting `itables.options.columnDefs`: 70 | 71 | ```{code-cell} ipython3 72 | :tags: [full-width] 73 | 74 | itables.options.columnDefs = [{"className": "dt-center", "targets": "_all"}] 75 | 76 | df 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/options/column_filters.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Column Filters 16 | 17 | Use `column_filters = "header"` or `"footer"` if you wish to display individual column filters 18 | (remove the global search box with a [`layout`](layout) modifier if desired). 19 | 20 | ```{code-cell} ipython3 21 | import pandas as pd 22 | 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | alpha_numeric_df = pd.DataFrame( 28 | [["one", 1.5], ["two", 2.3]], columns=["string", "numeric"] 29 | ) 30 | 31 | itables.show(alpha_numeric_df, column_filters="header", layout={"topEnd": None}) 32 | ``` 33 | 34 | As always you can set activate column filters by default with e.g. 35 | 36 | ```{code-cell} ipython3 37 | itables.options.column_filters = "footer" 38 | ``` 39 | 40 | Column filters also work on dataframes with multiindex columns: 41 | 42 | ```{code-cell} ipython3 43 | itables.sample_dfs.get_dict_of_test_dfs()["multiindex"] 44 | ``` 45 | 46 | ```{code-cell} ipython3 47 | :tags: [remove-cell] 48 | 49 | # Revert back to the default to avoid interactions with the tests 50 | itables.options.column_filters = False 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/options/colvis.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Column Visibility 16 | 17 | The [column visibility](https://datatables.net/extensions/buttons/examples/column_visibility/index.html) buttons of DataTables let you select which columns are visible. 18 | 19 | ```{code-cell} ipython3 20 | :tags: [full-width] 21 | 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | 26 | df = itables.sample_dfs.get_countries(html=False) 27 | 28 | itables.show( 29 | # column visibility works best with a flat header 30 | df.reset_index(), 31 | buttons=["columnsToggle"], 32 | ) 33 | ``` 34 | 35 | ```{tip} 36 | The column visibility button is available under many forms. 37 | 38 | Check-out `buttons=["colvis"]` for a [single](https://datatables.net/extensions/buttons/examples/column_visibility/simple.html) button. 39 | 40 | Extend the `colvis` button with the [collection layout](https://datatables.net/extensions/buttons/examples/column_visibility/layout.html). 41 | 42 | As always, when porting examples from DataTables to ITables, you will 43 | have to convert the JavaScript notation (left) to Python (right) as in the below: 44 | ::::{grid} 45 | 46 | :::{grid-item} 47 | :outline: 48 | :columns: 6 49 | ~~~javascript 50 | buttons: [ 51 | { 52 | extend: 'colvis', 53 | collectionLayout: 'fixed columns', 54 | popoverTitle: 'Column visibility control' 55 | } 56 | ] 57 | ~~~ 58 | ::: 59 | :::{grid-item} 60 | :outline: 61 | :columns: 6 62 | ~~~python 63 | buttons = [ 64 | { 65 | "extend": "colvis", 66 | "collectionLayout": "fixed columns", 67 | "popoverTitle": "Column visibility control" 68 | } 69 | ] 70 | ~~~ 71 | ::: 72 | 73 | :::: 74 | 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/options/fixed_columns.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # FixedColumns 16 | 17 | [FixedColumn](https://datatables.net/extensions/fixedcolumns/) is an extension 18 | that let you fix some columns as you scroll horizontally. 19 | 20 | ```{code-cell} ipython3 21 | import string 22 | 23 | import numpy as np 24 | import pandas as pd 25 | 26 | import itables 27 | 28 | itables.init_notebook_mode() 29 | 30 | wide_df = pd.DataFrame( 31 | { 32 | letter: np.random.normal(size=100) 33 | for letter in string.ascii_lowercase + string.ascii_uppercase 34 | } 35 | ) 36 | 37 | itables.show( 38 | wide_df, 39 | fixedColumns={"start": 1, "end": 2}, 40 | scrollX=True, 41 | ) 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/options/footer.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Footer 16 | 17 | Use `footer = True` if you wish to display a table footer. 18 | 19 | ```{code-cell} ipython3 20 | :tags: [full-width] 21 | 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | 26 | df = itables.sample_dfs.get_countries(html=False) 27 | itables.show(df, footer=True) 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/options/keys.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Keys 16 | 17 | With the [KeyTable](https://datatables.net/extensions/keytable/) extension you can navigate in a table using the arrow keys: 18 | 19 | ```{code-cell} ipython3 20 | :tags: [full-width] 21 | 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | 26 | itables.show( 27 | itables.sample_dfs.get_countries(html=False), 28 | keys=True, 29 | ) 30 | ``` 31 | 32 | ```{tip} 33 | You can activate this option for all your tables with 34 | 35 | ~~~python 36 | itables.options.keys = True 37 | ~~~ 38 | ``` 39 | 40 | ```{warning} 41 | The KeyTable extension works in Jupyter Book (try it here in the documentation) but not in JupyterLab. 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/options/layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Layout 16 | 17 | By default, datatables that don't fit in one page come with a search box, a pagination control, a table summary, etc. 18 | You can select which elements are actually displayed using 19 | DataTables' [`layout` option](https://datatables.net/reference/option/layout) with e.g.: 20 | 21 | ```{code-cell} ipython3 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | df = itables.sample_dfs.get_countries(html=False) 26 | ``` 27 | 28 | ```{code-cell} ipython3 29 | itables.show(df, layout={"topStart": "search", "topEnd": None}) 30 | ``` 31 | 32 | The available positions are `topStart, topEnd, bottomStart, bottomEnd`. You can also use `top2Start`, etc... (see more 33 | in the [DataTables documentation](https://datatables.net/reference/option/layout)). 34 | 35 | Like for the other arguments of `show`, you can change the default value of the dom option with e.g.: 36 | 37 | ``` 38 | itables.options.layout = { 39 | "topStart": "pageLength", 40 | "topEnd": "search", 41 | "bottomStart": "info", 42 | "bottomEnd": "paging" 43 | } # (default value) 44 | ``` 45 | 46 | ```{tip} 47 | The `layout` option was introduced with `itables==2.0` and `DataTables==2.0` 48 | and replaced the former [`dom` option](https://datatables.net/reference/option/dom). 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/options/length_menu.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # LengthMenu 16 | 17 | Select [how many entries](https://datatables.net/examples/advanced_init/length_menu.html) should appear at once in the table with either the `lengthMenu` argument of the `show` function, or with the global option `itables.options.lengthMenu`: 18 | 19 | ```{code-cell} ipython3 20 | import itables 21 | 22 | itables.init_notebook_mode() 23 | df = itables.sample_dfs.get_countries(html=False) 24 | ``` 25 | 26 | ```{code-cell} ipython3 27 | :tags: [full-width] 28 | 29 | itables.show(df, lengthMenu=[2, 5, 10, 20, 50]) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/options/options.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Options 16 | 17 | ## DataTable Options 18 | 19 | ITables is a wrapper for the Javascript DataTables library, which means that you can use more or less directly the DataTables [options](https://datatables.net/options) from within Python. 20 | 21 | Since ITables just maps these options to DataTables, you are invited to have a look at DataTable's great [documentation](https://datatables.net/), and to its huge collection of [examples](https://datatables.net/examples/index). The DataTable [forum](https://datatables.net/forums/) can be quite useful as well. 22 | 23 | A non-exhaustive list of the DataTable options, together with their expected types, is available at [`itables.typing.DataTableOptions`](https://github.com/mwouts/itables/blob/main/src/itables/typing.py). 24 | 25 | Option names and types are checked by default at run time when `typeguard>=4.4.1` is installed - you can deactivate this by setting `warn_on_undocumented_option=False`. 26 | 27 | If you see an option that you find useful and is not documented, or a type hint that is incorrect, please make a PR (and add an example to the documentation, too). 28 | 29 | ```{code-cell} ipython3 30 | :tags: [scroll-output] 31 | 32 | import inspect 33 | 34 | import itables 35 | 36 | print(inspect.getsource(itables.typing.DataTableOptions)) 37 | ``` 38 | 39 | ## ITable Options 40 | 41 | ITables itself adds a few options like `connected`, `maxBytes`, `allow_html` etc. 42 | 43 | The ITable options are documented at [`itables.typing.ITableOptions`](https://github.com/mwouts/itables/blob/main/src/itables/typing.py): 44 | 45 | ```{code-cell} ipython3 46 | :tags: [scroll-output] 47 | 48 | print(inspect.getsource(itables.typing.ITableOptions)) 49 | ``` 50 | 51 | ## Default values 52 | 53 | Some of the options have a default value set in [`itables.options`](https://github.com/mwouts/itables/blob/main/src/itables/options.py). You can change these defaults easily, and even set defauts for the options that don't have one yet with e.g. 54 | 55 | ```python 56 | import itables 57 | 58 | itables.options.maxBytes = "128KB" 59 | ``` 60 | 61 | ```{code-cell} ipython3 62 | :tags: [scroll-output] 63 | 64 | print(inspect.getsource(itables.options)) 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/options/order.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Order 16 | 17 | Since ITables v1.3.0, the interactive datatable shows the rows in the same order as the original dataframe. 18 | 19 | You can pre-select a explicit order with the [`order`](https://datatables.net/reference/option/order) option: 20 | 21 | ```{code-cell} ipython3 22 | import pandas as pd 23 | 24 | import itables 25 | 26 | itables.init_notebook_mode() 27 | 28 | sorted_df = pd.DataFrame({"a": [2, 1]}, index=pd.Index([1, 2], name="i")) 29 | itables.show(sorted_df, order=[[1, "asc"]]) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/options/paging.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Paging 16 | 17 | Use [`paging=False`](https://datatables.net/reference/option/paging) to show the table in full: 18 | 19 | ```{code-cell} ipython3 20 | import itables 21 | 22 | itables.init_notebook_mode() 23 | df = itables.sample_dfs.get_countries(html=False) 24 | ``` 25 | 26 | ```{code-cell} ipython3 27 | :tags: [full-width] 28 | 29 | itables.show(df.head(8), paging=False) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/options/row_group.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # pyright: reportUnknownMemberType=false 19 | ``` 20 | 21 | # RowGroup 22 | 23 | Use the [RowGroup](https://datatables.net/extensions/rowgroup/) extension to group 24 | the data according to the content of one colum. Optionally, you can hide the content of that column to avoid duplicating the information. 25 | 26 | ```{code-cell} ipython3 27 | :tags: [full-width] 28 | 29 | import itables 30 | 31 | itables.init_notebook_mode() 32 | 33 | df = itables.sample_dfs.get_countries(html=False) 34 | 35 | itables.show( 36 | df.sort_values("region"), 37 | rowGroup={"dataSrc": 1}, 38 | columnDefs=[{"targets": 1, "visible": False}], 39 | ) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/options/scroll_x.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Horizontal Scroll 16 | 17 | DataTables has a [`scrollX`](https://datatables.net/reference/option/scrollX) option that might be helpful for wide tables. 18 | 19 | ```{tip} 20 | Since ITables v2.1.2, the `.dt-layout-table` div has a default overflow equal to `auto`, so in most cases you don't need to use `scrollX`. 21 | ``` 22 | 23 | ```{code-cell} ipython3 24 | :tags: [full-width] 25 | 26 | import itables 27 | 28 | itables.init_notebook_mode() 29 | 30 | df = itables.sample_dfs.get_dict_of_test_dfs()["wide"] 31 | itables.show(df, scrollX=True) 32 | ``` 33 | 34 | ```{warning} 35 | Using `scrollX` on a table that is not wide enough can lead to an issue where the table headers are not aligned with the table content. 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/options/scroll_y.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Vertical Scroll 16 | 17 | The [`scrollY`](https://datatables.net/examples/basic_init/scroll_y.html) parameter is an interesting alternative to the pagination: 18 | 19 | ```{code-cell} ipython3 20 | :tags: [full-width] 21 | 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | 26 | df = itables.sample_dfs.get_countries(html=False) 27 | itables.show(df, scrollY="350px", scrollCollapse=True, paging=False) 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/options/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Search 16 | 17 | The [search option](https://datatables.net/reference/option/search) let you control the initial value for the search field, and whether the query should be treated as a regular expression or not: 18 | 19 | ```{code-cell} ipython3 20 | import itables 21 | 22 | itables.init_notebook_mode() 23 | ``` 24 | 25 | ```{code-cell} ipython3 26 | :tags: [full-width] 27 | 28 | df = itables.sample_dfs.get_countries(html=False) 29 | itables.show(df, search={"regex": True, "caseInsensitive": True, "search": "s.ain"}) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/options/search_builder.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Search Builder 16 | 17 | [SearchBuilder](https://datatables.net/extensions/searchbuilder/) let you build complex search queries. You just need to add it to the layout 18 | by passing e.g. `layout={"top1": "searchBuilder"}`. 19 | 20 | It is possible to set a predefined search, as we do in the below: 21 | 22 | ```{code-cell} ipython3 23 | :tags: [full-width] 24 | 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | 29 | df = itables.sample_dfs.get_countries(html=False, climate_zone=True) 30 | 31 | itables.show( 32 | df, 33 | layout={"top1": "searchBuilder"}, 34 | searchBuilder={ 35 | "preDefined": { 36 | "criteria": [ 37 | {"data": "climate_zone", "condition": "=", "value": ["Sub-tropical"]} 38 | ] 39 | } 40 | }, 41 | ) 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/options/search_panes.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Search Panes 16 | 17 | [SearchPanes](https://datatables.net/extensions/searchpanes/) is an extension that lets you select rows based on unique values. In the example below we have activated the cascade filtering through the [`searchPanes.cascadePanes`](https://datatables.net/extensions/searchpanes/examples/initialisation/cascadePanes.html) argument. 18 | 19 | Note that, in Jupyter, the [`searchPanes.layout`](https://datatables.net/extensions/searchpanes/layout) argument is required (otherwise the search panes are too wide). 20 | 21 | ```{code-cell} ipython3 22 | :tags: [full-width] 23 | 24 | import itables 25 | 26 | itables.init_notebook_mode() 27 | 28 | df = itables.sample_dfs.get_countries(html=False, climate_zone=True) 29 | 30 | itables.show( 31 | df.reset_index(), 32 | layout={"top1": "searchPanes"}, 33 | searchPanes={"layout": "columns-3", "cascadePanes": True, "columns": [1, 6, 7]}, 34 | ) 35 | ``` 36 | 37 | ```{warning} 38 | When searching, please keep in mind that ITables will [downsample](../downsampling.md) your table if it is larger than `maxBytes`, so you might not see the full dataset - pay attention to the downsampling message at the bottom left of the table. 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/options/select.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Select 16 | 17 | The [select](https://datatables.net/extensions/select) extension let you select rows (or cells). 18 | 19 | ~~~{admonition} The `selected_rows` attribute 20 | :class: tip 21 | It is possible to access the `selected_rows` back in Python but for this you will have to use, instead of `show`, either 22 | - the `ITable` [Widget](../apps/widget.md) 23 | - the `ITable` [Dash component](../apps/dash.md) 24 | - the `interactive_table` [Streamlit component](../apps/streamlit.md) 25 | 26 | ⚠ When a table has been downsampled, only the visible rows can be selected. 27 | ~~~ 28 | 29 | ```{tip} 30 | The `select` option also interacts with the [`buttons`](buttons.md) extension. If you click on the CSV or Excel export while having selected some rows, only those rows will be exported - see the example below. 31 | ``` 32 | 33 | ```{code-cell} ipython3 34 | import itables 35 | 36 | itables.init_notebook_mode() 37 | 38 | itables.show( 39 | itables.sample_dfs.get_countries(html=False), 40 | select=True, 41 | selected_rows=[2, 4, 5], 42 | buttons=["copyHtml5", "csvHtml5", "excelHtml5"], 43 | ) 44 | ``` 45 | 46 | ```{tip} 47 | The `select` option accept multiple values, as documented [here](https://datatables.net/extensions/select): 48 | - `select=True` or `select="os"` let you select using single click, shift-click and ctrl-click 49 | - `select="single"` let you select a single row 50 | - `select="multi"` for single click selection of multiple rows, `select="multi+shift"`, ... 51 | 52 | With `select={"style": "os", "items": "cell"}` you can let the user select specific cells, however the cell selection is not exposed in Python, nor taken into account when exporting the data. 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/options/show_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Show Index 16 | 17 | By default, the index of a Series/DataFrame is shown only when not trivial, i.e. when it has a name, or when it differs from a range index. If you prefer, you can change the value of `showIndex` to either `True` or `False` to always or never show the index (the default value being `"auto"`). 18 | 19 | ```{code-cell} ipython3 20 | import pandas as pd 21 | 22 | import itables 23 | 24 | itables.init_notebook_mode() 25 | ``` 26 | 27 | You can change this behavior globally with e.g. 28 | ```python 29 | itables.options.showIndex = True 30 | ``` 31 | 32 | or locally by passing an argument `showIndex` to the `show` function: 33 | 34 | ```{code-cell} ipython3 35 | df_with_range_index = pd.DataFrame({"letter": list("abcd")}) 36 | itables.show(df_with_range_index, showIndex=True) 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/options/state_save.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # State Save 16 | 17 | To preserve the table state, like the [selected columns](colvis.md) or the [row order](order.md) you can use the [`stateSave`](https://datatables.net/reference/option/stateSave) option. This will preserve the table state when the page is reloaded, or when an ITable component is updated. 18 | 19 | ```{tip} 20 | You might also want to adjust [`stateDuration`](https://datatables.net/reference/option/stateDuration) which defaults to 2 hours. 21 | ``` 22 | 23 | Try the option on this page: re-order the table by clicking on one column, then refresh the page - the order will be preserved. 24 | 25 | ```{code-cell} ipython3 26 | :tags: [full-width] 27 | 28 | import itables 29 | 30 | itables.init_notebook_mode() 31 | 32 | df = itables.sample_dfs.get_countries(html=False) 33 | 34 | itables.show(df, stateSave=True) 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/options/style.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Style 16 | 17 | The `show` function has a `style` argument that determines the layout for a particular table. 18 | 19 | The default value for `style` is `"table-layout:auto;width:auto;margin:auto;caption-side:bottom"`. 20 | - Without `width:auto`, tables with few columns are stretched to fit the container width. 21 | - Using `margin:auto` makes non-wide tables centered. 22 | 23 | ```{code-cell} ipython3 24 | :tags: [full-width] 25 | 26 | import pandas as pd 27 | 28 | import itables 29 | 30 | itables.init_notebook_mode() 31 | 32 | df_small = pd.DataFrame({"a": [2, 1]}) 33 | ``` 34 | 35 | You can set a specific width or position for a table using with the `style` argument of the show function: 36 | 37 | ```{code-cell} ipython3 38 | itables.show(df_small, style="table-layout:auto;width:50%;float:right") 39 | ``` 40 | 41 | or you can also change it for all tables by changing `itables.options.style`: 42 | 43 | ```python 44 | itables.options.style = "table-layout:auto;width:auto" 45 | ``` 46 | 47 | ```{tip} 48 | The height of a table is governed by either [`lengthMenu`](length_menu.md) or by [`scrollY`](scroll_y.md). 49 | ``` 50 | 51 | If you are looking for changing the table content appeareance, see the page on [CSS](../css.md), or the page on [Pandas Style](../pandas_style.md). 52 | -------------------------------------------------------------------------------- /docs/options/text_in_header_can_be_selected.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | # Select Text in Header 16 | 17 | The `text_in_header_can_be_selected` option, which defaults to `True`, is provided by ITables since v2.4.0. 18 | 19 | With that option set (the default), you can select the text in the table headers. This is useful in the context of data exploration where 20 | you need to copy back the column name to your code. 21 | 22 | ```{code-cell} ipython3 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | df = itables.sample_dfs.get_countries(html=False) 27 | 28 | itables.show(df, "A table in which column headers can be selected") 29 | ``` 30 | 31 | When `text_in_header_can_be_selected=False`, the column title cannot be selected as clicking on the title sorts the table. 32 | 33 | ```{code-cell} ipython3 34 | itables.show( 35 | df, 36 | "A table in which column headers cannot be selected", 37 | text_in_header_can_be_selected=False, 38 | ) 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/py/README.md: -------------------------------------------------------------------------------- 1 | This folders contains the `py:percent` representation of the documentation notebooks. 2 | 3 | This is used to enforce formatting (black, isort) and quality checks (ruff, pyright) on the documentation. 4 | 5 | NB: These files are kept in sync with the markdown documentation thanks to our Jupytext pre-commit hook in `.pre-commit-config.yaml`. You can edit either the `.py` or `.md` version and the pre-commit hook will update the other. 6 | -------------------------------------------------------------------------------- /docs/py/apps/dash.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: docs///md:myst,docs/py///py:percent 5 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 6 | # text_representation: 7 | # extension: .py 8 | # format_name: percent 9 | # format_version: '1.3' 10 | # kernelspec: 11 | # display_name: itables 12 | # language: python 13 | # name: itables 14 | # --- 15 | 16 | # %% [markdown] 17 | # # Dash 18 | # 19 | # 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: 20 | # 21 | # ```{include} ../../apps/dash/1_display_only.py 22 | # :code: python 23 | # ``` 24 | # 25 | # ## Selected rows 26 | # 27 | # 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")`. 28 | # 29 | # ```{include} ../../apps/dash/2_selected_rows.py 30 | # :code: python 31 | # ``` 32 | # 33 | # ## Updating the DataFrame 34 | # 35 | # The `ITable` component has many properties. These properties (table content, 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)): 36 | # 37 | # ```python 38 | # from itables.dash import ITable, ITableOutputs, updated_itable_outputs 39 | # 40 | # # (...) 41 | # 42 | # @callback( 43 | # ITableOutputs("my_dataframe"), 44 | # [ 45 | # Input("checklist", "value"), 46 | # Input("caption", "value"), 47 | # State("my_dataframe", "selected_rows"), 48 | # State("my_dataframe", "dt_args"), 49 | # ], 50 | # ) 51 | # def update_table(checklist, caption, selected_rows, dt_args): 52 | # if checklist is None: 53 | # checklist = [] 54 | # 55 | # kwargs = {} 56 | # 57 | # # When df=None and when the dt_args don't change, the table is not updated 58 | # if callback_context.triggered_id == "checklist": 59 | # kwargs["df"] = get_countries(html="HTML" in checklist) 60 | # 61 | # kwargs["select"] = "Select" in checklist 62 | # if "Buttons" in checklist: 63 | # kwargs["buttons"] = ["copyHtml5", "csvHtml5", "excelHtml5"] 64 | # 65 | # return updated_itable_outputs( 66 | # caption=caption, selected_rows=selected_rows, current_dt_args=dt_args, **kwargs 67 | # ) 68 | # ``` 69 | -------------------------------------------------------------------------------- /docs/py/apps/html.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnknownVariableType=false 19 | 20 | # %% [markdown] 21 | # # HTML export 22 | # 23 | # To get the HTML representation of a Pandas DataFrame `df` as an interactive [DataTable](https://datatables.net/), you can use `to_html_datatable` as below: 24 | 25 | # %% 26 | from IPython.display import HTML, display 27 | 28 | import itables 29 | 30 | df = itables.sample_dfs.get_countries(html=False) 31 | html = itables.to_html_datatable(df.head(3), display_logo_when_loading=False) 32 | 33 | # %% [markdown] 34 | # You can then save the `html` variable to a text file (note: if you're writing an HTML application, you could consider using [Shiny](shiny.md) or [Streamlit](streamlit.md) instead), or print it: 35 | 36 | # %% tags=["scroll-output"] 37 | print(html) 38 | 39 | # %% [markdown] 40 | # or display it, like `show` does: 41 | 42 | # %% 43 | display(HTML(html)) 44 | 45 | # %% [markdown] 46 | # ~~~{admonition} The `connected` argument 47 | # :class: tip 48 | # The `to_html_datatable` function has a `connected` argument which defaults to what you set in `init_notebook_mode` (it's `True` if you didn't call it). 49 | # 50 | # - With `connected=True` you get an autonomous HTML fragment that loads `dt_for_itables` from the Internet 51 | # - With `connected=False`, the HTML snippet works only after you add the output of `generate_init_offline_itables_html()` to your HTML document 52 | # ~~~ 53 | -------------------------------------------------------------------------------- /docs/py/apps/marimo.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Marimo 19 | # 20 | # ```{warning} 21 | # The `init_notebook_mode` and the `show` function do not work in Marimo. This is because they both use `IPython.display` to display the HTML representation of the table, which is not a good fit for Marimo. 22 | # ``` 23 | # 24 | # In Marimo the recommended way to use ITable is through the `ITable` [widget](widget.md): 25 | 26 | # %% 27 | import pandas as pd 28 | 29 | from itables.widget import ITable 30 | 31 | df = pd.DataFrame({"x": [2, 1, 3]}) 32 | 33 | ITable(df) 34 | 35 | # %% [markdown] 36 | # A Sample Marimo application is available at [`apps/marimo/widget.py`](https://github.com/mwouts/itables/tree/main/apps/marimo/widget.py). 37 | # 38 | # 39 | # ## Using HTML 40 | # 41 | # You can also use `to_html_datatable` in combination with `mo.iframe` like in this example - but, as there is no `init_notebook_mode` cell, you will have to use the connected mode: 42 | # 43 | # ```python 44 | # import marimo as mo 45 | # 46 | # from itables import to_html_datatable 47 | # 48 | # html = to_html_datatable(df, connected=True) 49 | # mo.iframe(html) 50 | # ``` 51 | # 52 | # A Sample Marimo application that uses `to_html_datatable` is available at [`apps/marimo/html.py`](https://github.com/mwouts/itables/tree/main/apps/marimo/html.py). 53 | -------------------------------------------------------------------------------- /docs/py/apps/notebook.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Notebook Mode 19 | # 20 | # Activate ITables in a Jupyter environment for all your tables with `init_notebook_mode`: 21 | 22 | # %% 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | # %% [markdown] 28 | # You can go back to the standard HTML representation of Pandas DataFrames with `init_notebook_mode(all_interactive=False)`. 29 | # 30 | # Note that the `init_connected_mode` function also activates ITable's offline mode, unless you call it with a `connected=False` argument. 31 | # 32 | # 33 | # ## Offline mode 34 | # 35 | # By default `init_connected_mode` configures ITables to work offline (except in Colab). No internet connection is required as the JavaScript code is embedded into the notebook itself when you execute `init_notebook_mode`. 36 | # 37 | # In some contexts (Jupyter Book, Google Colab, etc...) you might 38 | # prefer to load the libraries dynamically from the internet. 39 | # To do so, add the argument `connected=True` when you 40 | # execute `init_notebook_mode`. This will also make your notebook lighter by 41 | # about [700kB](https://github.com/mwouts/itables/blob/main/tests/test_connected_notebook_is_small.py). Note that, in Google Colab, `connected=True` is the only working option. 42 | # 43 | # ## Show 44 | # 45 | # If you prefer to render only certain tables using `itables`, or want to set additional options, use `show`: 46 | 47 | # %% 48 | df = itables.sample_dfs.get_countries(html=False) 49 | 50 | itables.show( 51 | df, 52 | caption="A DataFrame rendered with ITables", 53 | lengthMenu=[2, 5, 10, 25, 50, 100, 250], 54 | ) 55 | 56 | # %% [markdown] 57 | # ## HTML 58 | # 59 | # The `show` function simply displays the HTML snippet for the table, which is obtained with `to_html_datatable`. See more in the section on [HTML export](html.md). 60 | -------------------------------------------------------------------------------- /docs/py/contributing.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnknownVariableType=false 19 | 20 | # %% [markdown] 21 | # # Contributing 22 | # 23 | # Thanks for considering making a contribution to ITables. There are 24 | # many ways you can help! 25 | # 26 | # ## Report an issue 27 | # 28 | # If you see an issue, a possible improvement, or if you can't find 29 | # the answer to your question, then you are very welcome to create 30 | # an issue on this project. Please provide enough detail so that 31 | # we can reproduce the issue. 32 | # 33 | # ## Improve the documentation 34 | # 35 | # If you would like to add a new example, 36 | # or improve the documentation, feel free to make a pull request! 37 | # 38 | # You can render the documentation locally - see the section on 39 | # [Jupyter Book](developing.md#jupyter-book) in the developer guide. 40 | # 41 | # ## Give credit to ITables 42 | # 43 | # It's always great to see new stars coming to ITables! 44 | # 45 | # 46 | # If you wanted to share a link to ITables and DataTables (no obligation whatsoever), you could use something like this: 47 | 48 | # %% 49 | from IPython.display import HTML, display 50 | 51 | display( 52 | HTML( 53 | """ 54 | Tables displayed with ITables, 55 | a Python wrapper for DataTables 56 | """ 57 | ) 58 | ) 59 | 60 | # %% [markdown] 61 | # ## Support DataTables 62 | # 63 | # Allan Jardine, the main developer of DataTables, has done a fantastic work on [DataTables](https://datatables.net/). 64 | # 65 | # If you enjoy his library, you could become a 66 | # [supporter](https://datatables.net/supporters/) - 67 | # contributions range from 9 to 99$/year before VAT. 68 | # Or you could take a subscription for DataTable's [Editor](https://editor.datatables.net) 69 | # that ITables might support in the future (please subscribe to [#243](https://github.com/mwouts/itables/issues/243) for updates). 70 | # 71 | # ## Develop a new feature 72 | # 73 | # It is generally a good idea to get in touch with us first - e.g. 74 | # open an issue and let us know what you'd like to do. 75 | # 76 | # But you can also simply clone the project and test your ideas. 77 | # A guide on how to set up a development environment, and how to 78 | # run some tests, is available [here](developing.md). 79 | -------------------------------------------------------------------------------- /docs/py/css.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # ruff: noqa: E402 19 | # pyright: reportUnknownVariableType=false 20 | 21 | # %% [markdown] 22 | # # CSS 23 | # 24 | # You can use CSS to alter how tables are rendered. 25 | # 26 | # For instance, we change the 27 | # [font size](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size) 28 | # for all the tables in the document with this code: 29 | 30 | # %% 31 | from IPython.display import HTML, display 32 | 33 | css = """ 34 | .dt-container { 35 | font-size: small; 36 | } 37 | """ 38 | display(HTML(f"" "")) 39 | 40 | # %% [markdown] 41 | # This is helpful for instance in the context of 42 | # [Quarto presentations](apps/quarto.md). 43 | # 44 | # With this over CSS, we change _every datatable_ table header 45 | # in the notebook to bold/italic. 46 | 47 | # %% 48 | css = """ 49 | .dataTable th { 50 | font-weight: bolder; 51 | font-style: italic; 52 | } 53 | """ 54 | display(HTML(f"" "")) 55 | 56 | # %% [markdown] 57 | # You might also want to alter the style of specific tables only. 58 | # To do this, add a new class to the target tables, as 59 | # in the example below: 60 | 61 | # %% 62 | class_specific_css = ".table_with_monospace_font { font-family: courier, monospace }" 63 | display(HTML(f"" "")) 64 | 65 | # %% tags=["full-width"] 66 | import itables 67 | 68 | itables.init_notebook_mode() 69 | 70 | df = itables.sample_dfs.get_countries(html=False) 71 | 72 | itables.show(df, classes="display nowrap table_with_monospace_font") 73 | -------------------------------------------------------------------------------- /docs/py/dark_mode.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # formats: docs///md:myst,docs/py///py:percent 5 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 6 | # text_representation: 7 | # extension: .py 8 | # format_name: percent 9 | # format_version: '1.3' 10 | # kernelspec: 11 | # display_name: itables 12 | # language: python 13 | # name: itables 14 | # --- 15 | 16 | # %% [markdown] 17 | # # Dark Themes 18 | # 19 | # When a notebook or application is rendered using a dark theme, DataTable requires that a `dark` 20 | # class be added to the HTML document. This can be done with the following Javascript snippet: 21 | # ```javascript 22 | # document.documentElement.classList.add('dark'); 23 | # ``` 24 | # 25 | # When ITables is used in a notebook, this is handled by 26 | # `init_notebook_mode` which displays the [`init_datatables.html`](https://github.com/mwouts/itables/blob/main/src/itables/html/init_datatables.html) snippet. 27 | # 28 | # Please open a PR if you see how to further improve the 29 | # support of light vs dark themes, and e.g. set the `dark` 30 | # class dynamically when the theme is changed. 31 | -------------------------------------------------------------------------------- /docs/py/downsampling.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnusedExpression=false 19 | 20 | # %% [markdown] 21 | # # Downsampling 22 | # 23 | # When an interactive table is displayed by `itables`, the table data is embedded in the notebook output. As we don't want your notebook to become super heavy just because you displayed a large table, we have a downsampling mechanism in place. 24 | # 25 | # When the data in a table is larger than `maxBytes`, which is equal to 64KB by default, `itables` will display only a subset of the table - one that fits into `maxBytes`, and display a warning that points to the `itables` documentation. 26 | # 27 | # If you wish, you can increase the value of `maxBytes` or even deactivate the limit (with `maxBytes=0`). Similarly, you can set a limit on the number of rows (`maxRows`, defaults to 0) or columns (`maxColumns`, defaults to `200`). 28 | 29 | # %% 30 | import itables 31 | 32 | itables.init_notebook_mode() 33 | 34 | # %% 35 | itables.options.lengthMenu = [2, 5, 10, 20, 50, 100, 200, 500] 36 | itables.options.maxBytes = "8KB" 37 | 38 | df = itables.sample_dfs.get_indicators() 39 | itables.downsample.as_nbytes(itables.options.maxBytes), itables.downsample.nbytes(df) 40 | 41 | # %% tags=["full-width"] 42 | df 43 | 44 | # %% [markdown] 45 | # To show the table in full, we can modify the value of `maxBytes` either locally: 46 | 47 | # %% tags=["full-width"] 48 | itables.show(df, maxBytes=0) 49 | 50 | # %% [markdown] 51 | # or globally: 52 | 53 | # %% tags=["full-width"] 54 | itables.options.maxBytes = "1MB" 55 | df 56 | -------------------------------------------------------------------------------- /docs/py/options/allow_html.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnknownVariableType=false 19 | # pyright: reportUnknownArgumentType=false 20 | # pyright: reportUnknownMemberType=false 21 | 22 | # %% [markdown] 23 | # # Allow HTML 24 | # 25 | # Since v2.4.0, the HTML content in your tables is escaped by default. You can change this by passing `allow_html=True`. 26 | # 27 | # ```{warning} 28 | # Please make sure that you trust the content of your tables before allowing HTML content. When `allow_html=True`, ITables passes the table content verbatim to datatables.net, and the HTML will be executed. 29 | # 30 | # If you did not produce the non-numeric content yourself, you should sanitize it using e.g. `rh3` or `bleach`. 31 | # 32 | # See also this datatable page on [security](https://datatables.net/manual/security). 33 | # ``` 34 | # 35 | # ## Text formatting 36 | # 37 | # When you allow HTML content, you can have formatted text, 38 | # links or even images in your tables: 39 | 40 | # %% 41 | import pandas as pd 42 | 43 | import itables 44 | 45 | itables.init_notebook_mode() 46 | 47 | itables.show( 48 | pd.Series( 49 | [ 50 | "bold", 51 | "italic", 52 | 'link', 53 | ], 54 | name="HTML", 55 | ), 56 | allow_html=True, 57 | ) 58 | 59 | # %% [markdown] 60 | # ## Images in a table 61 | 62 | # %% tags=["full-width"] 63 | df = itables.sample_dfs.get_countries(html=False) 64 | 65 | df["flag"] = [ 66 | '' 67 | ''.format(code=code.lower(), country=country) 69 | for code, country in zip(df.index, df["country"]) 70 | ] 71 | df["country"] = [ 72 | '{}'.format(country, country) 73 | for country in df["country"] 74 | ] 75 | df["capital"] = [ 76 | '{}'.format(capital, capital) 77 | for capital in df["capital"] 78 | ] 79 | itables.show(df, allow_html=True) 80 | 81 | # %% [markdown] 82 | # ## Base64 images 83 | # 84 | # [Base64 encoded image](https://stackoverflow.com/a/8499716/9817073) are supported, too: 85 | 86 | # %% 87 | itables.show( 88 | pd.Series( 89 | { 90 | "url": 'MNIST', 91 | "base64": 'Red dot', 94 | }, 95 | name="Images", 96 | ), 97 | allow_html=True, 98 | ) 99 | -------------------------------------------------------------------------------- /docs/py/options/buttons.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Buttons 19 | # 20 | # The DataTables [buttons](https://datatables.net/extensions/buttons/) let you copy the table data, or export it as CSV or Excel files. 21 | # 22 | # To display the buttons, you need to pass a `buttons` argument to the `show` function: 23 | 24 | # %% 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | 29 | df = itables.sample_dfs.get_countries(html=False) 30 | 31 | itables.show(df, buttons=["pageLength", "copyHtml5", "csvHtml5", "excelHtml5"]) 32 | 33 | # %% [markdown] 34 | # You can also specify a [`layout`](layout) modifier that will decide 35 | # the location of the buttons (the default is `layout={"topStart": "buttons"}`). And if you want to keep the pagination control too, you can add `"pageLength"` to the list of buttons - as done above. 36 | # 37 | # As always, it is possible to set default values for these parameters by setting these on `itables.options`. For instance, set 38 | # ```python 39 | # itables.options.buttons = ["copyHtml5", "csvHtml5", "excelHtml5"] 40 | # ``` 41 | # to get the buttons for all your tables. 42 | # 43 | # 44 | # By default, the exported file name is the name of the HTML page. To change it, set a 45 | # [`title` option](https://datatables.net/extensions/buttons/examples/html5/filename.html) on the buttons, like 46 | # here: 47 | 48 | # %% tags=["full-width"] 49 | itables.show( 50 | df, 51 | buttons=[ 52 | "pageLength", 53 | {"extend": "csvHtml5", "title": "download_filename"}, 54 | {"extend": "excelHtml5", "title": "download_filename"}, 55 | ], 56 | ) 57 | 58 | # %% [markdown] 59 | # ```{tip} 60 | # Only the filtered or selected rows are exported to CSV/Excel. To filter the rows you can use the simple search box, the [SearchPanes](search_panes) and [SearchBuilder](search_builder) options, or the [select](select.md) extension. 61 | # ``` 62 | # 63 | # ```{warning} 64 | # At the moment, the CSV and Excel buttons don't work well with large tables in some browsers. 65 | # Please subscribe to [#251](https://github.com/mwouts/itables/issues/251) if you wish to receive updates on this. 66 | # ``` 67 | # 68 | # ```{warning} 69 | # The PDF button is not included in ITables' DataTable bundle. This is because the required PDF libraries have a large footprint on the bundle size. Still, you can add it to your custom bundle, see our page on how to bundle [custom extensions](../custom_extensions.md). 70 | # ``` 71 | -------------------------------------------------------------------------------- /docs/py/options/caption.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Caption 19 | 20 | # %% tags=["full-width"] 21 | import itables 22 | 23 | itables.init_notebook_mode() 24 | 25 | df = itables.sample_dfs.get_countries(html=False) 26 | itables.show(df, "Countries from the World Bank Database") 27 | 28 | # %% [markdown] 29 | # The caption appears at the bottom of the table by default: this is governed by `caption-side:bottom` 30 | # in the [`style` option](style) (but for some reason this is not effective in Jupyter Book 🤔). 31 | -------------------------------------------------------------------------------- /docs/py/options/classes.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Classes 19 | # 20 | # Select how your table looks like with the `classes` argument (defaults to `"display nowrap"`) of the `show` function, or by changing `itables.options.classes`. 21 | # 22 | # Add `"compact"` if you want a denser table: 23 | 24 | # %% tags=["full-width"] 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | 29 | df = itables.sample_dfs.get_countries(html=False) 30 | 31 | itables.show(df, classes="display nowrap compact") 32 | 33 | # %% [markdown] 34 | # Remove `"nowrap"` if you want the cell content to be wrapped: 35 | 36 | # %% tags=["full-width"] 37 | itables.show(df, classes="display") 38 | 39 | # %% [markdown] 40 | # [More options](https://datatables.net/manual/styling/classes#Table-classes) like `"cell-border"` are available: 41 | 42 | # %% tags=["full-width"] 43 | itables.show(df, classes="display nowrap cell-border") 44 | -------------------------------------------------------------------------------- /docs/py/options/column_defs.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnusedExpression=false 19 | 20 | # %% [markdown] 21 | # # Column Definitions 22 | # 23 | # The [`columnDefs.width`](https://datatables.net/reference/option/columns.width) argument let you adjust the column widths. 24 | # 25 | # Note that the default value of `style`, or of `autoWidth` (defaults to `True`), might override custom column widths, 26 | # so you might have to change their values as in the examples below. 27 | # 28 | # You can set a fixed width for all the columns with `"targets": "_all"`: 29 | 30 | # %% tags=["full-width"] 31 | import itables 32 | 33 | itables.init_notebook_mode() 34 | 35 | df = itables.sample_dfs.get_countries(html=False) 36 | 37 | itables.show( 38 | df, 39 | columnDefs=[{"width": "120px", "targets": "_all"}], 40 | style="width:1200px", 41 | autoWidth=False, 42 | ) 43 | 44 | # %% [markdown] 45 | # You can also adjust the width of selected columns only: 46 | 47 | # %% tags=["full-width"] 48 | itables.show( 49 | df, 50 | columnDefs=[{"width": "30%", "targets": [2, 3]}], 51 | style="width:100%;margin:auto", 52 | ) 53 | 54 | # %% [markdown] 55 | # If you wish you can also set a value for `columnDefs` permanently in `itables.options` as demonstrated in the cell alignment example below. 56 | # 57 | # You can use the DataTables [cell classes](https://datatables.net/manual/styling/classes#Cell-classes) like `dt-left`, `dt-center`, `dt-right` etc. to set the cell alignment. Specify it for one table by using the `columnDefs` argument of `show` 58 | 59 | # %% tags=["full-width"] 60 | itables.show(df, columnDefs=[{"className": "dt-center", "targets": "_all"}]) 61 | 62 | # %% [markdown] 63 | # or globally by setting `itables.options.columnDefs`: 64 | 65 | # %% tags=["full-width"] 66 | itables.options.columnDefs = [{"className": "dt-center", "targets": "_all"}] 67 | 68 | df 69 | -------------------------------------------------------------------------------- /docs/py/options/column_filters.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Column Filters 19 | # 20 | # Use `column_filters = "header"` or `"footer"` if you wish to display individual column filters 21 | # (remove the global search box with a [`layout`](layout) modifier if desired). 22 | 23 | # %% 24 | import pandas as pd 25 | 26 | import itables 27 | 28 | itables.init_notebook_mode() 29 | 30 | alpha_numeric_df = pd.DataFrame( 31 | [["one", 1.5], ["two", 2.3]], columns=["string", "numeric"] 32 | ) 33 | 34 | itables.show(alpha_numeric_df, column_filters="header", layout={"topEnd": None}) 35 | 36 | # %% [markdown] 37 | # As always you can set activate column filters by default with e.g. 38 | 39 | # %% 40 | itables.options.column_filters = "footer" 41 | 42 | # %% [markdown] 43 | # Column filters also work on dataframes with multiindex columns: 44 | 45 | # %% 46 | itables.sample_dfs.get_dict_of_test_dfs()["multiindex"] 47 | 48 | # %% tags=["remove-cell"] 49 | # Revert back to the default to avoid interactions with the tests 50 | itables.options.column_filters = False 51 | -------------------------------------------------------------------------------- /docs/py/options/colvis.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Column Visibility 19 | # 20 | # The [column visibility](https://datatables.net/extensions/buttons/examples/column_visibility/index.html) buttons of DataTables let you select which columns are visible. 21 | 22 | # %% tags=["full-width"] 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | df = itables.sample_dfs.get_countries(html=False) 28 | 29 | itables.show( 30 | # column visibility works best with a flat header 31 | df.reset_index(), 32 | buttons=["columnsToggle"], 33 | ) 34 | 35 | # %% [markdown] 36 | # ```{tip} 37 | # The column visibility button is available under many forms. 38 | # 39 | # Check-out `buttons=["colvis"]` for a [single](https://datatables.net/extensions/buttons/examples/column_visibility/simple.html) button. 40 | # 41 | # Extend the `colvis` button with the [collection layout](https://datatables.net/extensions/buttons/examples/column_visibility/layout.html). 42 | # 43 | # As always, when porting examples from DataTables to ITables, you will 44 | # have to convert the JavaScript notation (left) to Python (right) as in the below: 45 | # ::::{grid} 46 | # 47 | # :::{grid-item} 48 | # :outline: 49 | # :columns: 6 50 | # ~~~javascript 51 | # buttons: [ 52 | # { 53 | # extend: 'colvis', 54 | # collectionLayout: 'fixed columns', 55 | # popoverTitle: 'Column visibility control' 56 | # } 57 | # ] 58 | # ~~~ 59 | # ::: 60 | # :::{grid-item} 61 | # :outline: 62 | # :columns: 6 63 | # ~~~python 64 | # buttons = [ 65 | # { 66 | # "extend": "colvis", 67 | # "collectionLayout": "fixed columns", 68 | # "popoverTitle": "Column visibility control" 69 | # } 70 | # ] 71 | # ~~~ 72 | # ::: 73 | # 74 | # :::: 75 | # 76 | # ``` 77 | -------------------------------------------------------------------------------- /docs/py/options/fixed_columns.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # FixedColumns 19 | # 20 | # [FixedColumn](https://datatables.net/extensions/fixedcolumns/) is an extension 21 | # that let you fix some columns as you scroll horizontally. 22 | 23 | # %% 24 | import string 25 | 26 | import numpy as np 27 | import pandas as pd 28 | 29 | import itables 30 | 31 | itables.init_notebook_mode() 32 | 33 | wide_df = pd.DataFrame( 34 | { 35 | letter: np.random.normal(size=100) 36 | for letter in string.ascii_lowercase + string.ascii_uppercase 37 | } 38 | ) 39 | 40 | itables.show( 41 | wide_df, 42 | fixedColumns={"start": 1, "end": 2}, 43 | scrollX=True, 44 | ) 45 | -------------------------------------------------------------------------------- /docs/py/options/footer.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Footer 19 | # 20 | # Use `footer = True` if you wish to display a table footer. 21 | 22 | # %% tags=["full-width"] 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | df = itables.sample_dfs.get_countries(html=False) 28 | itables.show(df, footer=True) 29 | -------------------------------------------------------------------------------- /docs/py/options/keys.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Keys 19 | # 20 | # With the [KeyTable](https://datatables.net/extensions/keytable/) extension you can navigate in a table using the arrow keys: 21 | 22 | # %% tags=["full-width"] 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | itables.show( 28 | itables.sample_dfs.get_countries(html=False), 29 | keys=True, 30 | ) 31 | 32 | # %% [markdown] 33 | # ```{tip} 34 | # You can activate this option for all your tables with 35 | # 36 | # ~~~python 37 | # itables.options.keys = True 38 | # ~~~ 39 | # ``` 40 | # 41 | # ```{warning} 42 | # The KeyTable extension works in Jupyter Book (try it here in the documentation) but not in JupyterLab. 43 | # ``` 44 | -------------------------------------------------------------------------------- /docs/py/options/layout.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Layout 19 | # 20 | # By default, datatables that don't fit in one page come with a search box, a pagination control, a table summary, etc. 21 | # You can select which elements are actually displayed using 22 | # DataTables' [`layout` option](https://datatables.net/reference/option/layout) with e.g.: 23 | 24 | # %% 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | df = itables.sample_dfs.get_countries(html=False) 29 | 30 | # %% 31 | itables.show(df, layout={"topStart": "search", "topEnd": None}) 32 | 33 | # %% [markdown] 34 | # The available positions are `topStart, topEnd, bottomStart, bottomEnd`. You can also use `top2Start`, etc... (see more 35 | # in the [DataTables documentation](https://datatables.net/reference/option/layout)). 36 | # 37 | # Like for the other arguments of `show`, you can change the default value of the dom option with e.g.: 38 | # 39 | # ``` 40 | # itables.options.layout = { 41 | # "topStart": "pageLength", 42 | # "topEnd": "search", 43 | # "bottomStart": "info", 44 | # "bottomEnd": "paging" 45 | # } # (default value) 46 | # ``` 47 | # 48 | # ```{tip} 49 | # The `layout` option was introduced with `itables==2.0` and `DataTables==2.0` 50 | # and replaced the former [`dom` option](https://datatables.net/reference/option/dom). 51 | # ``` 52 | -------------------------------------------------------------------------------- /docs/py/options/length_menu.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # LengthMenu 19 | # 20 | # Select [how many entries](https://datatables.net/examples/advanced_init/length_menu.html) should appear at once in the table with either the `lengthMenu` argument of the `show` function, or with the global option `itables.options.lengthMenu`: 21 | 22 | # %% 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | df = itables.sample_dfs.get_countries(html=False) 27 | 28 | # %% tags=["full-width"] 29 | itables.show(df, lengthMenu=[2, 5, 10, 20, 50]) 30 | -------------------------------------------------------------------------------- /docs/py/options/options.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Options 19 | # 20 | # ## DataTable Options 21 | # 22 | # ITables is a wrapper for the Javascript DataTables library, which means that you can use more or less directly the DataTables [options](https://datatables.net/options) from within Python. 23 | # 24 | # Since ITables just maps these options to DataTables, you are invited to have a look at DataTable's great [documentation](https://datatables.net/), and to its huge collection of [examples](https://datatables.net/examples/index). The DataTable [forum](https://datatables.net/forums/) can be quite useful as well. 25 | # 26 | # A non-exhaustive list of the DataTable options, together with their expected types, is available at [`itables.typing.DataTableOptions`](https://github.com/mwouts/itables/blob/main/src/itables/typing.py). 27 | # 28 | # Option names and types are checked by default at run time when `typeguard>=4.4.1` is installed - you can deactivate this by setting `warn_on_undocumented_option=False`. 29 | # 30 | # If you see an option that you find useful and is not documented, or a type hint that is incorrect, please make a PR (and add an example to the documentation, too). 31 | 32 | # %% tags=["scroll-output"] 33 | import inspect 34 | 35 | import itables 36 | 37 | print(inspect.getsource(itables.typing.DataTableOptions)) 38 | 39 | # %% [markdown] 40 | # ## ITable Options 41 | # 42 | # ITables itself adds a few options like `connected`, `maxBytes`, `allow_html` etc. 43 | # 44 | # The ITable options are documented at [`itables.typing.ITableOptions`](https://github.com/mwouts/itables/blob/main/src/itables/typing.py): 45 | 46 | # %% tags=["scroll-output"] 47 | print(inspect.getsource(itables.typing.ITableOptions)) 48 | 49 | # %% [markdown] 50 | # ## Default values 51 | # 52 | # Some of the options have a default value set in [`itables.options`](https://github.com/mwouts/itables/blob/main/src/itables/options.py). You can change these defaults easily, and even set defauts for the options that don't have one yet with e.g. 53 | # 54 | # ```python 55 | # import itables 56 | # 57 | # itables.options.maxBytes = "128KB" 58 | # ``` 59 | 60 | # %% tags=["scroll-output"] 61 | print(inspect.getsource(itables.options)) 62 | -------------------------------------------------------------------------------- /docs/py/options/order.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Order 19 | # 20 | # Since ITables v1.3.0, the interactive datatable shows the rows in the same order as the original dataframe. 21 | # 22 | # You can pre-select a explicit order with the [`order`](https://datatables.net/reference/option/order) option: 23 | 24 | # %% 25 | import pandas as pd 26 | 27 | import itables 28 | 29 | itables.init_notebook_mode() 30 | 31 | sorted_df = pd.DataFrame({"a": [2, 1]}, index=pd.Index([1, 2], name="i")) 32 | itables.show(sorted_df, order=[[1, "asc"]]) 33 | -------------------------------------------------------------------------------- /docs/py/options/paging.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Paging 19 | # 20 | # Use [`paging=False`](https://datatables.net/reference/option/paging) to show the table in full: 21 | 22 | # %% 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | df = itables.sample_dfs.get_countries(html=False) 27 | 28 | # %% tags=["full-width"] 29 | itables.show(df.head(8), paging=False) 30 | -------------------------------------------------------------------------------- /docs/py/options/row_group.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # pyright: reportUnknownMemberType=false 19 | 20 | # %% [markdown] 21 | # # RowGroup 22 | # 23 | # Use the [RowGroup](https://datatables.net/extensions/rowgroup/) extension to group 24 | # the data according to the content of one colum. Optionally, you can hide the content of that column to avoid duplicating the information. 25 | 26 | # %% tags=["full-width"] 27 | import itables 28 | 29 | itables.init_notebook_mode() 30 | 31 | df = itables.sample_dfs.get_countries(html=False) 32 | 33 | itables.show( 34 | df.sort_values("region"), 35 | rowGroup={"dataSrc": 1}, 36 | columnDefs=[{"targets": 1, "visible": False}], 37 | ) 38 | -------------------------------------------------------------------------------- /docs/py/options/scroll_x.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Horizontal Scroll 19 | # 20 | # DataTables has a [`scrollX`](https://datatables.net/reference/option/scrollX) option that might be helpful for wide tables. 21 | # 22 | # ```{tip} 23 | # Since ITables v2.1.2, the `.dt-layout-table` div has a default overflow equal to `auto`, so in most cases you don't need to use `scrollX`. 24 | # ``` 25 | 26 | # %% tags=["full-width"] 27 | import itables 28 | 29 | itables.init_notebook_mode() 30 | 31 | df = itables.sample_dfs.get_dict_of_test_dfs()["wide"] 32 | itables.show(df, scrollX=True) 33 | 34 | # %% [markdown] 35 | # ```{warning} 36 | # Using `scrollX` on a table that is not wide enough can lead to an issue where the table headers are not aligned with the table content. 37 | # ``` 38 | -------------------------------------------------------------------------------- /docs/py/options/scroll_y.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Vertical Scroll 19 | # 20 | # The [`scrollY`](https://datatables.net/examples/basic_init/scroll_y.html) parameter is an interesting alternative to the pagination: 21 | 22 | # %% tags=["full-width"] 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | df = itables.sample_dfs.get_countries(html=False) 28 | itables.show(df, scrollY="350px", scrollCollapse=True, paging=False) 29 | -------------------------------------------------------------------------------- /docs/py/options/search.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Search 19 | # 20 | # The [search option](https://datatables.net/reference/option/search) let you control the initial value for the search field, and whether the query should be treated as a regular expression or not: 21 | 22 | # %% 23 | import itables 24 | 25 | itables.init_notebook_mode() 26 | 27 | # %% tags=["full-width"] 28 | df = itables.sample_dfs.get_countries(html=False) 29 | itables.show(df, search={"regex": True, "caseInsensitive": True, "search": "s.ain"}) 30 | -------------------------------------------------------------------------------- /docs/py/options/search_builder.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Search Builder 19 | # 20 | # [SearchBuilder](https://datatables.net/extensions/searchbuilder/) let you build complex search queries. You just need to add it to the layout 21 | # by passing e.g. `layout={"top1": "searchBuilder"}`. 22 | # 23 | # It is possible to set a predefined search, as we do in the below: 24 | 25 | # %% tags=["full-width"] 26 | import itables 27 | 28 | itables.init_notebook_mode() 29 | 30 | df = itables.sample_dfs.get_countries(html=False, climate_zone=True) 31 | 32 | itables.show( 33 | df, 34 | layout={"top1": "searchBuilder"}, 35 | searchBuilder={ 36 | "preDefined": { 37 | "criteria": [ 38 | {"data": "climate_zone", "condition": "=", "value": ["Sub-tropical"]} 39 | ] 40 | } 41 | }, 42 | ) 43 | -------------------------------------------------------------------------------- /docs/py/options/search_panes.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Search Panes 19 | # 20 | # [SearchPanes](https://datatables.net/extensions/searchpanes/) is an extension that lets you select rows based on unique values. In the example below we have activated the cascade filtering through the [`searchPanes.cascadePanes`](https://datatables.net/extensions/searchpanes/examples/initialisation/cascadePanes.html) argument. 21 | # 22 | # Note that, in Jupyter, the [`searchPanes.layout`](https://datatables.net/extensions/searchpanes/layout) argument is required (otherwise the search panes are too wide). 23 | 24 | # %% tags=["full-width"] 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | 29 | df = itables.sample_dfs.get_countries(html=False, climate_zone=True) 30 | 31 | itables.show( 32 | df.reset_index(), 33 | layout={"top1": "searchPanes"}, 34 | searchPanes={"layout": "columns-3", "cascadePanes": True, "columns": [1, 6, 7]}, 35 | ) 36 | 37 | # %% [markdown] 38 | # ```{warning} 39 | # When searching, please keep in mind that ITables will [downsample](../downsampling.md) your table if it is larger than `maxBytes`, so you might not see the full dataset - pay attention to the downsampling message at the bottom left of the table. 40 | # ``` 41 | -------------------------------------------------------------------------------- /docs/py/options/select.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Select 19 | # 20 | # The [select](https://datatables.net/extensions/select) extension let you select rows (or cells). 21 | # 22 | # ~~~{admonition} The `selected_rows` attribute 23 | # :class: tip 24 | # It is possible to access the `selected_rows` back in Python but for this you will have to use, instead of `show`, either 25 | # - the `ITable` [Widget](../apps/widget.md) 26 | # - the `ITable` [Dash component](../apps/dash.md) 27 | # - the `interactive_table` [Streamlit component](../apps/streamlit.md) 28 | # 29 | # ⚠ When a table has been downsampled, only the visible rows can be selected. 30 | # ~~~ 31 | # 32 | # ```{tip} 33 | # The `select` option also interacts with the [`buttons`](buttons.md) extension. If you click on the CSV or Excel export while having selected some rows, only those rows will be exported - see the example below. 34 | # ``` 35 | 36 | # %% 37 | import itables 38 | 39 | itables.init_notebook_mode() 40 | 41 | itables.show( 42 | itables.sample_dfs.get_countries(html=False), 43 | select=True, 44 | selected_rows=[2, 4, 5], 45 | buttons=["copyHtml5", "csvHtml5", "excelHtml5"], 46 | ) 47 | 48 | # %% [markdown] 49 | # ```{tip} 50 | # The `select` option accept multiple values, as documented [here](https://datatables.net/extensions/select): 51 | # - `select=True` or `select="os"` let you select using single click, shift-click and ctrl-click 52 | # - `select="single"` let you select a single row 53 | # - `select="multi"` for single click selection of multiple rows, `select="multi+shift"`, ... 54 | # 55 | # With `select={"style": "os", "items": "cell"}` you can let the user select specific cells, however the cell selection is not exposed in Python, nor taken into account when exporting the data. 56 | # ``` 57 | -------------------------------------------------------------------------------- /docs/py/options/show_index.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Show Index 19 | # 20 | # By default, the index of a Series/DataFrame is shown only when not trivial, i.e. when it has a name, or when it differs from a range index. If you prefer, you can change the value of `showIndex` to either `True` or `False` to always or never show the index (the default value being `"auto"`). 21 | 22 | # %% 23 | import pandas as pd 24 | 25 | import itables 26 | 27 | itables.init_notebook_mode() 28 | 29 | # %% [markdown] 30 | # You can change this behavior globally with e.g. 31 | # ```python 32 | # itables.options.showIndex = True 33 | # ``` 34 | # 35 | # or locally by passing an argument `showIndex` to the `show` function: 36 | 37 | # %% 38 | df_with_range_index = pd.DataFrame({"letter": list("abcd")}) 39 | itables.show(df_with_range_index, showIndex=True) 40 | -------------------------------------------------------------------------------- /docs/py/options/state_save.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # State Save 19 | # 20 | # To preserve the table state, like the [selected columns](colvis.md) or the [row order](order.md) you can use the [`stateSave`](https://datatables.net/reference/option/stateSave) option. This will preserve the table state when the page is reloaded, or when an ITable component is updated. 21 | # 22 | # ```{tip} 23 | # You might also want to adjust [`stateDuration`](https://datatables.net/reference/option/stateDuration) which defaults to 2 hours. 24 | # ``` 25 | # 26 | # Try the option on this page: re-order the table by clicking on one column, then refresh the page - the order will be preserved. 27 | 28 | # %% tags=["full-width"] 29 | import itables 30 | 31 | itables.init_notebook_mode() 32 | 33 | df = itables.sample_dfs.get_countries(html=False) 34 | 35 | itables.show(df, stateSave=True) 36 | -------------------------------------------------------------------------------- /docs/py/options/style.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Style 19 | # 20 | # The `show` function has a `style` argument that determines the layout for a particular table. 21 | # 22 | # The default value for `style` is `"table-layout:auto;width:auto;margin:auto;caption-side:bottom"`. 23 | # - Without `width:auto`, tables with few columns are stretched to fit the container width. 24 | # - Using `margin:auto` makes non-wide tables centered. 25 | 26 | # %% tags=["full-width"] 27 | import pandas as pd 28 | 29 | import itables 30 | 31 | itables.init_notebook_mode() 32 | 33 | df_small = pd.DataFrame({"a": [2, 1]}) 34 | 35 | # %% [markdown] 36 | # You can set a specific width or position for a table using with the `style` argument of the show function: 37 | 38 | # %% 39 | itables.show(df_small, style="table-layout:auto;width:50%;float:right") 40 | 41 | # %% [markdown] 42 | # or you can also change it for all tables by changing `itables.options.style`: 43 | # 44 | # ```python 45 | # itables.options.style = "table-layout:auto;width:auto" 46 | # ``` 47 | # 48 | # ```{tip} 49 | # The height of a table is governed by either [`lengthMenu`](length_menu.md) or by [`scrollY`](scroll_y.md). 50 | # ``` 51 | # 52 | # If you are looking for changing the table content appeareance, see the page on [CSS](../css.md), or the page on [Pandas Style](../pandas_style.md). 53 | -------------------------------------------------------------------------------- /docs/py/options/text_in_header_can_be_selected.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% [markdown] 18 | # # Select Text in Header 19 | # 20 | # The `text_in_header_can_be_selected` option, which defaults to `True`, is provided by ITables since v2.4.0. 21 | # 22 | # With that option set (the default), you can select the text in the table headers. This is useful in the context of data exploration where 23 | # you need to copy back the column name to your code. 24 | 25 | # %% 26 | import itables 27 | 28 | itables.init_notebook_mode() 29 | df = itables.sample_dfs.get_countries(html=False) 30 | 31 | itables.show(df, "A table in which column headers can be selected") 32 | 33 | # %% [markdown] 34 | # When `text_in_header_can_be_selected=False`, the column title cannot be selected as clicking on the title sorts the table. 35 | 36 | # %% 37 | itables.show( 38 | df, 39 | "A table in which column headers cannot be selected", 40 | text_in_header_can_be_selected=False, 41 | ) 42 | -------------------------------------------------------------------------------- /docs/py/troubleshooting.py: -------------------------------------------------------------------------------- 1 | # --- 2 | # jupyter: 3 | # jupytext: 4 | # default_lexer: ipython3 5 | # formats: docs///md:myst,docs/py///py:percent 6 | # notebook_metadata_filter: -jupytext.text_representation.jupytext_version 7 | # text_representation: 8 | # extension: .py 9 | # format_name: percent 10 | # format_version: '1.3' 11 | # kernelspec: 12 | # display_name: itables 13 | # language: python 14 | # name: itables 15 | # --- 16 | 17 | # %% tags=["remove-cell"] 18 | # ruff: noqa: E402 19 | # pyright: reportUnusedExpression=false 20 | # pyright: reportDuplicateImport=false 21 | 22 | # %% [markdown] 23 | # # Troubleshooting 24 | # 25 | # ## Loading takes forever? 26 | 27 | # %% tags=["hide-input"] 28 | import pandas as pd 29 | 30 | import itables 31 | 32 | df = pd.DataFrame() 33 | 34 | itables.show(df, connected=False) 35 | 36 | # %% [markdown] 37 | # If a table says "Loading..." forever, then maybe 38 | # - You forgot to run `init_notebook_mode` (like in the example above), or you deleted that cell or its output 39 | # - Or you ran `init_notebook_mode(connected=True)` but you are not connected to the internet? 40 | # 41 | # ```{tip} 42 | # If you change the value of the `connected` argument in 43 | # the `init_notebook_mode` cell, you need to re-execute all the cells 44 | # that display interactive tables. 45 | # ``` 46 | # 47 | # ## Trust your notebook 48 | # 49 | # It could also be that your notebook is not _trusted_. This happens when you 50 | # have not run the notebook in full yourself (e.g. the notebook was sent to you with outputs, 51 | # or the notebook was created by a tool like `papermill`). In that case, JavaScript 52 | # code cannot run (and the interactive tables won't display) 53 | # until you tell Jupyter that you trust the notebook content 54 | # (run "Trust Notebook" in View / Activate Command Palette). 55 | # 56 | # ## Check ITables' version 57 | # 58 | # If the above does not help, please check out the [ChangeLog](changelog.md) 59 | # and decide whether you should upgrade `itables`. You can tell the version 60 | # of ITables that you are using by looking at the loading message (from ITables v2.0.1 on) 61 | # or by running this code snippet: 62 | 63 | # %% 64 | import itables 65 | 66 | itables.__version__ 67 | -------------------------------------------------------------------------------- /docs/references.md: -------------------------------------------------------------------------------- 1 | # References 2 | 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). 4 | 5 | ## ITables and its alternatives 6 | 7 | ### Jupyter Widgets 8 | 9 | In Jupyter or VS Code, ITables can render your dataframes using either HTML (with either `init_notebook_mode()`, or `init_notebook_mode(all_interactive=False)` and `show`) or the [`ITable` widget](apps/widget.md). 10 | 11 | Other Jupyter widgets that let you render a dataframe in Jupyter are 12 | - [QGrid](https://github.com/quantopian/qgrid) by Quantopian 13 | - [IPyaggrid](https://dgothrek.gitlab.io/ipyaggrid/) by Louis Raison and Olivier Borderies 14 | - [IPySheet](https://github.com/QuantStack/ipysheet) by QuantStack. 15 | 16 | ### Dash component 17 | 18 | Since ITables v2.3.0 you can use our [`ITable` component](apps/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](apps/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. 39 | -------------------------------------------------------------------------------- /docs/show_df.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/docs/show_df.png -------------------------------------------------------------------------------- /docs/supported_editors.md: -------------------------------------------------------------------------------- 1 | # Supported Editors 2 | 3 | ITables has been tested in the following environments 4 | 5 | ## Jupyter Notebook 6 | 7 | Try it on [![Notebook](https://img.shields.io/badge/Binder-JupyterNotebook-blue.svg)](https://mybinder.org/v2/gh/mwouts/itables/main?filepath=docs/quick_start.md) 8 | 9 | ![](images/notebook.png) 10 | 11 | ## Jupyter Lab 12 | 13 | Try it on [![Lab](https://img.shields.io/badge/Binder-JupyterLab-blue.svg)](https://mybinder.org/v2/gh/mwouts/itables/main?urlpath=lab/tree/docs/quick_start.md) 14 | 15 | ![](images/lab.png) 16 | 17 | ## Jupyter NB convert 18 | 19 | The tables are still interactive when you _download_ the notebook as an HTML file, or when you execute `jupyter nbconvert --to html`. 20 | 21 | ![](images/html.png) 22 | 23 | ## Jupyter Book 24 | 25 | The tables are interactive in interactive books powered by [Jupyter Book](https://jupyterbook.org), see e.g. the [ITables documentation](https://mwouts.github.io/itables/). 26 | 27 | ## Google Colab 28 | 29 | A short sample notebook is available [here](https://colab.research.google.com/drive/1JPZIasTiH3rIUysDr3eWDz4jgTTq00aq?usp=sharing) 30 | 31 | ![](images/colab.png) 32 | 33 | ## VS Code 34 | 35 | In VS Code, `itables` works both for Jupyter Notebooks and Python scripts. 36 | 37 | ![](images/code.png) 38 | 39 | ## PyCharm 40 | 41 | In PyCharm we recommend to call `init_notebook_mode` with the `connected=True` argument, 42 | because otherwise the notebooks do not display the interactive tables when they are reloaded. 43 | 44 | ![](images/pycharm.png) 45 | 46 | ## Quarto 47 | 48 | ITables works well with Quarto - check out our `html` and `revealjs` [examples](apps/quarto.md). 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | --- 2 | jupytext: 3 | formats: docs///md:myst,docs/py///py:percent 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 | 15 | ```{code-cell} ipython3 16 | :tags: [remove-cell] 17 | 18 | # ruff: noqa: E402 19 | # pyright: reportUnusedExpression=false 20 | # pyright: reportDuplicateImport=false 21 | ``` 22 | 23 | # Troubleshooting 24 | 25 | ## Loading takes forever? 26 | 27 | ```{code-cell} ipython3 28 | :tags: [hide-input] 29 | 30 | import pandas as pd 31 | 32 | import itables 33 | 34 | df = pd.DataFrame() 35 | 36 | itables.show(df, connected=False) 37 | ``` 38 | 39 | If a table says "Loading..." forever, then maybe 40 | - You forgot to run `init_notebook_mode` (like in the example above), or you deleted that cell or its output 41 | - Or you ran `init_notebook_mode(connected=True)` but you are not connected to the internet? 42 | 43 | ```{tip} 44 | If you change the value of the `connected` argument in 45 | the `init_notebook_mode` cell, you need to re-execute all the cells 46 | that display interactive tables. 47 | ``` 48 | 49 | ## Trust your notebook 50 | 51 | It could also be that your notebook is not _trusted_. This happens when you 52 | have not run the notebook in full yourself (e.g. the notebook was sent to you with outputs, 53 | or the notebook was created by a tool like `papermill`). In that case, JavaScript 54 | code cannot run (and the interactive tables won't display) 55 | until you tell Jupyter that you trust the notebook content 56 | (run "Trust Notebook" in View / Activate Command Palette). 57 | 58 | ## Check ITables' version 59 | 60 | If the above does not help, please check out the [ChangeLog](changelog.md) 61 | and decide whether you should upgrade `itables`. You can tell the version 62 | of ITables that you are using by looking at the loading message (from ITables v2.0.1 on) 63 | or by running this code snippet: 64 | 65 | ```{code-cell} ipython3 66 | import itables 67 | 68 | itables.__version__ 69 | ``` 70 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: itables 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - nodejs 6 | - python 7 | - jupyter 8 | - jupyterlab>=4 9 | - jupyter-book 10 | - jupytext 11 | - nbconvert 12 | - ipykernel 13 | - pandas 14 | - matplotlib 15 | - polars 16 | - pyarrow 17 | - pytest 18 | - pytest-xdist 19 | - pytest-cov 20 | - pre-commit 21 | - pyright 22 | - typeguard 23 | - pip 24 | - setuptools 25 | - twine 26 | - ghp-import 27 | - anywidget 28 | - dash 29 | - marimo 30 | - shiny 31 | - shinywidgets 32 | - streamlit 33 | - hatch 34 | - pip: 35 | - world_bank_data 36 | - sphinxext-rediraffe 37 | -------------------------------------------------------------------------------- /packages/dt_for_itables/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.3.2+dev 2 | 3 | - We have made sure that the ordering icons on empty headers do not reappear when ordering a column 4 | - Wrapping Javascript functions definitions within parentheses before evaluating is now done on the Javascript side. 5 | 6 | # 2.3.2 (2025-05-17) 7 | 8 | - We have fixed an issue with `selected_rows` when `filtered_row_count` was not explicitly set. 9 | 10 | # 2.3.1 (2025-05-17) 11 | 12 | - We have improved the implementation of `text_in_header_can_be_selected`. 13 | - The indirect evaluation of `JavascriptCode` and `JavascriptFunction` will not proceed when prototype pollution attempts are detected. 14 | 15 | # 2.3.0 (2025-05-11) 16 | 17 | - This version uses `datatables.net-dt=2.3.0`. The extensions have been updated, too. 18 | - We have added a new class `ITable` that processes some of the arguments on the Javascript side. 19 | - We use an indirect eval to parse the `JavascriptCode` and `JavascriptFunction` passed from Python. 20 | - The table data is now passed as JSON through `data_json`, with support for BigInts and non finite float values. 21 | 22 | # 2.2.0 (2025-03-15) 23 | 24 | - We have upgraded `datatables.net-dt==2.2.2` and `datatables.net-select-dt==3.0.0` 25 | 26 | # 2.0.13 (2024-09-22) 27 | 28 | - We have added two functions `set_selected_rows` and `get_selected_rows` to set and retrieve selected rows 29 | 30 | # 2.0.12 (2024-09-08) 31 | 32 | - We have added the datetime extension for DataTables ([#288](https://github.com/mwouts/itables/issues/288)) 33 | 34 | # 2.0.11 (2024-06-19) 35 | 36 | **Added** 37 | - The default CSS contains has `overflow:auto` on `.dt-layout-table>div`. This improves the horizontal scrolling in Jupyter, and discards the need for `scrollX` in Streamlit ([#282](https://github.com/mwouts/itables/pull/282)) 38 | 39 | 40 | # 2.0.10 (2024-06-08) 41 | 42 | **Added** 43 | - The default css for `dt-container` is `{max-width:100%;}` 44 | - We have included the `colvis` button in the package 45 | 46 | **Changed** 47 | - Updated `datatables.net-dt==2.0.8` and `datatables.net-select-dt==2.0.3` 48 | 49 | 50 | # 2.0.7 (2024-05-26) 51 | 52 | **Added** 53 | - Added a default export and typings 54 | 55 | **Changed** 56 | - Updated `datatables.net-dt==2.0.7` and `datatables.net-select-dt==2.0.2` 57 | -------------------------------------------------------------------------------- /packages/dt_for_itables/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-2025 Marc Wouts 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dt_for_itables/README.md: -------------------------------------------------------------------------------- 1 | ![ITables logo](https://raw.githubusercontent.com/mwouts/itables/3f8e8bd75af7ad38a500518fcb4fbbc370ea6c4c/itables/logo/wide.svg) 2 | 3 | This package is a ESM bundle of [DataTables](https://datatables.net/) 4 | and some of its extensions for [ITables](https://github.com/mwouts/itables/). 5 | 6 | # How to compile the bundle 7 | 8 | Run the following commands: 9 | ```bash 10 | npm install 11 | npm run build:js 12 | ``` 13 | 14 | # How to update the dependencies 15 | 16 | Run 17 | ```bash 18 | npm update --save 19 | ``` 20 | and check whether there are any outdated package with `npm outdated`. 21 | 22 | # How to publish a new version 23 | 24 | Update the dependencies, bump the version in `package.json`, and then: 25 | 26 | ```bash 27 | # Package the extension 28 | npm pack 29 | 30 | # Publish the package on npm with 31 | npm publish --access=public 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/dt_for_itables/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dt_for_itables", 3 | "version": "2.3.2", 4 | "description": "DataTables bundle for itables", 5 | "main": "src/index.js", 6 | "typings": "src/index.d.js", 7 | "files": [ 8 | "README.md", 9 | "CHANGELOG.md", 10 | "LICENSE", 11 | "dt_bundle.js", 12 | "dt_bundle.css", 13 | "package.json", 14 | "src/index.js", 15 | "src/index.css", 16 | "src/index.d.ts" 17 | ], 18 | "scripts": { 19 | "build:js": "esbuild src/index.js --format=esm --bundle --outfile=dt_bundle.js --minify", 20 | "install:js": "cp dt_bundle.js dt_bundle.css ../../src/itables/html", 21 | "build": "npm run build:js && npm run install:js" 22 | }, 23 | "author": "Marc Wouts", 24 | "license": "MIT", 25 | "dependencies": { 26 | "datatables.net-buttons": "^3.0.1", 27 | "datatables.net-buttons-dt": "^3.2.3", 28 | "datatables.net-datetime": "^1.5.5", 29 | "datatables.net-dt": "^2.3.0", 30 | "datatables.net-fixedcolumns-dt": "^5.0.4", 31 | "datatables.net-keytable-dt": "^2.12.1", 32 | "datatables.net-plugins": "^2.3.0", 33 | "datatables.net-rowgroup-dt": "^1.5.1", 34 | "datatables.net-searchbuilder-dt": "^1.8.2", 35 | "datatables.net-searchpanes-dt": "^2.3.3", 36 | "datatables.net-select-dt": "^3.0.0", 37 | "jquery": "^3.7.1", 38 | "jszip": "^3.10.1" 39 | }, 40 | "devDependencies": { 41 | "esbuild": "^0.25.3" 42 | }, 43 | "homepage": "https://mwouts.github.io/itables", 44 | "repository": { 45 | "type": "git", 46 | "url": "https://github.com/mwouts/itables.git" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/dt_for_itables/src/index.css: -------------------------------------------------------------------------------- 1 | div.dt-container { 2 | max-width: 100%; 3 | } 4 | 5 | div.dt-layout-table>div { 6 | overflow: auto; 7 | max-width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /packages/dt_for_itables/src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'dt_for_itables'; 2 | -------------------------------------------------------------------------------- /packages/itables_anywidget/README.md: -------------------------------------------------------------------------------- 1 | # itables_anywidget 2 | 3 | ## Installation 4 | 5 | ```sh 6 | pip install itables_anywidget 7 | ``` 8 | 9 | ## Development installation 10 | 11 | Create a virtual environment and and install itables_anywidget in *editable* mode with the 12 | optional development dependencies: 13 | 14 | ```sh 15 | python -m venv .venv 16 | source .venv/bin/activate 17 | pip install -e ".[dev]" 18 | ``` 19 | 20 | You then need to install the JavaScript dependencies and run the development server. 21 | 22 | ```sh 23 | npm install 24 | npm run dev 25 | ``` 26 | 27 | Open `example.ipynb` in JupyterLab, VS Code, or your favorite editor 28 | to start developing. Changes made in `js/` will be reflected 29 | in the notebook. 30 | -------------------------------------------------------------------------------- /packages/itables_anywidget/js/widget.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/packages/itables_anywidget/js/widget.css -------------------------------------------------------------------------------- /packages/itables_anywidget/js/widget.ts: -------------------------------------------------------------------------------- 1 | import type { RenderContext } from "@anywidget/types"; 2 | 3 | // DataTable and its css 4 | import { ITable, set_or_remove_dark_class } from 'dt_for_itables'; 5 | import 'dt_for_itables/dt_bundle.css'; 6 | 7 | /* Specifies attributes defined with traitlets in ../src/itables_anywidget/__init__.py */ 8 | interface WidgetModel { 9 | dt_args: object; 10 | caption: string; 11 | classes: string; 12 | style: string; 13 | } 14 | 15 | function render({ model, el }: RenderContext) { 16 | set_or_remove_dark_class(); 17 | let table = document.createElement("table"); 18 | el.classList.add("itables_anywidget"); 19 | el.appendChild(table); 20 | 21 | function update_classes() { 22 | table.className = model.get("classes"); 23 | } 24 | function update_style() { 25 | table.setAttribute('style', model.get("style")); 26 | } 27 | function update_caption() { 28 | let caption_text = model.get('caption'); 29 | if (caption_text) { 30 | let caption = table.createCaption(); 31 | caption.textContent = caption_text; 32 | } else { table.deleteCaption() }; 33 | } 34 | 35 | // Set initial values 36 | update_classes(); 37 | update_style(); 38 | update_caption(); 39 | 40 | // Update the table when one of these change 41 | model.on("change:classes", update_classes); 42 | model.on("change:style", update_style); 43 | model.on("change:caption", update_caption); 44 | 45 | // This variable is a place holder from the 46 | // DataTable instance. This way we can re-create it 47 | // from within 'create_table' 48 | let dt = null; 49 | 50 | let setting_selected_rows_from_model = false; 51 | function set_selected_rows_from_model() { 52 | // We use this variable to avoid triggering model updates! 53 | setting_selected_rows_from_model = true; 54 | dt.selected_rows = model.get('selected_rows'); 55 | setting_selected_rows_from_model = false; 56 | }; 57 | 58 | function update_table() { 59 | if (dt) { 60 | dt.destroy(); 61 | } 62 | 63 | let dt_args = model.get('_dt_args'); 64 | dt = new ITable(table, dt_args); 65 | set_selected_rows_from_model(); 66 | } 67 | update_table(); 68 | 69 | model.on('change:_dt_args', () => { 70 | update_table(); 71 | }); 72 | 73 | model.on("change:selected_rows", set_selected_rows_from_model); 74 | 75 | function export_selected_rows() { 76 | if (setting_selected_rows_from_model) 77 | return; 78 | 79 | model.set('selected_rows', dt.selected_rows); 80 | model.save_changes(); 81 | }; 82 | 83 | dt.dt.on('select', function (e: any, dt: any, type: any, indexes: any) { 84 | export_selected_rows(); 85 | }); 86 | 87 | dt.dt.on('deselect', function (e: any, dt: any, type: any, indexes: any) { 88 | export_selected_rows(); 89 | }); 90 | } 91 | 92 | export default { render }; 93 | -------------------------------------------------------------------------------- /packages/itables_anywidget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "npm run build -- --sourcemap=inline --watch", 4 | "build": "esbuild js/widget.ts --minify --format=esm --bundle --outdir=../../src/itables/widget/static", 5 | "typecheck": "tsc --noEmit" 6 | }, 7 | "dependencies": { 8 | "dt_for_itables": "file:../dt_for_itables" 9 | }, 10 | "devDependencies": { 11 | "@anywidget/types": "^0.2.0", 12 | "esbuild": "^0.25.3", 13 | "typescript": "^5.8.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/itables_anywidget/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "js" 4 | ], 5 | "compilerOptions": { 6 | "target": "ES2020", 7 | "module": "ESNext", 8 | "lib": [ 9 | "ES2020", 10 | "DOM", 11 | "DOM.Iterable" 12 | ], 13 | "skipLibCheck": true, 14 | "moduleResolution": "bundler", 15 | "allowImportingTsExtensions": true, 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "react", 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/itables_for_dash/.babelrc: -------------------------------------------------------------------------------- 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 | } 15 | -------------------------------------------------------------------------------- /packages/itables_for_dash/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itables_for_dash", 3 | "version": "0.2.0", 4 | "description": "ITables for Dash", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "build:js": "webpack --mode production", 8 | "build:backends": "dash-generate-components src/lib/components itables_for_dash -p package-info.json", 9 | "move": "mv itables_for_dash/* ../../src/itables_for_dash && rmdir itables_for_dash", 10 | "build": "npm run build:js && npm run build:backends && npm run move" 11 | }, 12 | "author": "Marc Wouts ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "dt_for_itables": "file:../dt_for_itables" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.27.1", 19 | "@babel/plugin-proposal-object-rest-spread": "^7.20.7", 20 | "@babel/preset-env": "^7.27.1", 21 | "@babel/preset-react": "^7.27.1", 22 | "@plotly/dash-component-plugins": "^1.2.3", 23 | "@plotly/webpack-dash-dynamic-import": "^1.3.0", 24 | "babel-loader": "^9.2.1", 25 | "css-loader": "^6.11.0", 26 | "prop-types": "^15.8.1", 27 | "react": "^16.14.0", 28 | "react-docgen": "^5.4.3", 29 | "style-loader": "^3.3.4", 30 | "styled-jsx": "^5.1.7", 31 | "webpack-cli": "^5.1.4" 32 | }, 33 | "engines": { 34 | "node": ">=8.11.0", 35 | "npm": ">=6.1.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/itables_for_dash/src/lib/LazyLoader.js: -------------------------------------------------------------------------------- 1 | export const ITable = React.lazy(() => import(/* webpackChunkName: "ITable" */ './fragments/ITable.react')); 2 | -------------------------------------------------------------------------------- /packages/itables_for_dash/src/lib/components/ITable.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ITable as RealComponent } from '../LazyLoader'; 4 | 5 | /** 6 | * ITable is a dash component for ITables 7 | */ 8 | const ITable = (props) => { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | }; 15 | 16 | ITable.defaultProps = {}; 17 | 18 | ITable.propTypes = { 19 | /** 20 | * The ID used to identify this component in Dash callbacks. 21 | */ 22 | id: PropTypes.string.isRequired, 23 | 24 | /** 25 | * The table caption 26 | */ 27 | caption: PropTypes.string, 28 | 29 | /** 30 | * The index of the selected rows (pass select=True to allow selection) 31 | */ 32 | selected_rows: PropTypes.array.isRequired, 33 | 34 | /** 35 | * The table style 36 | */ 37 | style: PropTypes.object.isRequired, 38 | 39 | /** 40 | * The table classes 41 | */ 42 | classes: PropTypes.string.isRequired, 43 | 44 | /** 45 | * The arguments for DataTable e.g. select, buttons, layout etc. 46 | */ 47 | dt_args: PropTypes.object.isRequired, 48 | 49 | /** 50 | * Dash-assigned callback that should be called to report property changes 51 | * to Dash, to make them available for callbacks. 52 | */ 53 | setProps: PropTypes.func 54 | }; 55 | 56 | export default ITable; 57 | export const defaultProps = ITable.defaultProps; 58 | export const propTypes = ITable.propTypes; 59 | -------------------------------------------------------------------------------- /packages/itables_for_dash/src/lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import ITable from './components/ITable.react'; 3 | 4 | export { 5 | ITable 6 | }; 7 | -------------------------------------------------------------------------------- /packages/itables_for_streamlit/README.md: -------------------------------------------------------------------------------- 1 | ![ITables logo](https://raw.githubusercontent.com/mwouts/itables/3f8e8bd75af7ad38a500518fcb4fbbc370ea6c4c/itables/logo/wide.svg) 2 | 3 | This is the ITables frontend for [Streamlit](https://streamlit.io/) 4 | 5 | It is build automatically when the ITables Python package is build, 6 | and the result of the build is moved to `src/itables/itables_for_streamlit`, 7 | cf. the `build` script. 8 | 9 | This package does not need to be published on `npm`. 10 | -------------------------------------------------------------------------------- /packages/itables_for_streamlit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "itables_for_streamlit", 3 | "version": "0.2.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/jest": "^29.5.14", 7 | "@types/node": "^20.17.32", 8 | "dt_for_itables": "file:../dt_for_itables", 9 | "react-scripts": "^5.0.1", 10 | "streamlit-component-lib": "^2.0.0", 11 | "typescript": "^4.9.5" 12 | }, 13 | "overrides": { 14 | "nth-check": "2.1.1", 15 | "postcss": ">=8.4.31" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build_only": "react-scripts build", 20 | "build": "react-scripts build && rm -rf ../../src/itables/itables_for_streamlit && mv build ../../src/itables/itables_for_streamlit", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "devDependencies": { 28 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11" 29 | }, 30 | "homepage": ".", 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/itables_for_streamlit/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ITables 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/itables_for_streamlit/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Streamlit, RenderData } from "streamlit-component-lib" 2 | 3 | import {ITable, set_or_remove_dark_class} from "dt_for_itables" 4 | import "dt_for_itables/dt_bundle.css" 5 | 6 | set_or_remove_dark_class(); 7 | // Create a table element 8 | const span = document.body.appendChild(document.createElement("span")) 9 | const table = span.appendChild(document.createElement("table")) 10 | let dt = new ITable(table, {}) 11 | 12 | function onRender(event: Event): void { 13 | // dt_args is the whole map of arguments passed on the Python side 14 | var other_args = (event as CustomEvent).detail.args.other_args; 15 | const dt_args = (event as CustomEvent).detail.args.dt_args; 16 | 17 | // As we can't pass the dt_args other than in the 18 | // DataTable constructor, we call 19 | // destroy and then re-create the table 20 | dt.destroy() 21 | 22 | // Set the class and style here otherwise the width 23 | // might become a fixed width 24 | table.setAttribute('class', other_args.classes) 25 | table.setAttribute('style', other_args.style) 26 | 27 | dt = new ITable(table, dt_args) 28 | if (other_args.caption) { 29 | dt.dt.caption(other_args.caption) 30 | } 31 | 32 | dt.selected_rows = other_args.selected_rows; 33 | 34 | function export_selected_rows() { 35 | Streamlit.setComponentValue({ selected_rows: dt.selected_rows }); 36 | }; 37 | 38 | dt.dt.on('select', function (e: any, dt: any, type: any, indexes: any) { 39 | export_selected_rows(); 40 | }); 41 | 42 | dt.dt.on('deselect', function (e: any, dt: any, type: any, indexes: any) { 43 | export_selected_rows(); 44 | }); 45 | 46 | // we recalculate the height 47 | Streamlit.setFrameHeight() 48 | } 49 | 50 | // connect the render function to Streamlit 51 | Streamlit.events.addEventListener(Streamlit.RENDER_EVENT, onRender) 52 | Streamlit.setComponentReady() 53 | -------------------------------------------------------------------------------- /packages/itables_for_streamlit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /packages/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build_all_itables_dependencies", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "build_all_itables_dependencies" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "build_all_itables_dependencies", 3 | "description": "A script to build and install the npm packages used by ITables", 4 | "scripts": { 5 | "build:dt_for_itables": "cd dt_for_itables && npm install && npm run build", 6 | "build:itables_anywidget": "cd itables_anywidget && npm install && npm run build", 7 | "build:itables_for_dash": "cd itables_for_dash && npm install && npm run build", 8 | "build:itables_for_streamlit": "cd itables_for_streamlit && npm install && npm run build", 9 | "build": "npm run build:dt_for_itables && npm run build:itables_anywidget && npm run build:itables_for_dash && npm run build:itables_for_streamlit" 10 | }, 11 | "author": "Marc Wouts" 12 | } 13 | -------------------------------------------------------------------------------- /src/itables/__init__.py: -------------------------------------------------------------------------------- 1 | from itables import downsample, options, sample_dfs 2 | 3 | from .javascript import init_notebook_mode, show, to_html_datatable 4 | from .typing import DataFrameOrSeries, ITableOptions, JavascriptCode, JavascriptFunction 5 | from .version import __version__ 6 | 7 | __all__ = [ 8 | "__version__", 9 | "to_html_datatable", 10 | "show", 11 | "init_notebook_mode", 12 | "JavascriptCode", 13 | "JavascriptFunction", 14 | "DataFrameOrSeries", 15 | "ITableOptions", 16 | "options", 17 | "downsample", 18 | "sample_dfs", 19 | ] 20 | -------------------------------------------------------------------------------- /src/itables/dash.py: -------------------------------------------------------------------------------- 1 | from itables import __version__ 2 | from itables_for_dash import ( 3 | ITABLE_PROPERTIES, 4 | ITable, 5 | ITableOutputs, 6 | updated_itable_outputs, 7 | ) 8 | 9 | __all__ = [ 10 | "ITable", 11 | "ITABLE_PROPERTIES", 12 | "ITableOutputs", 13 | "updated_itable_outputs", 14 | "__version__", 15 | ] 16 | -------------------------------------------------------------------------------- /src/itables/html/datatables_template.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 15 | -------------------------------------------------------------------------------- /src/itables/html/init_datatables.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /src/itables/interactive.py: -------------------------------------------------------------------------------- 1 | """Activate the representation of Pandas dataframes as interactive tables""" 2 | 3 | from .javascript import init_notebook_mode 4 | 5 | init_notebook_mode() 6 | -------------------------------------------------------------------------------- /src/itables/logo/loading.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 25 | 26 | 27 | 32 | 37 | 38 | 39 | 44 | 49 | 50 | 51 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/itables/logo/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/itables/logo/text.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | ITables 6 | 7 | 8 | 9 | Python DataFrames as 10 | Interactive DataTables 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/itables/logo/wide.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ITables 43 | 44 | 45 | 46 | Python DataFrames as 47 | Interactive DataTables 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/itables/logo/with_text.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ITables 42 | 43 | 44 | 45 | Python DataFrames 46 | as Interactive DataTables 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/itables/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/src/itables/py.typed -------------------------------------------------------------------------------- /src/itables/shiny.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Optional, Unpack 2 | 3 | import itables.options as opt 4 | 5 | from .javascript import ( 6 | generate_init_offline_itables_html, 7 | to_html_datatable, 8 | ) 9 | from .typing import ITableOptions 10 | from .utils import read_package_file 11 | 12 | _CONNECTED = True 13 | 14 | 15 | def init_itables( 16 | connected=False, 17 | dt_bundle=None, 18 | ): 19 | """Load the DataTables library and the corresponding css (if connected=False). 20 | 21 | Warning: make sure you keep the output of this cell when 'connected=False', 22 | otherwise the interactive tables will stop working. 23 | """ 24 | if dt_bundle is None: 25 | dt_bundle = opt.dt_bundle 26 | global _CONNECTED 27 | _CONNECTED = connected 28 | 29 | html = read_package_file("html/init_datatables.html") 30 | 31 | if not connected: 32 | html = html + "\n" + generate_init_offline_itables_html(dt_bundle) 33 | 34 | return html 35 | 36 | 37 | def DT(df, caption: Optional[str] = None, **kwargs: Unpack[ITableOptions]): 38 | """This is a version of 'to_html_datatable' that works in Shiny applications.""" 39 | kwargs["connected"] = kwargs.get("connected", _CONNECTED) 40 | html = to_html_datatable( 41 | df, 42 | caption, 43 | **kwargs, 44 | ) 45 | 46 | html = html.replace("init_notebook_mode", "init_itables") 47 | 48 | if "table_id" not in kwargs: 49 | return html 50 | 51 | script_end = "\n });\n\n" 52 | assert html.endswith(script_end) 53 | assert "let dt = new ITable" in html 54 | 55 | selected_rows_code = f""" 56 | function set_selected_rows_in_shiny(...args) {{ 57 | Shiny.setInputValue('{kwargs["table_id"]}_selected_rows', dt.selected_rows); 58 | }}; 59 | 60 | set_selected_rows_in_shiny(); 61 | dt.on('select', set_selected_rows_in_shiny); 62 | dt.on('deselect', set_selected_rows_in_shiny);""" 63 | 64 | return html.removesuffix(script_end) + selected_rows_code + script_end 65 | -------------------------------------------------------------------------------- /src/itables/streamlit.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import streamlit.components.v1 as components 4 | from typing_extensions import Unpack 5 | 6 | from .javascript import get_itables_extension_arguments 7 | from .typing import DataFrameOrSeries, ITableOptions 8 | from .utils import find_package_file 9 | 10 | _streamlit_component_func = components.declare_component( 11 | "itables_for_streamlit", path=find_package_file("itables_for_streamlit") 12 | ) 13 | 14 | 15 | def interactive_table( 16 | df: Optional[DataFrameOrSeries], 17 | key: Optional[str] = None, 18 | caption: Optional[str] = None, 19 | **kwargs: Unpack[ITableOptions], 20 | ): 21 | """Render the DataFrame as an interactive datatable in Streamlit applications""" 22 | dt_args, other_args = get_itables_extension_arguments(df, caption, **kwargs) 23 | return _streamlit_component_func(key=key, dt_args=dt_args, other_args=other_args) 24 | -------------------------------------------------------------------------------- /src/itables/utils.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | from pathlib import Path 3 | 4 | UNPKG_DT_BUNDLE_URL = "https://www.unpkg.com/dt_for_itables@2.3.2/dt_bundle.js" 5 | UNPKG_DT_BUNDLE_CSS = UNPKG_DT_BUNDLE_URL.replace(".js", ".css") 6 | UNPKG_DT_BUNDLE_URL_NO_VERSION = "https://www.unpkg.com/dt_for_itables/dt_bundle.js" 7 | UNPKG_DT_BUNDLE_CSS_NO_VERSION = "https://www.unpkg.com/dt_for_itables/dt_bundle.css" 8 | 9 | 10 | def find_package_file(*path): 11 | """Return the full path to a file from the itables package""" 12 | current_path = Path(__file__).parent 13 | return Path(current_path, *path) 14 | 15 | 16 | def read_package_file(*path): 17 | """Return the content of a file from the itables package""" 18 | with open(find_package_file(*path), encoding="utf-8") as fp: 19 | return fp.read() 20 | -------------------------------------------------------------------------------- /src/itables/version.py: -------------------------------------------------------------------------------- 1 | """ITables' version number""" 2 | 3 | __version__ = "2.4.1-dev" 4 | -------------------------------------------------------------------------------- /src/itables_for_dash/ITable.py: -------------------------------------------------------------------------------- 1 | # AUTO GENERATED FILE - DO NOT EDIT 2 | 3 | import typing # noqa: F401 4 | import numbers # noqa: F401 5 | from typing_extensions import TypedDict, NotRequired, Literal # noqa: F401 6 | from dash.development.base_component import Component, _explicitize_args 7 | try: 8 | from dash.development.base_component import ComponentType # noqa: F401 9 | except ImportError: 10 | ComponentType = typing.TypeVar("ComponentType", bound=Component) 11 | 12 | 13 | class ITable(Component): 14 | """An ITable component. 15 | ITable is a dash component for ITables 16 | 17 | Keyword arguments: 18 | 19 | - id (string; required): 20 | The ID used to identify this component in Dash callbacks. 21 | 22 | - caption (string; optional): 23 | The table caption. 24 | 25 | - classes (string; required): 26 | The table classes. 27 | 28 | - dt_args (dict; required): 29 | The arguments for DataTable e.g. select, buttons, layout etc. 30 | 31 | - selected_rows (list; required): 32 | The index of the selected rows (pass select=True to allow 33 | selection).""" 34 | _children_props = [] 35 | _base_nodes = ['children'] 36 | _namespace = 'itables_for_dash' 37 | _type = 'ITable' 38 | 39 | @_explicitize_args 40 | def __init__( 41 | self, 42 | id: typing.Optional[typing.Union[str, dict]] = None, 43 | caption: typing.Optional[str] = None, 44 | selected_rows: typing.Optional[typing.Sequence] = None, 45 | style: typing.Optional[typing.Any] = None, 46 | classes: typing.Optional[str] = None, 47 | dt_args: typing.Optional[dict] = None, 48 | **kwargs 49 | ): 50 | self._prop_names = ['id', 'caption', 'classes', 'dt_args', 'selected_rows', 'style'] 51 | self._valid_wildcard_attributes = [] 52 | self.available_properties = ['id', 'caption', 'classes', 'dt_args', 'selected_rows', 'style'] 53 | self.available_wildcard_properties = [] 54 | _explicit_args = kwargs.pop('_explicit_args') 55 | _locals = locals() 56 | _locals.update(kwargs) # For wildcard attrs and excess named props 57 | args = {k: _locals[k] for k in _explicit_args} 58 | 59 | for k in ['id', 'classes', 'dt_args', 'selected_rows', 'style']: 60 | if k not in args: 61 | raise TypeError( 62 | 'Required argument `' + k + '` was not specified.') 63 | 64 | super(ITable, self).__init__(**args) 65 | -------------------------------------------------------------------------------- /src/itables_for_dash/__init__.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import Optional, Unpack 2 | 3 | from itables import __version__ 4 | from itables.typing import ITableOptions 5 | 6 | from .ITable import ITable as ITableComponent 7 | from .properties import ( 8 | ITABLE_PROPERTIES, 9 | ITableOutputs, 10 | get_itable_component_kwargs, 11 | updated_itable_outputs, 12 | ) 13 | 14 | _js_dist = [ 15 | {"namespace": "itables_for_dash", "relative_package_path": name, **kwargs} 16 | for name, kwargs in { 17 | "async-ITable.js": {"async": True}, 18 | "async-ITable.js.map": {"dynamic": True}, 19 | "itables_for_dash.min.js": {}, 20 | "itables_for_dash.min.js.map": {"dynamic": True}, 21 | }.items() 22 | ] 23 | 24 | _css_dist = [] 25 | 26 | 27 | ITableComponent._js_dist = _js_dist # type: ignore 28 | ITableComponent._css_dist = _css_dist # type: ignore 29 | 30 | 31 | class ITable(ITableComponent): 32 | """An ITable component for Dash""" 33 | 34 | def __init__( 35 | self, 36 | id, 37 | df=None, 38 | caption: Optional[str] = None, 39 | **kwargs: Unpack[ITableOptions], 40 | ): 41 | """ 42 | Initialize the ITable component. 43 | 44 | Parameters 45 | ---------- 46 | id : str 47 | The ID of the component. 48 | **kwargs : dict 49 | Additional keyword arguments for the component. 50 | """ 51 | if not isinstance(id, str): 52 | raise ValueError("The id must be a string.") 53 | if not id: 54 | raise ValueError("The id cannot be an empty string.") 55 | 56 | return super().__init__( 57 | id=id, **get_itable_component_kwargs(df, caption, **kwargs) 58 | ) 59 | 60 | 61 | __all__ = [ 62 | "ITable", 63 | "ITABLE_PROPERTIES", 64 | "ITableOutputs", 65 | "updated_itable_outputs", 66 | "__version__", 67 | ] 68 | -------------------------------------------------------------------------------- /src/itables_for_dash/properties.py: -------------------------------------------------------------------------------- 1 | try: 2 | from dash import Output, no_update # type: ignore 3 | except ImportError as e: 4 | import_error = e 5 | 6 | def Output(*args, **kwargs): 7 | raise import_error 8 | 9 | def no_update(*args, **kwargs): 10 | raise import_error 11 | 12 | 13 | from typing import Optional 14 | 15 | from typing_extensions import Unpack 16 | 17 | from itables.javascript import ( 18 | get_expanded_style, 19 | get_itables_extension_arguments, 20 | ) 21 | from itables.typing import DataFrameOrSeries, DataTableOptions, ITableOptions 22 | 23 | ITABLE_PROPERTIES = ( 24 | "caption", 25 | "style", 26 | "classes", 27 | "dt_args", 28 | "selected_rows", 29 | ) 30 | 31 | 32 | def get_itable_component_kwargs( 33 | df: Optional[DataFrameOrSeries] = None, 34 | caption: Optional[str] = None, 35 | **kwargs: Unpack[ITableOptions], 36 | ): 37 | dt_args, other_args = get_itables_extension_arguments(df, caption, **kwargs) 38 | 39 | style = get_expanded_style(other_args.pop("style")) 40 | for key in style: 41 | # transform caption-side to captionSide 42 | words = key.split("-") 43 | if len(words) > 1: 44 | new_key = words[0] + "".join(word.capitalize() for word in words[1:]) 45 | style[new_key] = style.pop(key) 46 | 47 | return { 48 | "dt_args": dt_args, 49 | "style": style, 50 | **other_args, 51 | } 52 | 53 | 54 | def ITableOutputs(id): 55 | return [Output(id, key) for key in ITABLE_PROPERTIES] 56 | 57 | 58 | def updated_itable_outputs( 59 | df: Optional[DataFrameOrSeries] = None, 60 | caption: Optional[str] = None, 61 | current_dt_args: Optional[DataTableOptions] = None, 62 | **kwargs: Unpack[ITableOptions], 63 | ): 64 | updated_properties = get_itable_component_kwargs(df, caption, **kwargs) 65 | 66 | if current_dt_args is not None: 67 | if df is None: 68 | for k in { 69 | "columns", 70 | "data_json", 71 | "filtered_row_count", 72 | "downsampling_warning", 73 | }: 74 | if k in current_dt_args: 75 | updated_properties["dt_args"][k] = current_dt_args[k] 76 | 77 | if current_dt_args == updated_properties["dt_args"]: 78 | updated_properties["dt_args"] = no_update 79 | 80 | ordered_list_of_updated_properties = [ 81 | updated_properties.pop(k) for k in ITABLE_PROPERTIES 82 | ] 83 | 84 | if updated_properties: 85 | raise ValueError(f"Unexpected properties: {updated_properties}") 86 | 87 | return ordered_list_of_updated_properties 88 | -------------------------------------------------------------------------------- /src/itables_for_dash/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/src/itables_for_dash/py.typed -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwouts/itables/8fa250a684a6433314d2a984ce375bc9d8b07429/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | from itables.sample_dfs import PANDAS_VERSION_MAJOR, get_dict_of_test_dfs 5 | 6 | 7 | @pytest.fixture(params=list(get_dict_of_test_dfs())) 8 | def df(request): 9 | name = request.param 10 | df = get_dict_of_test_dfs()[name] 11 | assert isinstance(df, pd.DataFrame) 12 | return df 13 | 14 | 15 | @pytest.fixture(params=["None", "1D-array", "2D-array"]) 16 | def lengthMenu(request): 17 | if request.param == "1D-array": 18 | return [2, 5, 10, 20, 50] 19 | if request.param == "2D-array": 20 | return [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]] 21 | return None 22 | 23 | 24 | @pytest.fixture(params=[False, True]) 25 | def connected(request): 26 | return request.param 27 | 28 | 29 | @pytest.fixture(params=[False, True] if PANDAS_VERSION_MAJOR >= 1 else [False]) 30 | def use_to_html(request): 31 | return request.param 32 | -------------------------------------------------------------------------------- /tests/test_changelog.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | 7 | def replace_issue_number_with_links(text): 8 | return re.sub( 9 | r"([^\[])#([0-9]+)", 10 | r"\1[#\2](https://github.com/mwouts/itables/issues/\2)", 11 | text, 12 | ) 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "input,output", 17 | [ 18 | ( 19 | "Issue #535", 20 | "Issue [#535](https://github.com/mwouts/itables/issues/535)", 21 | ), 22 | ( 23 | "Multiline\ntext (#123)", 24 | "Multiline\ntext ([#123](https://github.com/mwouts/itables/issues/123))", 25 | ), 26 | ], 27 | ) 28 | def test_replace_issue_numbers_with_links(input, output): 29 | assert replace_issue_number_with_links(input) == output 30 | 31 | 32 | def test_update_changelog(): 33 | changelog_file = Path(__file__).parent.parent / "docs" / "changelog.md" 34 | cur_text = changelog_file.read_text() 35 | new_text = replace_issue_number_with_links(cur_text) 36 | if cur_text != new_text: 37 | changelog_file.write_text(new_text) # pragma: no cover 38 | -------------------------------------------------------------------------------- /tests/test_connected_notebook_is_small.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from jupytext.cli import jupytext 3 | 4 | 5 | def text_notebook(connected, display_logo_when_loading=True): 6 | return f"""# %% 7 | import itables 8 | 9 | itables.options.display_logo_when_loading = {display_logo_when_loading} 10 | itables.init_notebook_mode(connected={connected}) 11 | 12 | # %% 13 | import pandas as pd 14 | pd.DataFrame() 15 | """ 16 | 17 | 18 | @pytest.mark.parametrize("display_logo_when_loading", [True, False]) 19 | def test_connected_notebook_is_small(tmp_path, display_logo_when_loading): 20 | nb_py = tmp_path / "nb.py" 21 | nb_ipynb = tmp_path / "nb.ipynb" 22 | nb_py.write_text( 23 | text_notebook( 24 | connected=True, display_logo_when_loading=display_logo_when_loading 25 | ) 26 | ) 27 | jupytext([str(nb_py), "--to", "ipynb", "--set-kernel", "itables", "--execute"]) 28 | assert nb_ipynb.exists() 29 | if nb_ipynb.stat().st_size < (9000 if display_logo_when_loading else 5000): 30 | return 31 | raise AssertionError( 32 | f"Notebook size is too large: {nb_ipynb.stat().st_size} bytes:\n" 33 | f"{nb_ipynb.read_text()}" 34 | ) 35 | 36 | 37 | def test_offline_notebook_is_not_too_large(tmp_path): 38 | nb_py = tmp_path / "nb.py" 39 | nb_ipynb = tmp_path / "nb.ipynb" 40 | nb_py.write_text(text_notebook(connected=False)) 41 | jupytext([str(nb_py), "--to", "ipynb", "--set-kernel", "itables", "--execute"]) 42 | assert nb_ipynb.exists() 43 | assert 750000 < nb_ipynb.stat().st_size < 850000 44 | -------------------------------------------------------------------------------- /tests/test_documentation_notebooks_run.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import jupytext 4 | import pytest 5 | 6 | import itables.options as opt 7 | from itables import init_notebook_mode 8 | from itables.javascript import pd_style 9 | 10 | try: 11 | import polars as pl 12 | except ImportError: 13 | pl = None 14 | 15 | 16 | def list_doc_notebooks(): 17 | documentation_folder = Path(__file__).parent / ".." / "docs" 18 | for path in documentation_folder.iterdir(): 19 | if path.suffix == ".md": 20 | yield path 21 | if path.is_dir(): 22 | if path.name == ".ipynb_checkpoints": 23 | continue 24 | for file in path.iterdir(): 25 | if file.suffix == ".md": 26 | yield file 27 | 28 | 29 | @pytest.mark.parametrize( 30 | "notebook", list_doc_notebooks(), ids=lambda notebook: notebook.stem 31 | ) 32 | def test_run_documentation_notebooks(notebook): 33 | if "polars" in notebook.stem and pl is None: 34 | pytest.skip("Polars is not available") 35 | if "pandas_style" in notebook.stem and pd_style is None: 36 | pytest.skip("Pandas Style is not available") 37 | if "shiny" in notebook.stem: 38 | pytest.skip("shinywidgets makes the widget.md notebook fail") 39 | if "marimo" in notebook.stem or "widget" in notebook.stem: 40 | pytest.importorskip("anywidget") 41 | 42 | org_options = dir(opt) 43 | 44 | nb = jupytext.read(notebook) 45 | py_notebook = jupytext.writes(nb, "py:percent") 46 | exec(py_notebook, {}) 47 | 48 | new_options = set(dir(opt)).difference(org_options) 49 | for name in new_options: 50 | delattr(opt, name) 51 | 52 | # Revert back to the non initialized mode 53 | init_notebook_mode(all_interactive=False, connected=True) 54 | -------------------------------------------------------------------------------- /tests/test_extension_arguments.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itables.javascript import get_itables_extension_arguments 4 | 5 | 6 | def test_get_itables_extension_arguments(df): 7 | try: 8 | dt_args, other_args = get_itables_extension_arguments(df) 9 | except NotImplementedError as e: 10 | pytest.skip(str(e)) 11 | 12 | assert set(dt_args) <= { 13 | "table_html", 14 | "data_json", 15 | "column_filters", 16 | "layout", 17 | "order", 18 | "text_in_header_can_be_selected", 19 | "filtered_row_count", 20 | "downsampling_warning", 21 | }, set(dt_args) 22 | assert isinstance(dt_args["data_json"], str) # type: ignore 23 | assert isinstance(dt_args["table_html"], str) # type: ignore 24 | 25 | assert set(other_args) <= { 26 | "classes", 27 | "style", 28 | "caption", 29 | "selected_rows", 30 | }, set(dt_args) 31 | assert isinstance(other_args["classes"], str) 32 | assert isinstance(other_args["style"], str) 33 | assert other_args["caption"] is None 34 | -------------------------------------------------------------------------------- /tests/test_html_in_table_header.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | from itables import to_html_datatable 5 | 6 | 7 | @pytest.mark.parametrize("allow_html", [True, False]) 8 | def test_html_in_table_header(allow_html, df=pd.DataFrame({"B": [1]})): 9 | html = to_html_datatable(df, allow_html=allow_html) 10 | print(html) 11 | assert ("B" in html) == allow_html 12 | -------------------------------------------------------------------------------- /tests/test_init.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | from itables import init_notebook_mode 4 | 5 | 6 | def test_init(): 7 | assert not hasattr(pd.Series, "_repr_html_") 8 | 9 | init_notebook_mode(all_interactive=True) 10 | assert hasattr(pd.Series, "_repr_html_") 11 | 12 | init_notebook_mode(all_interactive=False) 13 | assert not hasattr(pd.Series, "_repr_html_") 14 | 15 | # No pb if we do this twice 16 | init_notebook_mode(all_interactive=False) 17 | assert not hasattr(pd.Series, "_repr_html_") 18 | -------------------------------------------------------------------------------- /tests/test_itables_for_dash.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import pytest 5 | 6 | try: 7 | from itables.dash import ITable 8 | except ImportError as e: 9 | pytest.skip(str(e), allow_module_level=True) 10 | 11 | 12 | def check_ressource(relative_package_path, namespace, **kwargs): 13 | ressource_file = sys.modules[namespace].__file__ 14 | assert isinstance(ressource_file, str), ressource_file 15 | module_path = os.path.join(os.path.dirname(ressource_file), relative_package_path) 16 | assert os.path.exists(module_path), module_path 17 | 18 | 19 | def test_itable_component_ressources(): 20 | for js in ITable._js_dist: # type: ignore 21 | check_ressource(**js) 22 | for css in ITable._css_dist: # type: ignore 23 | check_ressource(**css) 24 | 25 | 26 | def test_create_empty_table(): 27 | ITable(id="test") 28 | -------------------------------------------------------------------------------- /tests/test_keys_to_be_evaluated.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itables.javascript import JavascriptFunction, get_keys_to_be_evaluated 4 | 5 | 6 | @pytest.fixture() 7 | def coloredColumnDefs(): 8 | return [ 9 | { 10 | "targets": "_all", 11 | "createdCell": JavascriptFunction( 12 | "function (td, cellData, rowData, row, col) {if (cellData<0) {$(td).css('color', 'red')}}" 13 | ), 14 | } 15 | ] 16 | 17 | 18 | def test_get_keys_to_be_evaluated(coloredColumnDefs): 19 | keys_to_be_evaluated = get_keys_to_be_evaluated({"columnDefs": coloredColumnDefs}) 20 | 21 | assert keys_to_be_evaluated == [["columnDefs", 0, "createdCell"]] 22 | -------------------------------------------------------------------------------- /tests/test_marimo.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | MARIMO_APPS_PATH = Path(__file__).parent / ".." / "apps" / "marimo" 7 | MARIMO_APPS_PY_FILES = [ 8 | file for file in MARIMO_APPS_PATH.iterdir() if file.suffix == ".py" 9 | ] 10 | 11 | pytest.importorskip("marimo") 12 | 13 | 14 | @pytest.fixture(params=MARIMO_APPS_PY_FILES) 15 | def marimo_app(request) -> str: 16 | """Return the name of an example Marimo app""" 17 | return request.param.stem 18 | 19 | 20 | def test_marimo_apps_exist(): 21 | assert ( 22 | len(MARIMO_APPS_PY_FILES) > 0 23 | ), "No Marimo apps found in the apps/marimo directory." 24 | 25 | 26 | def test_marimo_apps_are_valid_python_scripts(marimo_app: str): 27 | """Test that the Marimo apps are valid Python scripts.""" 28 | file_path = MARIMO_APPS_PATH / f"{marimo_app}.py" 29 | spec = importlib.util.spec_from_file_location(marimo_app, file_path) 30 | assert spec is not None, f"Could not find spec for {marimo_app}" 31 | app = importlib.util.module_from_spec(spec) 32 | spec.loader.exec_module(app) # type: ignore 33 | assert app is not None 34 | -------------------------------------------------------------------------------- /tests/test_pandas_style.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pandas as pd 4 | import pytest 5 | 6 | from itables import to_html_datatable 7 | from itables.javascript import get_itable_arguments 8 | 9 | pytest.importorskip("jinja2") 10 | 11 | 12 | @pytest.mark.skipif( 13 | pd.__version__.startswith("0."), 14 | reason="AttributeError: 'Styler' object has no attribute 'to_html'", 15 | ) 16 | def test_buttons_are_shown_on_pd_style_objects(): 17 | df = pd.DataFrame({"A": ["a"]}).style 18 | html = to_html_datatable( 19 | df, 20 | buttons=["pageLength", "copyHtml5", "csvHtml5", "excelHtml5"], 21 | allow_html=True, 22 | ) 23 | 24 | # Extract the dt_args passed to datatables 25 | dt_args = "" 26 | for line in html.splitlines(): 27 | line = line.strip() 28 | if line.startswith("let dt_args"): 29 | dt_args = line.split("=", 1)[1] 30 | break 31 | 32 | assert dt_args.endswith(";"), dt_args 33 | dt_args = dt_args[:-1] 34 | dt_args = json.loads(dt_args) 35 | 36 | print(dt_args) 37 | assert "dom" not in dt_args 38 | assert "buttons" in dt_args 39 | assert "buttons" in dt_args["layout"].values() 40 | 41 | 42 | def test_non_trivial_index_of_styler_objects_are_included(): 43 | """ 44 | When a Pandas Styler index is non trivial, it should appear 45 | in the ITable output, see issue #393 46 | """ 47 | df = pd.DataFrame({"A": [1]}, index=pd.Index([0], name="index")) 48 | dt_args = get_itable_arguments(df.style, table_id="T_id", allow_html=True) 49 | assert "table_html" in dt_args 50 | table_html = dt_args["table_html"] 51 | assert "index" in table_html, table_html 52 | 53 | 54 | @pytest.mark.parametrize("showIndex", [True, "auto"]) 55 | def test_trivial_indexes_of_styler_objects_are_not_included(showIndex): 56 | """ 57 | When a Pandas Styler index is trivial, it should appear 58 | in the ITable output only if showIndex is True 59 | """ 60 | df = pd.DataFrame({"A": [1]}) 61 | dt_args = get_itable_arguments( 62 | df.style, table_id="T_id", allow_html=True, showIndex=showIndex 63 | ) 64 | assert "table_html" in dt_args 65 | table_html = dt_args["table_html"] 66 | assert ('class="blank level0"' in table_html) == (showIndex is True), table_html 67 | -------------------------------------------------------------------------------- /tests/test_polars.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from itables import to_html_datatable 4 | from itables.javascript import datatables_rows 5 | from itables.sample_dfs import ( 6 | get_dict_of_polars_test_dfs, 7 | get_dict_of_polars_test_series, 8 | ) 9 | 10 | try: 11 | import polars as pl # noqa 12 | except ImportError as e: 13 | pytest.skip(str(e), allow_module_level=True) 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "name,x", [(name, x) for name, x in get_dict_of_polars_test_series().items()] 18 | ) 19 | def test_show_polars_series(name, x): 20 | to_html_datatable(x) 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "name,df", [(name, df) for name, df in get_dict_of_polars_test_dfs().items()] 25 | ) 26 | def test_show_polars_df(name, df): 27 | to_html_datatable(df) 28 | 29 | 30 | def test_encode_mixed_contents(): 31 | # Make sure that the bigint escape works for mixed content # 291 32 | df = pl.DataFrame( 33 | { 34 | "bigint": [1666767918216000000], 35 | "int": [1699300000000], 36 | "float": [0.9510565400123596], 37 | "neg": [-0.30901700258255005], 38 | } 39 | ) 40 | assert ( 41 | datatables_rows(df) 42 | == "[[1666767918216000000, 1699300000000, 0.9510565400123596, -0.30901700258255005]]" 43 | ) 44 | 45 | 46 | def test_render_polars_struct(): 47 | df = pl.DataFrame( 48 | { 49 | "X": ["A", "A", "B", "C", "C", "C"], 50 | } 51 | ) 52 | assert ( 53 | datatables_rows(df.select(pl.col("X").value_counts(sort=True))) 54 | == '[["{\\"C\\",3}"], ["{\\"A\\",2}"], ["{\\"B\\",1}"]]' 55 | ) 56 | -------------------------------------------------------------------------------- /tests/test_shiny.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import warnings 3 | from pathlib import Path 4 | 5 | import pandas as pd 6 | import pytest 7 | 8 | from itables.downsample import downsample 9 | from itables.shiny import DT 10 | 11 | SHINY_APPS_PATH = Path(__file__).parent / ".." / "apps" / "shiny" 12 | SHINY_APPS_PY_FILES = [ 13 | f"{directory.name}/{file.stem}" 14 | for directory in SHINY_APPS_PATH.iterdir() 15 | if directory.is_dir() 16 | for file in directory.iterdir() 17 | if file.suffix == ".py" 18 | ] 19 | 20 | 21 | def test_select_on_downsampled_df(): 22 | """ 23 | When a DF of 17 rows is downsampled to 3 rows, 24 | we can only select rows 0, 1, 16 25 | """ 26 | df = pd.DataFrame({"x": range(17)}) 27 | dn, _ = downsample(df, max_rows=3) 28 | assert len(dn) == 3 29 | 30 | with warnings.catch_warnings(): 31 | warnings.simplefilter("error") 32 | DT(df, maxRows=3, selected_rows=[0, 1, 16]) 33 | 34 | for row in [-1, 17]: 35 | with pytest.raises(IndexError, match="Selected rows out of range"): 36 | DT(df, maxRows=3, selected_rows=[row]) 37 | 38 | for row in [2, 15]: 39 | with pytest.warns(match="no row with index between 2 and 15 can be selected"): 40 | DT(df, maxRows=3, selected_rows=[row]) 41 | 42 | 43 | pytest.importorskip("shiny") 44 | 45 | 46 | @pytest.fixture(params=SHINY_APPS_PY_FILES) 47 | def shiny_app(request) -> str: 48 | """Return the name of an example Shiny app""" 49 | return request.param 50 | 51 | 52 | def test_shiny_apps_exist(): 53 | assert ( 54 | len(SHINY_APPS_PY_FILES) > 0 55 | ), "No Shiny apps found in the apps/shiny directory." 56 | 57 | 58 | def test_shiny_apps_are_valid_python_scripts( 59 | shiny_app: str, 60 | ignore_errors=[ 61 | "RuntimeError: express.ui.page_opts() can only " 62 | "be used inside of a standalone Shiny Express app" 63 | ], 64 | ): 65 | """Test that the Shiny apps are valid Python scripts. 66 | 67 | Note that we don't use importlib here, as importing 68 | shinywidgets causes an interaction with the widget tests 69 | """ 70 | file_path = SHINY_APPS_PATH / f"{shiny_app}.py" 71 | result = subprocess.run( 72 | ["python", str(file_path)], capture_output=True, text=True, timeout=10 73 | ) 74 | for error in ignore_errors: 75 | if error in result.stderr: 76 | pytest.xfail(error) 77 | assert result.returncode == 0, f"Process failed: {result.stderr}" 78 | -------------------------------------------------------------------------------- /tests/test_streamlit.py: -------------------------------------------------------------------------------- 1 | import importlib.util 2 | from pathlib import Path 3 | 4 | import pytest 5 | 6 | STREAMLIT_APPS_PATH = Path(__file__).parent / ".." / "apps" / "streamlit" 7 | STREAMLIT_APPS_PY_FILES = [ 8 | file for file in STREAMLIT_APPS_PATH.iterdir() if file.suffix == ".py" 9 | ] 10 | 11 | pytest.importorskip("streamlit") 12 | 13 | 14 | @pytest.fixture(params=STREAMLIT_APPS_PY_FILES) 15 | def streamlit_app(request) -> str: 16 | """Return the name of an example Streamlit app""" 17 | return request.param.stem 18 | 19 | 20 | def test_streamlit_apps_exist(): 21 | assert ( 22 | len(STREAMLIT_APPS_PY_FILES) > 0 23 | ), "No Streamlit apps found in the apps/streamlit directory." 24 | 25 | 26 | def test_streamlit_apps_can_be_imported(streamlit_app: str): 27 | """Test that the Streamlit apps can be imported successfully.""" 28 | file_path = STREAMLIT_APPS_PATH / f"{streamlit_app}.py" 29 | spec = importlib.util.spec_from_file_location(streamlit_app, file_path) 30 | assert spec is not None, f"Could not find spec for {streamlit_app}" 31 | app = importlib.util.module_from_spec(spec) 32 | spec.loader.exec_module(app) # type: ignore 33 | assert app is not None 34 | -------------------------------------------------------------------------------- /tests/test_typing.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pandas as pd 4 | import pytest 5 | 6 | import itables 7 | 8 | if not itables.typing.is_typeguard_available(): 9 | pytestmark = pytest.mark.skip(reason="Typeguard is not available") 10 | 11 | 12 | @pytest.fixture 13 | def df(): 14 | return pd.DataFrame( 15 | { 16 | "a": [1, 2], 17 | } 18 | ) 19 | 20 | 21 | def test_warns_on_incorrect_option(df): 22 | with pytest.warns( 23 | SyntaxWarning, 24 | match="These arguments are not documented in ITableOptions: .*'lengthMenuWithTypo'", 25 | ): 26 | itables.to_html_datatable(df, lengthMenuWithTypo=[2, 5, 10]) # type: ignore 27 | 28 | 29 | def test_warns_on_incorrect_type(df): 30 | with pytest.warns(SyntaxWarning, match=re.escape("does not match")): 31 | itables.to_html_datatable(df, lengthMenu=[2.2]) # type: ignore 32 | -------------------------------------------------------------------------------- /tests/test_update_samples.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import world_bank_data as wb 5 | 6 | pytestmark = pytest.mark.xfail( 7 | reason="HTTPError: 502 Server Error: Bad Gateway for url: http://api.worldbank.org/v2..." 8 | ) 9 | 10 | SAMPLE_DIR = Path(__file__).parent / ".." / "src" / "itables" / "samples" 11 | 12 | 13 | def create_csv_file_if_missing(df, csv_file): 14 | if not csv_file.exists(): 15 | with open(str(csv_file), "w") as fp: # pragma: no cover 16 | fp.write(df.to_csv()) 17 | 18 | 19 | def test_update_countries(csv_file=SAMPLE_DIR / "countries.csv"): 20 | df = wb.get_countries() 21 | create_csv_file_if_missing(df, csv_file) 22 | 23 | 24 | def test_update_population(csv_file=SAMPLE_DIR / "population.csv"): 25 | x = wb.get_series("SP.POP.TOTL", mrv=1, simplify_index=True) 26 | create_csv_file_if_missing(x, csv_file) 27 | 28 | 29 | def test_update_indicators(csv_file=SAMPLE_DIR / "indicators.csv"): 30 | df = wb.get_indicators().sort_index().head(500) 31 | create_csv_file_if_missing(df, csv_file) 32 | -------------------------------------------------------------------------------- /tests/test_widget.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import itables.options as opt 4 | 5 | pytest.importorskip("anywidget") 6 | 7 | 8 | def test_create_widget_with_no_df(): 9 | from itables.widget import ITable 10 | 11 | itable = ITable() 12 | assert itable._df is None 13 | assert itable.caption == "" 14 | assert itable.classes == opt.classes 15 | assert itable.style == opt.style 16 | assert itable.selected_rows == [] 17 | assert itable._dt_args == { 18 | "text_in_header_can_be_selected": True, 19 | "order": [], 20 | "layout": {k: None for k in opt.layout}, 21 | } 22 | 23 | 24 | def test_create_widget_with_df(df): 25 | from itables.widget import ITable 26 | 27 | itable = ITable(df) 28 | assert itable.df is df 29 | assert itable.caption == "" 30 | assert itable.classes == opt.classes 31 | assert itable.style == opt.style 32 | assert itable.selected_rows == [] 33 | selected_dt_args = { 34 | k: v 35 | for k, v in itable._dt_args.items() 36 | if k 37 | not in [ 38 | "data_json", 39 | "table_html", 40 | "filtered_row_count", 41 | "downsampling_warning", 42 | "layout", 43 | ] 44 | } 45 | assert selected_dt_args == { 46 | "text_in_header_can_be_selected": True, 47 | "order": [], 48 | } 49 | --------------------------------------------------------------------------------