Display tables in Dash

I have started working on an official (editable) Table component here: https://github.com/plotly/dash-table-experiments. It will eventually get merged into dash-core-components when it becomes stable.

4 Likes

Thanks @chriddyp for the great work.
But what is your opinion about avoiding fetching all data into memory at startup.
You should create a fake big table into a database to see how it scale.


is for example able of displaying a chunk of a database table without fetching content of the whole table to memory (it’s using Blaze to achieve this)

This sounds like a problem that the dash developer should solve rather than the Table component should solve. The Dash developer can solve this by making queries that limit the number of results and/or building a UI that queries more data if necessary. The Table component itself won’t fetch or query data in the python context. The Table component could help out the user in building this UI by providing attributes for pagination, which could be Input elements in a callback that would query more data.

I will likely make the table performant so that passing in a huge dataframe “just works”. However, rendering a table with more than a thousand rows isn’t a useful experience without search, filtering, or ordering capabilities.

1 Like

Made some more progress on a supercharged table component tonight. It is now sortable and filterable. See for more: https://github.com/plotly/dash-table-experiments

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
import json
import pandas as pd
import numpy as np

app = dash.Dash()

app.scripts.config.serve_locally=True

DF_WALMART = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/1962_2006_walmart_store_openings.csv')

app.layout = html.Div([
    html.H4('DataTable'),
    dt.DataTable(
        dataframe=DF_WALMART.to_dict('split'),
        filterable=True,
        sortable=True
    )
])

if __name__ == '__main__':
    app.run_server(debug=True)

4 Likes

Hi, could you also share how you declared the dropdown? I need to create a dropdown that, based on selection, populates my pandas table.

Could you create a new thread for this question? I also recommend reading through the user guide at Part 1. Layout | Dash for Python Documentation | Plotly and Part 2. Basic Callbacks | Dash for Python Documentation | Plotly.

Hi Chris,

So cool to interact with you.

I will start a new thread.

Been playing with the dash-table-experiment as it does some of what I need.

Thanks, Jorge

Latest version of dash-table-experiments includes sorting and editing and allows you to listen to those changes through callbacks:

It’s getting there!

3 Likes

Very cool. Nice one @chriddyp! Can see a load of uses for this. Three requests for the future which I’ll try help with: 1) Ability to remove a column from the table figure 2) Lay a background image for the table 3) export the table as a png

Hi Chris,

