Unselect active_cell in dash datatable (python) - python

All,
I am trying to implement a dash datatable, where I select rows by a direct click on it (no radio buttons). Currently, I am doing this with the active_cell and it works well:
No matter in which cell of a row a user clicks, a graph is updated with the data in that row. If he clicks another cell in the same row, the data is unselected (via a dcc.storage)
Here comes my problem: If the user clicks the same cell again, there is no active_cell event triggered. Therefore, my callback is not triggered and nothing happens.
I would like to deselect that cell the second time the user clicks it. How can I implement that?
Thanks!

So... I solved this... it is not pretty but it works - it includes a loopbreaker which I had to implement to avoid a circular dependency, but yeah - I am absolutely open for cleaner solutions.
Find below the callbacks
# takes user selected cell (active_cell) and the current state of a dcc.storage (tableclick) which stores the last saved row that was clicked
# output: updated selected_rows for the datatable, styling for the selected row and update for dcc.storage
# if no cell is selected, do nothing
# if no cell is selected, but there is a row stored as selected, highlight that row (this is a consequence from the circular dependency)
# if a cell is selected that is different from the previous cell, highlight that new row. Otherwise, deselect the row.
#app.callback(
[Output('performancedatatable', 'style_data_conditional'), Output('tableclick', 'data'),
Output('performancedatatable', 'selected_rows')],
[
Input('performancedatatable', 'active_cell'),
], [State('tableclick', 'data')]
)
def highlight_row(cell, prevrow):
if cell is None:
if prevrow is None:
return [{}], None, []
else:
return [{}], None, prevrow
elif "row" in cell:
if cell.get("row", "") == prevrow:
return [{}], None, []
else:
return ([{
"if": {"row_index": cell.get("row", "")},
"backgroundColor": "rgba(146, 192, 234, 0.5)",
}], cell.get("row", ""), [cell.get("row", "")])
# Is triggered by changing the dcc.storage "tableclick"
# resets active cell and selected cell via callback below
#app.callback([Output('loopbreaker_div', "children")], [Input('tableclick', 'data')])
def reset_active_cell(input):
return [html.Div(id='loopbreaker', children=True)]
#loopbreaker to avoid circular dependency
#app.callback([Output('performancedatatable', "active_cell"), Output('performancedatatable', 'selected_cells')], [Input('loopbreaker', 'children')])
def reset_active_cell(input):
time.sleep(1)
return (None, [])
Shoutout to http://yaaics.blogspot.com/2019/03/circular-references-in-plotlydash.html for helping resolving the circular dependency

A simpler solution (but it has other drawbacks) is to select a hidden cell. This makes it appear to the user that nothing is selected.
In the example below a cell is processed and de-selected by a callback. Clearly this callback could also be for a "deselect all" button or whatever else you need.
Add column called "cant_see":
df = pd.read_csv("my_data.csv")
df.insert(0, "cant_see", ["" for i in df.iloc[:, 0] ])
Make it hidden when you create the DataTable using style_data_conditional and style_header_conditional:
dash_table.DataTable(
id="table",
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict("records"),
is_focused=True,
style_data_conditional=[
{'if': {'column_id': 'cant_see',}, 'display': 'None',}
],
style_header_conditional=[
{'if': {'column_id': 'cant_see',}, 'display': 'None',}
],
)
and then a callback can set the table's "active_cell" and/or "selected_cells"
#app.callback(
Output("table", "active_cell"),
Output("table", "selected_cells"),
Input("table", "active_cell"),)
def cell_clicked(cell):
# Do something useful with cell,
# such as toggling it's style to mimic select/de-select
# Now make it look like the cell is de-selected
# by selecting a hidden cell
#
# return active cell 0,0 and selected_cells list [ 0,0 ]
return {'column':0, 'row':0}, [{'column':0, 'row':0}]

Related

Store edited DataTable in Dash

