Remove data from dash datatable

I have an issue with my dash datatable. The table I have is being used to show ‘events’ to the user. I have these events connected to my graph that displays lines on a graph.

In some instances, when they pull in data, there are pre-existing events that they may want to remove. However when they try to remove them, and then add a new event, the old event continues to pop back up. Is there a way to get it removed permanently?

Here is the datatable:

layout =   dbc.Col([
              html.Center(html.H5('Event Lines')),
              dash_table.DataTable(
                    id='selections',
                    columns=[
                              {'name': 'selected', 'id': 'selected'},
                            ],
                    row_deletable=True,
                        )
              ],
              style={'display': 'flex', 'flex-direction': 'column', 'gap': '10px'})

And here is the callback:

@callback(
    Output('selections', 'data'),
    Output('event-drop', 'value'),
    Output('event-date', 'value'),
    State('event-drop', 'value'),
    State('event-date', 'value'),
    State('selections', 'data'),
    Input('add-lines-btn', 'n_clicks'),
    Input('store', 'data')
)
def update_event_table(event_drop, event_date, selections, n1, data):
    df = pod.get_prev_events_for_current_graph(report=data['report'], engine_name=data['engine'])
    if type(df) == pd.DataFrame:
        if not df.empty:
            i = 0
            while i < len(df.index):
                selections = [{'selected': df['Event'][i]}]
                i += 1
            if event_drop:
                if selections:
                    selections.append({'selected': f'{event_date} {event_drop}'})
            
        selections_unique = []
        [selections_unique.append(x) for x in selections if x not in selections_unique]
                
        return selections_unique, dash.no_update, dash.no_update

Update:

Is there a way to only import the dataframe from my dcc.Store once? Like on page load or something. Because the problem is that every time I want to add an event, it redoes the entire callback which is why the dataframe information keeps coming back if someone tries to remove it.

Hello @bpolasek,

It really hard to say without seeing more of your flow.

Is your dcc.Store supposed to be reset based upon user? Data I think should be static, but you should be making sure that you are pulling the proper filters to build.

Let me know if this helps. I’ll remove some of the unnecessary stuff, but it’s still a lot.

Here is my app.py. On this page I run a callback that calls to the sql database and creates a dataframe of information and stores it in the dcc.Store with id=‘store’. It has gathered information from user input from the home page. (The second dcc.Store is for something else).

app.py

import dash
import dash_bootstrap_components as dbc
from dash import html, dcc, Output, Input, State
import dash_loading_spinners as dls

from src import sql_connections as sqlc
from src import sqlite_functions as sqlf
from src import wrangler as wr

app = dash.Dash(__name__, use_pages=True,
                external_stylesheets=[dbc.themes.BOOTSTRAP], 
                meta_tags=[{"name": "viewport", "content": "width=device-width"}], 
                suppress_callback_exceptions=True,
                background_callback_manager=background_callback_manager
                )

app.layout = html.Div([
    dcc.Store(id="store", data={}, storage_type='session'),
    dcc.Store(id='store-results', data={}, storage_type='session'),
    dash.page_container,
    ]
)


@app.callback(
    Output('store', 'data'),
    Output('error-alert', 'is_open'),
    Output('graph-redirect', 'pathname'),
    Output('timeout-alert', 'is_open'),
    Input('data-button', 'n_clicks'),
    State('engine-drop', 'value'),
    State('start-date', 'value'),
    State('region-drop', 'value'),
    State('report-options', 'value'),
    State('loading', 'children'),
    prevent_initial_call=True,
    background=True,
    running=[
        (Output('data-button', 'disabled'), True, False),
        # (Output('loading', 'visibility'), True, False),
        (Output('loading', 'children'), dls.Scale(), ''),
    ],
    
)
def query_data(n1, engine, date, region, report, loading):
    if loading is None:
            return dash.no_update, dash.no_update, dash.no_update, dash.no_update

    if n1:
        if engine == None or region == None:
            store = {}
            return store, True, dash.no_update, False
        else: 
            try:
                data = sqlc.get_data_for_app(region=region, engine=engine, end_date=date) 
                data = wr.get_past_year(df=data, end_date=date)
                logging.info('Saving for Graph')
                store = {"df": data.to_dict('records'), "date": date, "engine": engine, "report": report}
                logging.info('Data Saved to Store and Directed to Graph Page')
                return store, False, '/graph', False
            except Exception:
                return dash.no_update, False, dash.no_update, True

    return dash.no_update, dash.no_update, dash.no_update, dash.no_update



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

Here is my graph.py. On this page, I take the stored data, manipulate it to create a graph. Part of that is adding lines to the graph. If lines already exist in the dataframe, it automatically adds them. The user can also add more lines. They should also be able to delete the existing lines if they want. A coworker noticed that if they delete an existing line, but then go to add a different line, the existing line repopulates. And it’s because it’s in the callback. I was trying to see if there was a way to not have that part refresh every time they go to add a line.

graph.py

from dash import html, register_page, callback, Input, Output, State, dash_table, dcc
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
from datetime import datetime
import pandas as pd
import dash
import plotly.express as px
import plotly.graph_objects as go
from scipy import signal
from dateutil import relativedelta
import numpy as np

from src import pod_sql as pod
from src import wrangler as wr

register_page(__name__)