This table-viewer is looking really powerful. I’d like to try it out but I get the following console error in my browser when I attempt your example (usage.py). It looks to me like an installation issue, but I’m not a javascript expert: The python package does seem to load in an interpreter. Is there some development environment I need to install, or will the latest dash versions work? (https://plot.ly/dash/installation).

Uncaught (in promise) Error: dash_table_experiments was not found.
    at Object.resolve (bundle.js:10)
    at i (bundle.js:10)
    at Array.map (<anonymous>)
    at i (bundle.js:10)
    at Array.map (<anonymous>)
    at i (bundle.js:10)
    at Array.map (<anonymous>)
    at i (bundle.js:10)
    at Array.map (<anonymous>)
    at i (bundle.js:10)

Hi @sagardamle

Typically when using dash component repos you need to run npm i to install the component before running python usage.py.

I just cloned the repo and ran into an error when I just ran python usage.py but the issue was resolved when I ran npm i and reran python usage.py.

Let me know if this works for you.

I’m sorry alishobeiri, I must be doing something wrong. I tried a couple of things but both have failed.

  1. [from the readme from github]: pip install dash-table-experiments

or

  1. [from the react comopnents installation guide (similar to your instructions above): git clone ‘http://(dash-table-package-url)’ && cd dash-table-experiments && npm install && python usage.py

When I use method #1, the package installs and I can import it in the interpreter, but the package won’t work in a dash app. When I use method #2 I get a couple of warnings during npm installation:

npm WARN optional Skipping failed optional dependency /chokidar/fsevents:                                                                            
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.1.2

and then usage.py fails at the load_components.

File "usage.py", line 5, in <module>
    import dash_table_experiments as dt
  File "/dash-table-experiments/dash_table_experiments/__init__.py", line 10, in <module>
    'dash_table_experiments'
  File "/usr/software/conda3/4.4.0/lib/python3.6/site-packages/dash/development/component_loader.py", line 22, in load_components
    with open(metadata_path) as data_file:
FileNotFoundError: [Errno 2] No such file or directory: '/dash-table-experiments/dash_table_experiments/metadata.json'

I should say I’m happy to wait for Chris’s dash-table to stabilize but at the same time I’ve failed in the past to properly build a component from scratch, so I was thinking this could be a good second example.

Hi @sagardamle

Sorry that’s my bad. I didn’t actually realize Chris had the pip install command in the README.

In regards to the the second method which I’m personally more familiar with. I’m not sure what those warnings mean, but I think one command I forgot to mention that you should run is: python setup.py install.

The overall order that should hopefully work is: git clone ‘http://(dash-table-package-url)’ && cd dash-table-experiments && npm install && python setup.py install && python usage.py.

I just tried using method #1 and it seemed to get going without an error for me. Maybe trying pip uninstall and reinstall and see what happens.

Let me know how it goes.

Hi Chris,

  1. dash-table-experiments is so cool, if the filter support ‘>, <, >=, <=’ will be better. like https://github.com/bgerm/react-table-sorter-demo

  2. When I use the dash-table-experiments, it may disabled the function of Multi-Page Apps and URL Support. I’m not sure if my code is wrong. See below for details:

     import dash
     import dash_core_components as dcc
     import dash_html_components as html
     import json
     import pandas as pd
     import dash_table_experiments as dt
    
    
     print(dcc.__version__) # 0.6.0 or above is required
    
     app = dash.Dash()
    
     # Since we're adding callbacks to elements that don't exist in the app.layout,
     # Dash will raise an exception to warn us that we might be
     # doing something wrong.
     # In this case, we're adding the elements through a callback, so we can ignore
     # the exception.
     app.scripts.config.serve_locally=True
     app.config.supress_callback_exceptions = True
    
     app.layout = html.Div([
         dcc.Location(id='url', refresh=False),
         html.Div(id='page-content')
     ])
    
    
     index_page = html.Div([
         dcc.Link('Go to Page 1', href='/page-1'),
         html.Br(),
         dcc.Link('Go to Page 2', href='/page-2'),
     ])
    
     DF_GAPMINDER = pd.read_csv(
         'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'
     )
     DF_GAPMINDER = DF_GAPMINDER[DF_GAPMINDER['year'] == 2007]
    
     page_1_layout = html.Div([
         html.H4('Gapminder DataTable'),
         dt.DataTable(
             rows=DF_GAPMINDER.to_dict('records'),
             filterable=True,
             sortable=True,
             enable_drag_and_drop = False,
             id='datatable-gapminder'
         ),
         dcc.Graph(
             id='graph-gapminder'
         )
     ], className="container")
    
     @app.callback(
         dash.dependencies.Output('graph-gapminder', 'figure'),
         [dash.dependencies.Input('datatable-gapminder', 'rows')])
     def update_figure(rows):
         dff = pd.DataFrame(rows)
         fig = plotly.tools.make_subplots(
             rows=3, cols=1,
             subplot_titles=('Life Expectancy', 'GDP Per Capita', 'Population',),
             shared_xaxes=True)
         marker = {'color': '#0074D9'}
         fig.append_trace({
             'x': dff['country'],
             'y': dff['lifeExp'],
             'type': 'bar',
             'marker': marker
         }, 1, 1)
         fig.append_trace({
             'x': dff['country'],
             'y': dff['gdpPercap'],
             'type': 'bar',
             'marker': marker
         }, 2, 1)
         fig.append_trace({
             'x': dff['country'],
             'y': dff['pop'],
             'type': 'bar',
             'marker': marker
         }, 3, 1)
         fig['layout']['showlegend'] = False
         fig['layout']['height'] = 800
         fig['layout']['margin'] = {
             'l': 20,
             'r': 20,
             't': 60,
             'b': 200
         }
         return fig
    
     page_2_layout = html.Div([
         html.H1('Page 2'),
         dcc.RadioItems(
             id='page-2-radios',
             options=[{'label': i, 'value': i} for i in ['Orange', 'Blue', 'Red']],
             value='Orange'
         ),
         html.Div(id='page-2-content'),
         html.Br(),
         dcc.Link('Go to Page 1', href='/page-1'),
         html.Br(),
     dcc.Link('Go back to home', href='/')
     ])
    
     @app.callback(dash.dependencies.Output('page-2-content', 'children'),
                   [dash.dependencies.Input('page-2-radios', 'value')])
     def page_2_radios(value):
         return 'You have selected "{}"'.format(value)
    
    
     # Update the index
     @app.callback(dash.dependencies.Output('page-content', 'children'),
                   [dash.dependencies.Input('url', 'pathname')])
     def display_page(pathname):
         if pathname == '/page-1':
             return page_1_layout
         elif pathname == '/page-2':
             return page_2_layout
         else:
             return index_page
         # You could also return a 404 "URL not found" page here
    
     app.css.append_css({
         'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
     })
    
    
     if __name__ == '__main__':
         app.run_server(host='0.0.0.0', port=8086, debug=True)
1 Like

Excellent, dash_table_experiments looks great!
I’m getting Error loading layout when I try it anywhere else aside of Chris’s example, any ideas what can cause this? Thanks!

Hi,

is there also a way to make rows in a table “selectable” and get e.g. the corresponding index in a callback?

1 Like

Great idea @rumcajs! I just created v0.3.0 which includes row selection. Row selection enables some very sweet crossfiltering applications. Here’s the example from usage.py (~100 lines of code)

5 Likes

First of all: thank you so mutch for this great table implementation!

I tried to integrate it into an app which uses dcc.Location and dynamic layout.
It seems like if one uses dcc.Location and the URL contains a DataTable the page breaks without any error.

I modified the usage.py example to demonstrage this behaviour

usage:

    import dash
    from dash.dependencies import Input, Output, State
    import dash_core_components as dcc
    import dash_html_components as html
    import dash_table_experiments as dt

    import pandas as pd
    import plotly

    app = dash.Dash()
    app.config.supress_callback_exceptions = True

    DF_WALMART = pd.read_csv(
        'https://raw.githubusercontent.com/plotly/datasets/master/1962_2006_walmart_store_openings.csv')

    DF_GAPMINDER = pd.read_csv(
        'https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv'
    )
    DF_GAPMINDER = DF_GAPMINDER[DF_GAPMINDER['year'] == 2007]

    DF_SIMPLE = pd.DataFrame({
        'x': ['A', 'B', 'C', 'D', 'E', 'F'],
        'y': [4, 3, 1, 2, 3, 6],
        'z': ['a', 'b', 'c', 'a', 'b', 'c']
    })

    _pages = {
        'page1': {
            'url': '/page1',
        },
        'page2': {
            'url': '/page2',
        }
    }

    app.layout = html.Div([
            html.Div(id="content"),
            dcc.Location(id='location', refresh=False)
        ])


    def _get_page_content(name):
        if name == 'page1':
            return _render_page1()
        elif name == 'page2':
            return _render_page2()


    def _render_page1():
        return html.H1('Page 1')


    def _render_page2():
        return html.Div([
            html.H1('Page 2'),
            html.H4('Gapminder DataTable'),
            dt.DataTable(
                rows=DF_GAPMINDER.to_dict('records'),

                # optional - sets the order of columns
                columns=sorted(DF_GAPMINDER.columns),

                row_selectable=True,
                filterable=False,
                sortable=True,
                selected_rows=[],
                id='datatable-gapminder'
            ),
            html.Div(id='selected-indexes'),
            dcc.Graph(
                id='graph-gapminder'
            ),

            html.H4('Simple DataTable'),
            dt.DataTable(
                rows=DF_SIMPLE.to_dict('records'),
                filterable=False,
                sortable=True,
                id='datatable'
            ),
            dcc.Graph(
                id='graph'
            ),
        ], className="container")


    @app.callback(
        Output('datatable-gapminder', 'selected_rows'),
        [Input('graph-gapminder', 'clickData')],
        [State('datatable-gapminder', 'selected_rows')])
    def update_selected_rows(clickData, selected_rows):
        if clickData:
            new_points = [point['pointNumber'] for point in clickData['points']]
        else:
            new_points = []
        return new_points + selected_rows


    @app.callback(
        Output('graph-gapminder', 'figure'),
        [Input('datatable-gapminder', 'rows'),
         Input('datatable-gapminder', 'selected_rows')])
    def update_figure(rows, selected_rows):
        dff = pd.DataFrame(rows)
        fig = plotly.tools.make_subplots(
            rows=3, cols=1,
            subplot_titles=('Life Expectancy', 'GDP Per Capita', 'Population',),
            shared_xaxes=True)
        marker = {'color': ['#0074D9'] * len(dff)}
        for i in (selected_rows or []):
            marker['color'][i] = '#FF851B'
        fig.append_trace({
            'x': dff['country'],
            'y': dff['lifeExp'],
            'type': 'bar',
            'marker': marker
        }, 1, 1)
        fig.append_trace({
            'x': dff['country'],
            'y': dff['gdpPercap'],
            'type': 'bar',
            'marker': marker
        }, 2, 1)
        fig.append_trace({
            'x': dff['country'],
            'y': dff['pop'],
            'type': 'bar',
            'marker': marker
        }, 3, 1)
        fig['layout']['showlegend'] = False
        fig['layout']['height'] = 800
        fig['layout']['margin'] = {
            'l': 20,
            'r': 20,
            't': 60,
            'b': 200
        }
        return fig


    @app.callback(
        Output('graph', 'figure'),
        [Input('datatable', 'rows')])
    def update_figure(rows):
        dff = pd.DataFrame(rows)
        return {
            'data': [{
                'x': dff['x'],
                'y': dff['y'],
                'text': dff['z'],
                'type': 'bar'
            }]
        }


    @app.callback(
        Output('content', 'children'),
        [Input('location', 'pathname')])
    def display_content(pathname):
        if pathname is None:
            return html.Div()
        matched = [c for c in _pages.keys()
                   if _pages[c]['url'] == pathname]

        if matched and matched[0] != 'index':
            content = html.Div([
                html.Div(_get_page_content(matched[0])),
            ])
        else:
            content = html.H1('Invalid Page')

        return content

    app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})

    if __name__ == '__main__':
        app.run_server(debug=True)
1 Like

@HansG Thanks for reporting! I see what’s going on here.

A little context: When Dash serves the page, it crawls the app.layout to see which component libraries are being used (e.g. dash_core_components). Then, with this list of unique component libraries, it serves the necessary JS and CSS bundles that are distributed with those component libraries.

In this case, we’re serving dash_table_experiments on a separate page, as the response of a callback. Dash only sees dash_html_components and dash_core_components in the app.layout and so it doesn’t serve the necessary JS and CSS bundles that are required for the dash-table-components component that is rendered in the future.

This is a design flaw of Dash. For now, you can get around this issue by rendering a hidden dash-table-experiments component in the layout like:

app.layout = html.Div([
    html.Div(id='content'),
    dcc.Location(id='location', refresh=False),
    html.Div(dt.DataTable(rows=[{}]), style={‘display’: ‘none’})
])
9 Likes

@chriddyp Thanks for the quick fix.

I had to add an empty records dict to the rows property of the DataTables object. Without it dash throws an Error loading dependencies error in the browser.
But for now, it works!

2 Likes