Button inside a Dash Table

Hi Everybody,

Today @AnnMarieW and I performed an option to have a kind of Button inside a Dash data Table.

Let me explain the trick:

  • Use active_cell property of the table as Input of a callback.
  • The active_cell property has (at least) 3 elements of the active cell, those are row, column, and column_id.
  • That means if a user selects the second element of the first column the active_cell will give us the following information: {‘row’: 1, ‘column’: 0, ‘column_id’: ‘first_column_name’}
  • Then using the derived_viewport_data property we can find and select the element inside the active cell selected by the user.
  • And with that element we can perform any action releated with that element as when the user clicks a specific Button.

Let me show you two different examples:

  • In the first one only when the user select an element of the first column the callback search for that specific element in an other link. That means if the active cell is not in the first column the callback will do nothing.
  • In the second, the user can select any cell of the table, and the output will show the element of the selected cell, no matters to wich column belongs that cell.

First example: It shows a table from yahoo finance data from today’s stock losers and if the user select a Symbol, the callback will call an app to analize that Company.

import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_html_components as html
import pandas as pd
import dash_core_components as dcc

app = dash.Dash(__name__)

# get the table of todays losers from Yahoo finance
urlyahoo = 'https://finance.yahoo.com/losers'
tables = pd.read_html(urlyahoo)
losers = pd.DataFrame(tables[0])
losers.drop(['Volume', 'Avg Vol (3 month)', 'PE Ratio (TTM)', '52 Week Range'], axis='columns', inplace=True)

app.layout = html.Div(
    [
        html.H2("This table has the Yahoo Today's Losers"),
        html.H2("Select the Symbol you want to analyze:"),
        dash_table.DataTable(
            id="table",
            fixed_rows={'headers': True},
            columns=[{"name": i, "id": i} for i in losers.columns],
            data=losers.to_dict("records"),
            is_focused=True,
            style_data_conditional=[
                {'if': {'column_id': 'Symbol'}, 'backgroundColor': 'green', 'text_align':'center','color': 'white'},
                {'if': {'column_id': '% Change'}, 'backgroundColor': 'yellow', 'color': 'red', 'font-weight': 'bold'},
                                   ],
        ),
        dcc.Location(id='location'),
    ]
)


@app.callback(
    Output("location", "href"),
    Input("table", "active_cell"),
    State("table", "derived_viewport_data"),
)
def cell_clicked(active_cell, data):
    if active_cell:
        row = active_cell["row"]
        col = active_cell["column_id"]

        if col == "Symbol":  # or whatever column you want
            selected = data[row][col]
            return f"https://companyanalysis.herokuapp.com/{selected}"
        else:
            return dash.no_update


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

The second will get any element selected by the user:

import dash
from dash.dependencies import Input, Output, State
import dash_table
import dash_html_components as html
import pandas as pd

app = dash.Dash(__name__)

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/solar.csv")

app.layout = html.Div(
    [
        dash_table.DataTable(
            id="table",
            columns=[{"name": i, "id": i} for i in df.columns],
            data=df.to_dict("records"),
            is_focused=True,
        ),
        html.Div(id="output"),
    ]
)


@app.callback(
    Output("output", "children"),
    Input("table", "active_cell"),
    State("table", "derived_viewport_data"),
)
def cell_clicked(cell, data):
    if cell:
        selected = data[cell["row"]][cell["column_id"]]
        return f"You have selected {selected}"
    else:
        return dash.no_update


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

And also you can style the elements before and after the user select the cell to gave the impresion that the user are clicking a button or a Link.

This is just expamples to show how the callbacks takes the element selected by the user, in the first example it could be possible to directly use markdown with a url link.

9 Likes

Very cool! Considering how often this feature gets requested by the community, it’s nice to have a nifty work-around that fulfills this type of requirement.

2 Likes

Yes, I love this! This was one of the main reasons I made active_cell a first-class property that you can listen to - so great to see a complete example showing it all put together! This would be a welcome addition to official datatable docs. Perhaps in the python-interactivity chapter Sorting, Filtering, Selecting, and Paging Natively | Dash for Python Documentation | Plotly (dash-docs/dash_docs/chapters/dash_datatable/interactivity at master · plotly/dash-docs · GitHub)

1 Like

Thanks Chris !!
I needed something like this to my app and I asked on Friday to the Dash data table Master (@AnnMarieW) and one day after we found this simple (and interesting) solution. :smiley:

Here’s an updated example that might work well with the chapter @chriddyp mentioned.

It listens to the active_cell property and updates a graph on change. Essentially, every cell acts like a button.


import dash
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_table
import dash_html_components as html
import plotly.express as px

app = dash.Dash(__name__)

df = px.data.gapminder()
df["id"] = df.index
dff = df[df.year == 2007]
columns = ["country", "continent", "lifeExp", "pop", "gdpPercap"]
color = {"lifeExp": "#636EFA", "pop": "#EF553B", "gdpPercap": "#00CC96"}
initial_active_cell = {"row": 0, "column": 0, "column_id": "country", "row_id": 0}

app.layout = html.Div(
    [
        html.Div(
            [
                html.H3("2007 Gap Minder"),
                dash_table.DataTable(
                    id="table",
                    columns=[{"name": c, "id": c} for c in columns],
                    data=dff.to_dict("records"),
                    page_size=10,
                    sort_action="native",
                    active_cell=initial_active_cell,
                ),
            ],
            style={"margin": 50},
        ),
        html.Div(id="output"),
    ]
)


@app.callback(
    Output("output", "children"), Input("table", "active_cell"),
)
def cell_clicked(active_cell):
    if active_cell is None:
        return dash.no_update

    row = active_cell["row_id"]
    col = active_cell["column_id"]
    country = df.at[row, "country"]
    y = col if col in ["pop", "gdpPercap"] else "lifeExp"

    fig = px.line(
        df[df["country"] == country], x="year", y=y, title=" ".join([country, y])
    )
    fig.update_layout(title={"font_size": 20})
    fig.update_traces(line=dict(color=color[y]))

    return dcc.Graph(figure=fig)


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

5 Likes

Hey everybody,
For anyone interested in learning more about how Ann’s code works, I made a tutorial on it here.

4 Likes