MehdiChelh / dash-draggable

Dash components for building draggable and resizable grid layouts. Based on the well-known STRML/react-grid-layout.
MIT License
56 stars 12 forks source link

Get position of elements in callback #4

Open europa502 opened 2 years ago

europa502 commented 2 years ago

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.

MehdiChelh commented 2 years ago

Hi ! Thank you for the support ❤️ !

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

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')
europa502 commented 2 years ago

@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 commented 2 years ago

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