Checkbox in Dash Datatable

I’m attempting to have several cells in a dash datatable containing checkboxes that the user can select (not selecting the column or row, but selecting the cell, thereby id’ing the row/column). Further I’m interested in a callback to determine which cells are checked.

I’ve looked through the markdown options, but can’t figure out if this is possible.
Similar to this example (except with more columns and rows of checkboxes):

1 Like

Hi @zachmorris

Check this page:
https://dash.plotly.com/datatable/interactivity

Yes, I’ve checked that page. As far as that shows, you can add a checkbox or radio for the row or column, but I want to put the checkbox in the cell. Is it possible?

I’m not sure if it is possible, what are you trying to implement?

From what I can tell, I think you’re correct in that it’s not possible.
I want a table detailing some events, and the user checks a box or some boxes to trigger a callback based on the cell(s) that they’ve selected.

It appears this is possible with the bootstrap components, but I’m already utilizing datatables in other areas and I wanted to use it for commonality if possible.

Opened a feature request here

Hi @zachmorris

Check this link, Ithink could be useful for your needs:

Hi @zachmorris

I think the best option would be to use an html table and style it so it’s similar to a Dash Datatable. Then you could put a checkbox component in the table, and it would work quite nicely.

But here is one workaround with a DataTable. You can use check boxes as icons in a dropdown. The callback could use the data prop as an Input.

image


import dash
import dash_html_components as html
import dash_table
import pandas as pd
from collections import OrderedDict


app = dash.Dash(__name__)

df = pd.DataFrame(
    OrderedDict(
        [
            ("Task", ["Task 1 ", "Task 2", "Task 3", "Task 4"]),
            ("Employee1", ["checked", "unchecked", "checked", "unchecked"]),
            ("Employee2", ["unchecked", "unchecked", "checked", "checked"]),
        ]
    )
)


app.layout = html.Div(
    [
        dash_table.DataTable(
            id="table-dropdown",
            data=df.to_dict("records"),
            columns=[
                {"id": "Task", "name": ""},
                {"id": "Employee1", "name": "Employee 1", "presentation": "dropdown"},
                {"id": "Employee2", "name": "Employee 2", "presentation": "dropdown"},
            ],
            style_data_conditional=[
                {
                    "if": {"column_id": ["Employee1", "Employee2"]},
                    "fontSize": 30,
                    "textAlign": "center",
                }
            ],
            editable=True,
            dropdown={
                "Employee1": {
                    "options": [
                        {"label": "☑ ", "value": "checked"},
                        {"label": "☐", "value": "unchecked"},
                    ],
                    "clearable": False,
                },
                "Employee2": {
                    "options": [
                        {"label": "☑ ", "value": "checked"},
                        {"label": "☐", "value": "unchecked"},
                    ],
                    "clearable": False,
                },
            },
        ),
        html.Div(id="table-dropdown-container"),
    ]
)


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

2 Likes

Thanks @AnnMarieW . I think I’ll try and work a solution with dash bootstrap components. Hopefully the issue is taken up and a feature is eventually added (which I’d think might be easy if markdown supported a markdown checkbox)

@AnnMarieW always coming up with a sneaky workaround :crazy_face:

1 Like

Hey @zachmorris and @AnnMarieW

I just find another way to solve it.

  • Add an additional column before the column you want to select.
  • Fill that column with an emoji to represent what you want to show, in this case I chose this :white_large_square:
  • then when the user click the box, the active_cell will give you that cell coordinates, and just search for the elements that has the same row but in the following column :grinning:
  • Style the active sell to show the user that the cell was selected.

Here is an example:

import dash
import pandas as pd
from bs4 import BeautifulSoup
import requests
from datetime import date
from datetime import datetime as dt
import numpy as np
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.exceptions import PreventUpdate


# -*- coding: utf-8 -*-

# * function that bild the table
# ******************************
# define URL
urlyahoo = 'https://finance.yahoo.com/losers'

# * get the table in Pandas *
# ***************************
tables = pd.read_html(urlyahoo)
losers = pd.DataFrame(tables[0])

# Drop the columns that do not matters
losers.drop(['Volume', 'Avg Vol (3 month)', 'PE Ratio (TTM)', '52 Week Range'], axis='columns', inplace=True)
# Raname Columns
losers.columns = ['Ticker','Company Name','Price', 'Change', '% Change', 'Mkt Cap']
# Add a checkbox column
losers.insert(0, 'Select', '⬜') 

# create the table to show the inforamtion
table = html.Div([
    dash_table.DataTable(
        columns=[{"name": i, "id": i} for i in losers.columns],
        data=losers.to_dict('records'),
        editable=False,
        id='table_losers',
        style_as_list_view= True,
        style_header={'backgroundColor': 'white', "font-size" : "14px", 'fontWeight': 'bold', 'textAlign': 'center'},
        is_focused=True,

        column_selectable= 'single',
        style_data_conditional=[
            {'if': {'state': 'active'},'backgroundColor': 'green', 'border': '12px solid white'},
            {'if': {'column_id': 'Select'}, 'width': 10},
            {'if': {'column_id': 'Ticker'}, 'text_align':'center', 'backgroundColor': 'green', 'width': 30, 'color': 'white', 
             'border': '12px solid white', 'letter-spacing': '3px'},
            {'if': {'column_id': 'Company Name'}, 'textAlign': 'left', 'text-indent': '10px', 'width':100},
            {'if': {'column_id': '% Change'}, 'backgroundColor': 'yellow', 'color': 'red', 'font-weight': 'bold'},
                               ],
        style_data={"font-size" : "14px", 'height': 15, "background":"white", 'border': '1px solid white'},
        fixed_rows={'headers': True})
    ])

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div(table),
    html.H2(id='message'),
])