I am trying to store an edited Python Dash DataTable into a dcc.store container with a button component and use the saved data at a later point. However, once I change the original DataTable, the stored container also changes. I found the following post (save a modified dash datatable to dataframe) but was not able to figure it out.
My core question is how can I store (copy) some data in a Python dash app and then manipulate the original data without altering the stored copy.
# Libraries
import pandas as pd
import dash
from dash import html, dash_table
import dash_core_components as dcc
from dash.dependencies import Input, Output
# Data
test_data = pd.DataFrame([[1,2],[3,4]], columns=['Col1', 'Col2'])
saved_test_data = None
# App
app = dash.Dash(__name__)
app.layout = html.Div(children=[dash_table.DataTable(columns = [{"name": i, "id": i} for i in test_data.columns],
data = test_data.to_dict('records'),
id = 'test_data_table',
editable=True,
),
html.Button('Save data', id='save_test_data', n_clicks=0),
dcc.Store(id = 'test_data_store'),
dash_table.DataTable(id = 'check_table', data=saved_test_data),
],
style={'width': '50%', 'display': 'inline-block', 'padding-left':'25%', 'padding-right':'25%'}
)
# Callbacks
#app.callback(Output('test_data_store', 'data'),
[Input('save_test_data', 'n_clicks'), Input('test_data_table', 'data')])
def save_test_data(n, data):
if n == 0:
saved_test_data = None
else:
saved_test_data = data
return saved_test_data
#app.callback(Output('check_table', 'data'),
Input('test_data_store', 'data'))
def restore_saved_test_data(data):
return data
if __name__ == '__main__':
app.run_server(debug=True, use_reloader=False)
If I change a value in the upper table after pressing the button, the lower table also changes, but it should display the value saved before the click, i.e.
Original Table
Press the button to save the table.
Stored table should be displayed below
Changing the upper table should not change the lower table before hitting the button again, but it changes immediately.
There are two problems. First, you have the original table as an Input, which triggers the callback every time it changes. This should be a State instead,
State('test_data_table', 'data')
Second, and maybe not so important if you make the above change, is this condition, if n == 0:. This will always be False after one click of the button. From there, every time the table updates.

Deleting/Hiding a column of a table in PySimpleGui

I got a table in PySimpleGui (sg.Table) and I want to add the option to hide/delete a column of that table. I found a way to do this with the data of the table, but it seems you can't edit the headings of a table on the fly. At least the .update() method doesn't provide one.
My first thought: Creating a new window with new data, without that column. But that seems like a laborious way of doing things...
Is there any clever way to do it?
You can configure ttk.Treeview (sg.Table) with option displaycolumns to select which columns are actually displayed and determines the order of their presentation. Add one more 'Row' before your displaycolumns list if you specified display_row_numbers=True.
from copy import deepcopy
import PySimpleGUI as sg
sg.theme("DarkBlue3")
newlist = [
[f"Cell ({row:0>2d}, {col:0>2d})" for col in range(8)]
for row in range(10)
]
COL_HEADINGS = ["Date", "Ref", "ID", "Owner", "Customer", "Price", "Size", "Path"]
layout = [
[sg.Table(
values=newlist,
headings=COL_HEADINGS,
max_col_width=25,
num_rows=10,
alternating_row_color='green',
display_row_numbers=True,
key='-TABLE-',
enable_click_events=True,
justification='center',
)],
[sg.Button('Hide Customer')],
]
window = sg.Window('Table', layout, finalize=True)
table = window['-TABLE-']
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'Hide Customer':
window[event].update(disabled=True)
displaycolumns = deepcopy(COL_HEADINGS)
displaycolumns.remove('Customer')
table.ColumnsToDisplay = displaycolumns
table.Widget.configure(displaycolumns=['Row']+displaycolumns)
window.close()

Updating video source on graph click in Dash Python

