Black Lives Matter. Please consider donating to Black Girls Code today.

Display tables in Dash

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’})
])
8 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

Good suggestion! I just add preliminary support for this in the latest version.

The latest version (v0.5.0) fixes several bugs and adds support for simultaneous row-selection, filtering, and sorting:

3 Likes

Hi!
First of all, thanks for this great features! :slight_smile:
I was wondering, is it possible to:

  • insert links into a column?
  • export table as a csv?

Thanks!

See Download raw data

Not possible right now, but tracking in https://github.com/plotly/dash-table-experiments/issues/6

1 Like

Thanks! :slight_smile:

1 Like

Hi Chris. This might be a bit pie in the sky. But what would be great would be to use dash core components in the table itself. Ideally, you could specify a column as a dropdown menu with the options the same for each row, and then you would pass in the value of each row as normal.

2 Likes

@chriddyp
If i add the hidden element to the main page layout, then i am getting the following error along with “Error loading layout” on the browser -

Blockquote
[pratik@bi-insightslab apps]$ uwsgi --http :8888 --wsgi-file /home/pratik/tools/apps/index.py --callable server --master --processes 1 --threads 4
*** Starting uWSGI 2.0.15 (64bit) on [Wed Sep 20 18:39:58 2017] ***
compiled with version: 4.4.7 20120313 (Red Hat 4.4.7-18) on 24 August 2017 01:08:04
os: Linux-2.6.32-696.1.1.el6.centos.plus.x86_64 #1 SMP Wed Apr 12 00:10:12 UTC 2017
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on :8888 fd 4
uwsgi socket 0 bound to TCP address 127.0.0.1:41455 (port auto-assigned) fd 3
Python version: 2.7.12 (default, Jun 28 2016, 17:49:40) [GCC 4.4.7 20120313 (Red Hat 4.4.7-17)]
Python main interpreter initialized at 0x1d7ac60
python threads support enabled
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 207360 bytes (202 KB) for 4 cores
*** Operational MODE: threaded ***
WSGI app 0 (mountpoint=’’) ready in 204 seconds on interpreter 0x1d7ac60 pid: 85492 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI master process (pid: 85492)
spawned uWSGI worker 1 (pid: 85858, cores: 4)
spawned uWSGI http 1 (pid: 85859)
[pid: 85858|app: 0|req: 1/1] 121.244.34.250 () {38 vars in 928 bytes} [Wed Sep 20 18:43:32 2017] GET / => generated 568 bytes in 5 msecs (HTTP/1.1 200) 6 headers in 447 bytes (1 switches on core 0)
[2017-09-20 18:43:34,019] ERROR in app: Exception on /_dash-layout [GET]
Traceback (most recent call last):
File “/usr/lib/python2.7/site-packages/flask/app.py”, line 1982, in wsgi_app
response = self.full_dispatch_request()
File “/usr/lib/python2.7/site-packages/flask/app.py”, line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File “/usr/lib/python2.7/site-packages/flask/app.py”, line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File “/usr/lib/python2.7/site-packages/flask/app.py”, line 1612, in full_dispatch_request
rv = self.dispatch_request()
File “/usr/lib/python2.7/site-packages/flask/app.py”, line 1598, in dispatch_request
return self.view_functionsrule.endpoint
File “/usr/lib/python2.7/site-packages/dash/dash.py”, line 156, in serve_layout
cls=plotly.utils.PlotlyJSONEncoder),
File “/usr/lib64/python2.7/json/init.py”, line 251, in dumps
sort_keys=sort_keys, **kw).encode(obj)
File “/usr/lib/python2.7/site-packages/plotly/utils.py”, line 136, in encode
encoded_o = super(PlotlyJSONEncoder, self).encode(o)
File “/usr/lib64/python2.7/site-packages/simplejson/encoder.py”, line 275, in encode
chunks = self.iterencode(o, _one_shot=True)
File “/usr/lib64/python2.7/site-packages/simplejson/encoder.py”, line 357, in iterencode
return _iterencode(o, 0)
File “/usr/lib/python2.7/site-packages/plotly/utils.py”, line 201, in default
return encoding_method(obj)
File “/usr/lib/python2.7/site-packages/plotly/utils.py”, line 210, in encode_as_plotly
return obj.to_plotly_json()
TypeError: unbound method to_plotly_json() must be called with DataTable instance as first argument (got nothing instead)
[pid: 85858|app: 0|req: 2/2] 121.244.34.250 () {42 vars in 981 bytes} [Wed Sep 20 18:43:34 2017] GET /_dash-layout => generated 291 bytes in 4 msecs (HTTP/1.1 500) 2 headers in 84 bytes (1 switches on core 1)
[pid: 85858|app: 0|req: 4/3] 121.244.34.250 () {38 vars in 910 bytes} [Wed Sep 20 18:43:34 2017] GET /favicon.ico => generated 568 bytes in 4 msecs (HTTP/1.1 200) 5 headers in 264 bytes (1 switches on core 2)
[pid: 85858|app: 0|req: 4/4] 121.244.34.250 () {42 vars in 993 bytes} [Wed Sep 20 18:43:34 2017] GET /_dash-dependencies => generated 553 bytes in 6 msecs (HTTP/1.1 200) 5 headers in 256 bytes (1 switches on core 3)
Blockquote

If am removing the hidden element then the rest of layout is being loaded correctly

The main page layout code is as follows -


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

from app import app,server
import toc, visibility, pricing, whatifscenario