layout = html.Div(
              
...

            # Graph area
            html.Div(
                className="div-app",
                id='div-app',
                loading_state={'is_loading': True},
                children=[
                    dash_loading_spinners.Pacman(
                        fullscreen=True,
                        id='loading-whole-app'
                    )]),

...

                    # Add Event Lines
                    dbc.Col([
                        html.H5('Add Event Lines'),
                        dcc.Dropdown(
                            id='event-drop',
                            options=['ControlsUpgrade', 'EquipmentUpgrade', 'FilterChange', 'HGP', 'MA', 'ModelUpdate', 'OfflineWash', 'Tuning'],
                            placeholder='Event',
                        ),
                        html.P('on'),
                        dmc.DatePicker(
                            id='event-date',
                            maxDate=datetime.now().strftime('%Y-%m-%d'),
                            firstDayOfWeek='sunday',
                            inputFormat="MM/DD/YYYY"
                        ),
                        dbc.Button(
                            'Add',
                            id='add-lines-btn',
                            color='secondary',
                            n_clicks=0,
                        )
                    ]),
                    
                   #Event table
                    dbc.Col([
                        html.Center(html.H5('Event Lines')),
                        dash_table.DataTable(
                            id='selections',
                            columns=[
                                {'name': 'selected', 'id': 'selected'},
                                ],
                            data=[],
                            editable=True,
                            row_selectable=True
                        ),
                        dbc.Button(
                            'Remove',
                            id='remove-event-btn',
                            n_clicks=0
                        )
                    ])
                ]),
            ])
         ]
     )


# # Event Lines
@callback(
    Output('selections', 'data'),
    Output('event-drop', 'value'),
    Output('event-date', 'value'),
    State('event-drop', 'value'),
    State('event-date', 'value'),
    State('selections', 'data'),
    Input('add-lines-btn', 'n_clicks'),
    Input('store', 'data'),
    Input('remove-event-btn', 'n_clicks')
)
def update_event_table(event_drop, event_date, selections, add_btn, data, rm_btn):
    df = pod.get_prev_events_for_current_graph(report=data['report'], engine_name=data['engine'])

    if type(df) == pd.DataFrame:
        if not df.empty:
            logging.info(f'Importing {len(df.index)} events from previous report')
            i = 0
            while i < len(df.index):
                if selections: 
                    selections.append({'selected': df['Event'][i]})
                    i += 1
                else: 
                    selections = [{'selected': df['Event'][i]}]
                    i += 1
            if event_drop:
                if selections:
                    selections.append({'selected': f'{event_date} {event_drop}'})
            
        selections_unique = []
        [selections_unique.append(x) for x in selections if x not in selections_unique]
                
        return selections_unique, dash.no_update, dash.no_update

    else:
        if event_drop:
            if selections:
                logging.info(f'Adding event {event_date} {event_drop}')
                selections.append({'selected': f'{event_date} {event_drop}'})
            else: 
                logging.info(f'Adding event {event_date} {event_drop}')
                selections = [{'selected': f'{event_date} {event_drop}'}]
        return selections, dash.no_update, dash.no_update


# Callback for graph
@callback(
    Output('div-app', 'children'),
    Output('data-alert', 'is_open'),
    Output('offset-alert', 'is_open'),
    Input('store', 'data'),
    Input('new-start-date', 'value'),
    State('data-alert', 'is_open'),
    Input('offset-checkbox', 'value'),
    Input('offset-number', 'value'),
    State('offset-alert', 'is_open'),
    Input('lower-bound', 'value'),
    Input('upper-bound', 'value'),
    Input('selections', 'data')
)
def create_graph(
    data,
    new_start_date,
    open_alert,
    offset_initial,
    offset_custom,
    offset_alert,
    lower_bound,
    upper_bound,
    events
):

... 
        logging.debug(f'{type(events)}')
        if events:
            i = 0
            while i < len(events):
                event_date = events[i]['selected'][:10]
                event_type = events[i]['selected'][11:]
                logging.info(f'Adding event: {event_date} {event_type}')
                location = df['MeasureDate'].searchsorted(f'{event_date}T00:00:00')
                if event_type == 'ControlsUpgrade':
                    fig3.add_vline(x=location, line_color='rgb(255, 127, 14)', line_width=2)
                    i += 1
                if event_type == 'EquipmentUpgrade':
                    fig3.add_vline(x=location, line_color='rgb(44, 160, 44)', line_width=2)
                    i += 1
                if event_type == 'FilterChange':
                    fig3.add_vline(x=location, line_color='rgb(214, 39, 40)', line_width=2)
                    i += 1
                if event_type == 'HGP':
                    fig3.add_vline(x=location, line_color='rgb(148, 103, 189)', line_width=2)
                    i += 1
                if event_type == 'MA':
                    fig3.add_vline(x=location, line_color='rgb(227, 119, 194)', line_width=2)
                    i += 1
                if event_type == 'ModelUpdate':
                    fig3.add_vline(x=location, line_color='rgb(188, 189, 34)', line_width=2)
                    i += 1
                if event_type == 'OfflineWash':
                    fig3.add_vline(x=location, line_color='rgb(23, 190, 207)', line_width=2)
                    i += 1
                if event_type == 'Tuning':
                    fig3.add_vline(x=location, line_color='rgb(31, 119, 180)', line_width=2)
                    i += 1

        return dcc.Graph(figure=fig3), open_alert, open_alert

I’m leaning towards creating a list where you can add/remove with two buttons and using row_selectable instead of row_deletable. But currently haven’t been able to get that to work either.

If you want it to be completely independent, that upon load of this page, you could create a copy, that is solely for use on this page.

And then update that copy upon user interaction.


Using a div in the layout that triggers the data to reset.

Could you show me a little of what that might look like?

Sure.

@app.callback(
Output('graphingStore', 'data'),
Input('tempDiv','id'),
State('store','data')
)
def createGraphStore(_, data):
     if ctx.triggered_id = 'tempDiv':
         return data

Then update your graphingStore by adding the events to this callback, and use the input of this data to chart your graph.

1 Like

Thank you!