I have a html.video element in my app layout, along with a graph, when I click on an element of the graph I want to update the video element, to a video corresponding that section of the dataset.
The below is how I have my app layout set up.
app.layout = html.Div([
html.H1("Belfast Harbor Data Overview", style ={'text-align': 'center'}),
#Droptown for machine type
dcc.Dropdown(id="slct_machine",
options=[{'label': k, 'value': k} for k in all_options.keys()],
multi=False,
value=options1[0],
style={'width': "70%"}
),
#Dropdown for date
dcc.Dropdown(id="selct_date",
style={'width': "70%"},
),
#HTML Break
html.Br(),
dcc.Graph(id='hist', figure={}, style={'width': "90%"},
config={
'staticPlot': False, # True, False
'scrollZoom': True, # True, False
'doubleClick': 'reset', # 'reset', 'autosize' or 'reset+autosize', False
'showTips': False, # True, False
'displayModeBar': True, # True, False, 'hover'
'watermark': True,
# 'modeBarButtonsToRemove': ['pan2d','select2d'],
},),
html.Video(
controls=True,
id='video_player',
src={},
autoPlay=False,
style={'width': "70%"}
),
After this, I have some callback functions for my graph and a function to populate the graph with data.
Following this, I have another app call back and here I am trying to update the video player with some test data. I have done this as follows:
#app.callback (
Output('video_player', 'src'),
[Input('hist', 'clickData')]
)
def updateVideo(clk_data, src):
if clk_data is None:
video_src = "https://www.w3schools.com/html/mov_bbb.mp4"
else:
print(f'hover data: {clk_data}')
video_src = "http://techslides.com/demos/sample-videos/small.webm"
return video_src
I get the following callback error when I do this:
I am not really sure why the src is not updating, as passing components seems to work like this for figures and containers.
There are other elements to the app as well but I feel they aren't consequential as the video source is the part that is not updating.
I can provide all the code if required, but I feel I am simply updating the video source incorrectly.
The number of arguments in your function definition (currently two) must match the number of inputs (+ number of states) to your callback (currently one). Hence your should remove the (unused) src argument, or add another input or state.

Hooking up dcc.Slider to a clientside_callback in Dash for animated graph

I want to have a basic Dash app with an animated graph that you can Play/Pause, and change the time it displays with a dcc.Slider. I also need to have the current frame that the animation is exposed available so that I can also display additional graphs/datatables linked to the data currently being displayed, so I can't just use a Plotly graph.
My data is large enough (think, ~3 columns, 50k rows, with ~60 points displayed at a time) that using server-side callbacks is very slow. So, instead I stream chunks of the data into a dcc.Store every n intervals, use another dcc.Store to keep track of the current animation frame, and then use a clientside callback to update the graph.
At the moment, I have the slider value set up to mirror the stored frame, so that it automatically updates. However, I'm having trouble figuring out a way to let the user move the value of the slider, and have the graph update accordingly. Since the dcc.Store with the frame is being updated in the clientside callback, it can't be updated elsewhere. This is a simple version of what the code looks like now:
# -*- coding: utf-8 -*-
# Run this app with `python app.py` and
# visit http://127.0.0.1:8050/ in your web browser.
import pandas as pd
import numpy as np
import dash
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import dash_table # DataTable library
import plotly.express as px
import plotly.graph_objects as go
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SLATE])
df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
columns=['x', 'y', 'time'])
# Initial trace/figure
trace = go.Scatter(x=df.x[:1], y=df.y[:1],
name='Location',
mode='markers'
)
fig = go.Figure(data=[trace])
app.layout = html.Div([
# Animated graph
dcc.Graph(
id="graph",
figure=fig.update_layout(
template='plotly_dark',
paper_bgcolor= 'rgba(0, 0, 0, 0)',
),
),
# Slider to control
dcc.Slider(
id="slider",
min=0,
max=len(df),
value=0
),
# Buttons
dbc.ButtonGroup([
# Play
dbc.Button(
"Play",
color='success',
id="dashPlay",
n_clicks=0
),
# Pause
dbc.Button(
"Pause",
color='danger',
id="dashPause",
n_clicks=0
),],
size="lg"),
# Datatable
dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns], # Don't display seconds
data=df.to_dict('records')
),
# Storing clientside data
dcc.Store(
id='store',
data=dict(x=df.x, y=df.y) # Store figure data
),
dcc.Store(
id='frame',
data=0 # Store the current frame
),
# Client-side animation interval
dcc.Interval(
id="animateInterval",
interval=2000,
n_intervals=0,
disabled=True
),
])
# Update the animation client-side
app.clientside_callback(
"""
function (n_intervals, data, frame) {
frame = frame % data.x.length;
const end = Math.min((frame + 1), data.x.length);
return [[{x: [data.x.slice(frame, end)], y: [data.y.slice(frame, end)]}, [0], 1], end, end]
}
""",
[Output('graph', 'extendData'), Output('frame', 'data'), Output('slider', 'value')],
[Input('animateInterval', 'n_intervals')], [State('store', 'data'), State('frame', 'data')]
)
# Handles updating the data tables
# --------------------------------
# Updates the currently displayed info to show the
# current second's data.
# https://community.plotly.com/t/update-a-dash-datatable-with-callbacks/21382/2
#app.callback(
Output("table", "data"),
Input("frame", "data"),
)
def updateTable(frame):
# Once the animation has started...
if frame:
return (df.loc[df['time'] == frame]).to_dict('records')
else:
return (df.loc[df['time'] == 0]).to_dict('records')
# Handles pause/play
# ------------------
# Starts/pauses the 'interval' component, which starts/pauses
# the animation.
#app.callback(
Output("animateInterval","disabled"),
Input("dashPlay", "n_clicks"),
Input("dashPause", "n_clicks"),
State("animateInterval","disabled"),
)
def pause_play(play_btn, pause_btn, disabled):
# Check which button was pressed
ctx = dash.callback_context
if not ctx.triggered:
return True
else:
button = ctx.triggered[0]['prop_id']
if 'dashPlay' in button:
return False
else:
return True
if __name__ == '__main__':
app.run_server(debug=True)
As you can likely see, as of now there isn't a way to change the value of the slider and have it change the frame of the animation.
My first thought was simply adding a context check in the clientside callback, and try and account for the slider changing there, but I couldn't figure out how to do that with clientside_callback.
The only thing I can think of is having a second dcc.Store with the current frame, so there's Store A and Store B. Then I'd have two callbacks, the clientside callback to update the animation, and another to read any changes in the slider values. Then, the structure would be:
Clientside Callback:
In: Store A
Out: Store B
Slider Callback:
In: Store B
Out: Store A
Then if the slider value changed (i.e., the user moved the slider), that would be reflected in Store A, and would update in the animation. Similarly, the slider would be updated by the clientside callback and move to an appropriate value. However, this seems to re-introduce waiting on the server into the animation for the graph, and it feels like this must be a solved problem with a better solution. I'd love any advice on the topic!
What you can do is catch any user intervention when they play with the slider. Then you set frame to the value retrieved from the slider.
In your case, this can be something like:
app.clientside_callback(
"""
function (n_intervals,slider_value, data, frame) {
const triggered = dash_clientside.callback_context.triggered.map(t => t.prop_id);
frame = frame % data.x.length;
if (triggered == "slider.value") {
frame = slider_value;
}
const end = Math.min((frame + 1), data.x.length);
return [[{x: [data.x.slice(frame, end)], y: [data.y.slice(frame, end)]}, [0], 1], end, end]
}
""",
[Output('graph', 'extendData'), Output('frame', 'data'), Output('slider', 'value')],
[Input('animateInterval', 'n_intervals'),Input('slider', 'value')], [State('store', 'data'), State('frame', 'data')])

