dash-draggable icon indicating copy to clipboard operation
dash-draggable copied to clipboard

Get position of elements in callback

Open europa502 opened this issue 2 years ago • 3 comments

Is it possible to access the position of elements added in dash_draggable.GridLayout() from a callback? I tried accessing the children but I guess the array is not sorted according to position of defined elements when returning in callback?

I know 'Drag and drop items in list (trello-like)' is in your action items and most probably the list will be sorted based on the position of the elements in the childern, but I can't wait till that feature is implemented in the next release.😬

BTW, awesome work! Love it.

europa502 avatar Oct 11 '21 14:10 europa502

Hi ! Thank you for the support ❤️ !

Indeed you can use callbacks to get the position of elements on the dashboard!

  • For GridLayout you can access it through the layout property
  • For ResponsiveGridLayout you can access it through the current_layout and all_layouts properties (as the layout depends on the screen size).
@app.callback(
    Output('layout_callback', 'children'),
    Input('draggable', 'layout'))
def update_figure(layout):
    return(str(layout))

BTW thank you for your feedback 👍, I will update examples in the repository to make it more obvious how to use callbacks with GridLayout and ResponsiveGridLayout.

Below is a complete example with the callback displaying the layout on the page:

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

import plotly.express as px
import pandas as pd

import dash_draggable


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

server = app.server

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

app.layout = html.Div([
    html.H1("Dash Draggable"),
    html.Div(id='layout_callback'),
    dash_draggable.GridLayout(
        id='draggable',
        children=[
            html.Div(children=[
                dcc.Graph(
                    id='graph-with-slider',
                    responsive=True,
                    style={
                        "min-height":"0",
                        "flex-grow":"1"
                    }),
                dcc.Slider(
                    id='year-slider',
                    min=df['year'].min(),
                    max=df['year'].max(),
                    value=df['year'].min(),
                    marks={str(year): str(year) for year in df['year'].unique()},
                    step=None)
                ],
                style={
                    "height":'100%',
                    "width":'100%',
                    "display":"flex",
                    "flex-direction":"column",
                    "flex-grow":"0"
                }),
        ]
    ),
])





@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('year-slider', 'value'))
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]

    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=True, size_max=55)

    fig.update_layout(transition_duration=500)

    return fig


@app.callback(
    Output('layout_callback', 'children'),
    Input('draggable', 'layout'))
def display_layout(layout):
    return(str(layout))


if __name__ == '__main__':
    app.run_server(debug=True, port='5080')

MehdiChelh avatar Oct 14 '21 06:10 MehdiChelh

@MehdiChelh ,

Thanks a lot for helping me out with the solution. The example you provided would be enough if we were to track the position of only one element in the the layout, but if we were to track the position of more than one element within the layout, I think it'd become difficult, since the list of dict of layout properties don't have ant identifiable fields, like element id.

For Eg.

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

import plotly.express as px
import pandas as pd

import dash_draggable


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

server = app.server

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

app.layout = html.Div([
    html.H1("Dash Draggable"),
    html.Div(id='layout_callback'),
    dash_draggable.GridLayout(
        id='draggable',
        children=[
            html.Div(children=[
                dcc.Graph(
                    id='graph-with-slider',
                    responsive=True,
                    style={
                        "min-height":"0",
                        "flex-grow":"1"
                    }),
                dcc.Slider(
                    id='year-slider',
                    min=df['year'].min(),
                    max=df['year'].max(),
                    value=df['year'].min(),
                    marks={str(year): str(year) for year in df['year'].unique()},
                    step=None)
                ],
                style={
                    "height":'100%',
                    "width":'100%',
                    "display":"flex",
                    "flex-direction":"column",
                    "flex-grow":"0"
                }),
            html.Div(children=[
                dcc.Graph(
                    id='graph-with-slider1',
                    responsive=True,
                    style={
                        "min-height":"0",
                        "flex-grow":"1"
                    }),
                dcc.Slider(
                    id='year-slider1',
                    min=df['year'].min(),
                    max=df['year'].max(),
                    value=df['year'].min(),
                    marks={str(year): str(year) for year in df['year'].unique()},
                    step=None)
                ],
                style={
                    "height":'100%',
                    "width":'100%',
                    "display":"flex",
                    "flex-direction":"column",
                    "flex-grow":"0"
                }),
        ]
    ),
])





@app.callback(
    Output('graph-with-slider', 'figure'),
    Input('year-slider', 'value'))
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]

    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=True, size_max=55)

    fig.update_layout(transition_duration=500)

    return fig


@app.callback(
    Output('layout_callback', 'children'),
    Input('draggable', 'layout'))
def display_layout(layout):
    return(str(layout))


if __name__ == '__main__':
    app.run_server(debug=True, port='5080')

By running the code I'd get an output that looks like this - image

From the above string it is difficult to figure out which dict in the list represents which div (unless you know the initial position of the divs from the code). I guess it'd be really simple to just add the id prop to the layout dict?

Please let me know what you think about this.

Cheers, Abhijit

europa502 avatar Oct 21 '21 07:10 europa502

I just passed the id of div and I got it returned as 'i' in the layout dict. This should work I guess. Also can you replace 'i' with 'id' if its not meant for properties like 'index' or anything else?

Thanks, Abhijit

europa502 avatar Oct 21 '21 09:10 europa502