app.layout = html.Div([
dcc.Location(id=‘url’, refresh=False),
html.Div(dt.DataTable, style={‘display’: ‘none’}),
html.Div(
dcc.Tabs(
tabs=[
{‘label’: ‘Table of Contents’, ‘value’: 1},
{‘label’: ‘Visibility Elasticity’, ‘value’: 2},
{‘label’: ‘Pricing Elasticity’, ‘value’: 3},
{‘label’: ‘What if Scenario’, ‘value’: 4},
],
value=1,
id=‘tabs’,
vertical=True,
style={
‘height’: ‘100vh’,
‘borderRight’: ‘thin lightgrey solid’,
‘textAlign’: ‘left’
}
),
style={‘width’: ‘20%’, ‘float’: ‘left’}
),
html.Div(html.Div(id=‘tab-output’),style={‘width’: ‘80%’, ‘float’: ‘right’})

],style={
‘fontFamily’: ‘Sans-Serif’,
‘margin-left’: ‘auto’,
‘margin-right’: ‘auto’,
}
)

@app.callback(Output(‘url’, ‘pathname’),
[Input(‘tabs’, ‘value’)])
def display_page(value):
if value == 2:
return '/visibility’
elif value == 3:
return '/pricing’
elif value == 4:
return '/whatif’
else:
return ‘/’

@app.callback(Output(‘tab-output’, ‘children’),
[Input(‘url’, ‘pathname’)])
def display_page(pathname):
if pathname == ‘/visibility’:
return visibility.layout
elif pathname == ‘/pricing’:
return pricing.layout
elif pathname==’/whatif’:
return whatifscenario.layout
else:
return toc.layout

if name == ‘main’:
server.run()


try html.Div(dtable.DataTable(rows=[{}]), style={‘display’: ‘none’})

2 Likes

Thanks for the clarification. It worked. :smiley:

Hi Chris,

When do you think you will merge table code into the core components?

I’m looking to use the editable table feature, where could i read more about it?

Thanks, Dave

Undefined timeline right now, still soliciting feedback from the community and from our Plotly On-Premise customers.

However, you can use this today by following the instructions in https://github.com/plotly/dash-table-experiments. Follow this thread or the CHANGELOG.md file for changes and note that backwards incompatible changes may be made.

I just created a simple example that uses editable=True. You can check it out here: https://github.com/plotly/dash-table-experiments/blob/master/usage-editable.py

Chris, thanks for the prompt reply. I plan on using it and I will give you feedback on it.

Hi Chris,

I’m attempting to use the dash-table-experiments example you provided while combining it with what you’ve accomplished with tabs.

I’m able to recreate your usage.py without issue on its own and have had no issues creating apps with tabs. However, when I attempt to package the app.layout from your example into a ‘children Div’ to show when clicking a tab, the platform hangs and cannot switch between tabs… nor does it show the table and graphs.

Here is the full code:

'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.scripts.config.serve_locally = True
’app.config.supress_callback_exceptions = True

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

'test_layout = html.Div([

’ 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=True,
’ sortable=True,
’ selected_row_indices=[],
’ id=‘datatable-gapminder’
’ ),
’ html.Div(id=‘selected-indexes’),
’ dcc.Graph(
’ id=‘graph-gapminder’
’ ),
’], className=“container”)

‘app.layout = html.Div([

’ html.Div([
’ html.Div(
’ dcc.Tabs(
’ tabs=[
’ {‘label’:‘First View’, ‘value’:1},
’ {‘label’:‘Second View’, ‘value’:2},
’ {‘label’:‘Third View’, ‘value’:3}
’ ],
’ value=1,
’ id=‘overall-tabs’,
’ vertical=False,
’ style={
’ ‘borderRight’:‘thin lightgrey solid’,
’ ‘textAlign’:‘left’
’ }
’ ),
’ style={‘width’:‘100%’, ‘float’:‘top’}
’ ),
’ html.Div(
’ html.Div(id=‘tab-output’),
’ style={‘width’:‘100%’, ‘float’:‘bottom’}
’ )
’ ],
’ style={‘fontFamily’:‘Dosis’,
’ }),

'# test_layout"""

'])

@app.callback(
’ dash.dependencies.Output(‘tab-output’, ‘children’),
’ [dash.dependencies.Input(‘overall-tabs’, ‘value’)])
‘def display_content(overall_tab):
’ if overall_tab == 1:
’ return html.Div([])
’ elif overall_tab == 2:
’ return html.Div([])
’ elif overall_tab == 3:
’ return test_layout

@app.callback(
’ Output(‘datatable-gapminder’, ‘selected_row_indices’),
’ [Input(‘graph-gapminder’, ‘clickData’)],
’ [State(‘datatable-gapminder’, ‘selected_row_indices’)])
‘def update_selected_row_indices(clickData, selected_row_indices):
’ print ‘make it here?’
’ if clickData:
’ for point in clickData[‘points’]:
’ if point[‘pointNumber’] in selected_row_indices:
’ selected_row_indices.remove(point[‘pointNumber’])
’ else:
’ selected_row_indices.append(point[‘pointNumber’])
’ return selected_row_indices

@app.callback(
’ Output(‘graph-gapminder’, ‘figure’),
’ [Input(‘datatable-gapminder’, ‘rows’),
’ Input(‘datatable-gapminder’, ‘selected_row_indices’)])
‘def update_figure(rows, selected_row_indices):
’ 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_row_indices 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’: 40,
’ ‘r’: 10,
’ ‘t’: 60,
’ ‘b’: 200
’ }
’ fig[‘layout’][‘yaxis3’][‘type’] = ‘log’
’ return fig

‘if name == ‘main’:
’ app.run_server(debug=True)

I know test_layout produces the original display, it can be shown by uncommenting test_layout within app.layout and commenting the tabs related Div above it. Interestingly, if I try to use both at once, I get test_layout showing up as a tab and as its separate Div.

Any ideas on why it will not show within a tab without including it as its own Div?

Thanks for all the help, great product!