How to update chained dropdown value in Dash dcc?

In dash, how do I update the values of one dropdown into another checklist or slider?
In the below code, I am selecting one value from a dropdown which should update checklist values based on the selected value from the dropdown. Here I am partially successful in taking value from the dropdown but it's accumulating with older selected values in the checklist.
Please find below part of the 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
import pandas as pd
from dash.exceptions import PreventUpdate
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/solar.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.config['suppress_callback_exceptions'] = True
app.layout = html.Div([
dash_table.DataTable(
id='datatable-upload-container',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict('records'),
),
html.Div(dcc.Dropdown(
id='data_selector1',
options=[
{'label': '', 'value': ''}
],
value=[]
)
),
html.Br(),
html.Div([
html.Div(id='numerical-slider'),
# this is a hack: include a hidden dcc component so that
# dash registers and serve's this component's JS and CSS
# libraries
dcc.Input(style={'display': 'none'})
])
])
#app.callback(Output('data_selector1', 'options'),
[Input('datatable-upload-container', 'data')])
def update_dropdown(rows):
print('updating menus')
numerical_col = [i for i in df.columns if df[i].dtypes != "object"]
col_labels=[{'label' :k, 'value' :k} for k in numerical_col]
return col_labels
#app.callback(Output('numerical-slider','children'),
[Input('data_selector1', 'value'),
Input('datatable-upload-container', 'data')])
def explanatory_cat_slider(value, rows):
if value:
categories, intervals = pd.cut(df[value], 3, retbins=True)
return html.Div(html.Label([value,
dcc.RangeSlider(id='numerical-slider',
min=min(intervals),
max=max(intervals),
step=None,
marks={str(val): str(round(val,2)) for val in intervals},
value = [intervals[0],intervals[-1]]
)
],style={'width': '50%', 'display': 'inline-block', 'textAlign': 'left'})
)
else:
return []
if __name__ == '__main__':
app.run_server(debug=False)
updated code...
I am getting an issue with explanatory_cat_slider, it's not getting updated with new selected values.
In the first image I can select one value of dropdown which automatically shows slider of that value
In the second image sliders getting accumulated on upon other. How do I rectify this issue?
In the last image, how it becomes slider overlapped
The code you posted is incomplete, because you have the Input ID data_selector and the ID datatable-upload-container in your callback, but they do not exist in your layout. I can see that the callback will have a problem with its Output as well, because it's trying to update the children of categorical-checklist with an element that contains an ID of the same. This would result in non-unique IDs in your layout, which will break.
I may be able to help more with the complete code but, essentially, you have the right idea to check if value: and run your code inside that block. Make sure you do not let the callback silently end, because that will return None and cause trouble for you. You should include a return [] or return '' at the end, or inside an else: block to protect against that, and keep the children component valid, even if only with an empty value.
You should also include the dcc.Checklist in your initial layout. Give it options=[] to start with and have your callback update its options prop. That should work but, if there are still problems, update your posted code and I'll take another look.

Categories

Resources