@app.callback(Output("message", "children"),
              [Input('table_losers', 'active_cell'),
              Input('table_losers', 'derived_viewport_data')])
def update_loosers(cell, data):
    if not cell:
        raise PreventUpdate    
    # imprime la celda activa
    if cell["column_id"] == 'Select':
        if cell:
            selected = data[cell["row"]]["Ticker"]
            return "You have selected the Ticker: "+selected
    else:
        return " " 


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

It will show this:

And when the user select a box it will show this:

This solution is just to select one option, if you want to do a multi option you need to use a dataframe in the callback to append and store all the selected options.

I was going to propose this exact approach after seeing what @AnnMarieW has showcased in another post (Button inside a Dash Table - #5 by AnnMarieW). However, it will mean lots of callbacks, one for every single check/uncheck action in the entire table. Also, state management of the last known state of each checkbox will also require custom logic. Not sure if it’s natively managed already by Dash DataTable.

Hey @DeejL

I don’t think is too complicate, you just need to define where you want to have the boxes, define if there are any restriction for selecting any column and use a DataFrame to append all the user selections.

The logic is the same no matters how complicated is the table, ones you have a dataframe with the user selections just use this information to do your task.

@AnnMarieW @DeejL @zachmorris

I think this aproach gives the posibility to select more than one item and also de-select any one.

As I’m changing the background, I think it is also possible to change the content of the cell with other emoji like :heavy_check_mark:

And with the dataframe of the user selection you can do anything.

Here is the code:

import dash
import pandas as pd
import numpy as np
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.exceptions import PreventUpdate


# -*- coding: utf-8 -*-

# * function that bild the table
# ******************************
# define URL
urlyahoo = 'https://finance.yahoo.com/losers'

# dataframe to store the user selection
df_selection = pd.DataFrame(columns = ['Row','Column'])

df_selection.to_csv('last_selected.csv')

# * bild the table *
# ******************

df = pd.DataFrame(columns = ['Task','Employee1', 'Employee2', 'Employee3', 'Employee4'])
df['Task']= ["Task 1 ", "Task 2", "Task 3", "Task 4"]
df['Employee1'] = ['⬜','⬜','⬜','⬜']
df['Employee2'] = ['⬜','⬜','⬜','⬜']
df['Employee3'] = ['⬜','⬜','⬜','⬜']
df['Employee4'] = ['⬜','⬜','⬜','⬜']
df['Employee5'] = ['⬜','⬜','⬜','⬜']
df['Employee6'] = ['⬜','⬜','⬜','⬜']
df['Employee7'] = ['⬜','⬜','⬜','⬜']
df['Employee8'] = ['⬜','⬜','⬜','⬜']

tables = pd.read_html(urlyahoo)
losers = pd.DataFrame(tables[0])

# create the table to show the inforamtion
table = html.Div([
    dash_table.DataTable(
        columns=[{"name": i, "id": i} for i in df.columns],
        data=df.to_dict('records'),
        editable=False,
        is_focused=True,
        column_selectable= 'single',
        id='table_task',
        style_data={"font-size" : "14px", 'width': 15, "background":"white", 'text-align': 'center'},
    )
])

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div(table),
    html.H2(id='message'),
])

@app.callback(Output("table_task", "style_data_conditional"),
              [Input('table_task', 'active_cell'),
              Input('table_task', 'derived_viewport_data')])
def update_loosers(cell, data):
    if not cell:
        raise PreventUpdate    

    if cell:
        # control the user selections
        # select the table
        df_selection = pd.read_csv('last_selected.csv')
        # drop the first column
        df_selection.drop(df_selection.columns[0], axis=1, inplace=True)
        # take the new user selection
        new_row = {'Row':cell["row"], 'Column':cell["column_id"]}
        # append the table
        df_selection= df_selection.append(new_row, ignore_index=True)
        # search for doble click
        check = len(df_selection)       
        df_selection= df_selection.drop_duplicates(keep=False, ignore_index=True)
        if len(df_selection)<check:
            color='white'
        else:
            color='green'
            
        # store the dataframe
        df_selection.to_csv('last_selected.csv')

        # prepare the Outputs
        # color of the selected row
        out_format= [{'if': {'state': 'active'}, 'backgroundColor': color}]
        for r in range(len(df_selection)):
            condition= {'if': {'row_index': int('{}'.format(df_selection.loc[r]["Row"])), 'column_id': '{}'.format(df_selection.loc[r]["Column"])}, 'backgroundColor': 'green'}
            out_format.append(condition)
        
        return out_format
        
        
    raise PreventUpdate


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

Here is the Output:

hmmm interesting. I’ll have a closer look a little later. But you could probably update the df with the check mark or box icon, then instead of updating the style, just update the data:

Output("table_task", "data"),

Yes, and check if the user un-select the cell to add the box again. Like I did with the color variable. :smiley:

1 Like

Any progress on that?