dash-table
dash-table copied to clipboard
Tooltip position is incorrect after sorting/filtering data table
Tooltips get messed up after sorting or filtering. Simple reproducible example below.
import dash
import dash_table
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/curran/data/gh-pages/Rdatasets/csv/MASS/Animals.csv')
df.columns = ["Animal", "body", "brain"]
def create_tooltip(cell):
num = len(cell)
if num > 7:
return(cell)
else:
return("")
tooltip_data = [{
column:
{'value': create_tooltip(df.loc[i, column]), 'type': 'markdown'} for column in df.columns if column == "Animal"} for i in range(len(df))]
app = dash.Dash(__name__)
app.layout = dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in df.columns],
data=df.to_dict('records'),
sort_action = "native",
filter_action = 'native',
style_header={'fontWeight': 'bold','border': 'thin lightgrey solid','backgroundColor': 'rgb(100, 100, 100)','color': 'white'},
style_cell={'fontFamily': 'Open Sans','textAlign': 'left','maxWidth': '0px','whiteSpace': 'no-wrap','overflow': 'hidden','textOverflow': 'ellipsis'},
style_table = {'overflowX':'scroll', 'overflowY':'scroll', 'maxHeight':'400px', 'maxWidth':'200px', 'marginLeft':'100px'},
tooltip_data=tooltip_data,
tooltip_delay=0,
tooltip_duration=None
)
if __name__ == '__main__':
app.run_server(debug=True)
Wanted to also add to this the fact that if you have pagination, then on pages not == 1 the tooltips are also misaligned.
Anyone have a work around to this? I'm seeing the same problem.
Forgot to mention that the problem also exists if the table has pagination and user navigates to any page other than 1
Hi, I haven't found a work around. But I did notice that in the DOM the position of the tooltips are being calculated based on the initial table state, even after filtering/sorting and likely after paginations. I think the fix would be to dynamically calculate the tooltip positions based on the current state of the table
@Dekermanjian - Thanks for the suggestion! You are right. Tooltip location is calculated initially, and I can't figure out how to get datatable/dash/plotly to recalculate.
My idea is that tooltip_data
might need to be recalculated on every table update. However, my idea only works for pagination and requires disabling of the cool sort/filter functionality :( The solution is probably to re-implement sorting/filtering with a callback/custom function but my approach would require quite a bit of custom code to replace default functionality.
Here's the code I tried in case I'm close or it sparks an idea for someone else:
# additional dependencies
from dash.dependencies import Input, Output
# table properties must be
# filter_action="custom",
# sort_action="custom",
# page_action="custom",
@app.callback(
[
Output('table', 'tooltip_data'),
Output('table', 'data')],
[
Input('table', 'page_current'),
Input('table', 'page_size')
])
def update_table(page_current,page_size):
ret_df = orig_df.iloc[page_current*page_size:(page_current+1)*page_size].to_dict(orient='records')
ret_tooltip = [{
column: {'value': str(value), 'type': 'markdown'}
for column, value in row.items()
} for row in ret_df]
return ret_tooltip, ret_df
I'll play around with this a little more - but I'd really like a baked in solution. Hopefully the plotly dev folks will notice this issue and take pity on us!
Since my previous comment, I got something working, but am not very happy with the result. I've found that if I follow the section [Backend Paging with Filtering and Multi-Column Sorting](https://dash.plotly.com/datatable/callbacks#backend-paging-with-filtering-and-multi-column-sorting one can re-implement) all the client side code I care about can be replicated with about 50 or so lines of Python code. By dynamically generating the view of the dataframe and associated tooltips on each user interaction, the tooltips always work.
As an alternative, I investigated using dash-bootstrap-components but found that solution to be quite a mess as well - probably due to my inexperience with Plotly Dash and React.
It seems that DataTable cells do not have an id
which is how dash-bootstrap-components attach their tooltips. I couldn't figure out how to add it via Python or any native Plotly Dash api. Additionally, since dash_bootstrap_components.Tooltip
requires an id
when added to the layout, it cannot be present in the initial app.layout()
Through a combination of client side callbacks I found I can add id
attributes where I want, then I can dynamically add the dash_bootstrap_components.Tooltip
As this was just testing, I have a button to start everything, plus my table - something like this:
app.layout = html.Div(children=[
html.Button('Click Me', id='submit-btn', n_clicks=0, className='start'),
dash_table.DataTable(
id='my-table'
# more stuff here
),
html.Div(id="layout")
])
When the button is clicked, some client side code adds the desired id
app.clientside_callback(
ClientsideFunction(
namespace='clientside',
function_name='myfunc'
),
Output('submit-btn', 'className'),
Input('submit-btn', 'n_clicks')
)
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
myfunc: function () {
elem = document.getElementById('my-table')
if (elem) {
child = elem.getElementsByClassName('dash-cell column-0')
child[0].id = 'cell1'
}
}
}
});
Then I have another callback that adds the Tooltip:
@app.callback(
Output('layout', 'children'),
[Input('submit-btn', 'n_clicks')],
[State('layout', 'children')],
)
def add_strategy_divison(val, children):
if val:
el = dbc.Tooltip(
f"Tooltip on cell",
id='cell1',
target='cell1'
)
return el
else:
raise PreventUpdate
I'm sure these callbacks could be combined into one.
I've noticed issue #736 describes a related problem. Perhaps @Marc-Andre-Rivet has some better insight on how to make DataTable tooltips work with "native" functionality?
Is there any new update/fix on this issue?
Anything on this?
The issue is still there after three years. Anything in the planning about this?