Dash-draggable and dash-extentions not playing nicely together

I am trying to use the dash-draggable library (instead of rc-dock which is giving me problems) in my application. The content inside each draggable box is dynamic and can be rather large (plot with many data points). Now instead of passing the children down to the dash-draggable to append a new child onto it (can be 10s of MB of data being passed back and forth) I want to make use of dash-extensions OperatorTransform with OutputOperator to append to the list.

The example below shows that this is not working. If I use regular callbacks the layout object works well and i can set default width of 12 columns, but when i move to OutputOperator it does not seem to work. Below is working example modified from the dash-draggable library. @Emil do you have any ideas/suggestions why dash-extensions causes this issue?

import dash
from dash import dcc, Input, Output, State, html, ctx
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import DashProxy, OperatorOutput, Operator, OperatorTransform
import dash_draggable


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


app = DashProxy(__name__,
    transforms=[OperatorTransform()], external_stylesheets=external_stylesheets)


@app.callback(
    Output("draggable", "layouts"),
    Output("draggable", "children"),
    # OperatorOutput("draggable", "children"),
    Input("graph", "n_clicks"),
    Input("slider", "n_clicks"),
    State("draggable", "children"),
    State("draggable", "layouts"),

)
def ddd (graph, slider, children, layouts):
    layouts = layouts or {"lg": []}
    children = children or []
    if ctx.triggered_id is None:
        raise PreventUpdate

    if ctx.triggered_id == "graph":
        id=f'graph-with-slider-{graph}'
        new_item = dcc.Graph(
                id=id,
                responsive=True,
                style={
                    "width":"100%",
                    "height":"100%",

                })
        layouts["lg"].append(dict(w=12, h=10, x=0, y=0, i=id))


    if ctx.triggered_id == "slider":
        id=f'year-slider-{slider}'
        years = [2000 + r for r in range(0, 10)]
        new_item = dcc.Slider(
                id=id,
                min=min(years),
                max=max(years),
                value=min(years),
                marks={str(year): str(year) for year in years},
                step=None)
        layouts["lg"].append(dict(w=12, h=2, x=0, y=0,i=id))
    children.append(new_item)
    # return layouts,   Operator().list.append(new_item)
    return layouts,   children


app.layout = html.Div([
    html.Button("graph", id="graph"),
    html.Button("slider", id="slider"),
    dash_draggable.ResponsiveGridLayout(
        id='draggable',
        clearSavedLayout=True,

        children=[ ]
    ),
])



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

Could you elaborate on the issue (I am not sure what you mean by “not working”)? I don’t see any immediate issues on my system.

from dash import dcc, Input, Output, State, html, ctx
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import DashProxy, OperatorOutput, Operator, OperatorTransform
import dash_draggable

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

app = DashProxy(__name__,
                transforms=[OperatorTransform()], external_stylesheets=external_stylesheets)


@app.callback(
    Output("draggable", "layouts"),
    OperatorOutput("draggable", "children"),
    Input("graph", "n_clicks"),
    Input("slider", "n_clicks"),
    State("draggable", "layouts"),

)
def ddd(graph, slider, layouts):
    layouts = layouts or {"lg": []}
    if ctx.triggered_id == "graph":
        id = f'graph-with-slider-{graph}'
        new_item = dcc.Graph(
            id=id,
            responsive=True,
            style={
                "width": "100%",
                "height": "100%",

            })
        layouts["lg"].append(dict(w=12, h=10, x=0, y=0, i=id))
        return layouts, Operator().list.append(new_item)
    if ctx.triggered_id == "slider":
        id = f'year-slider-{slider}'
        years = [2000 + r for r in range(0, 10)]
        new_item = dcc.Slider(
            id=id,
            min=min(years),
            max=max(years),
            value=min(years),
            marks={str(year): str(year) for year in years},
            step=None)
        layouts["lg"].append(dict(w=12, h=2, x=0, y=0, i=id))
        return layouts, Operator().list.append(new_item)
    raise PreventUpdate


app.layout = html.Div([
    html.Button("graph", id="graph"),
    html.Button("slider", id="slider"),
    dash_draggable.ResponsiveGridLayout(
        id='draggable',
        clearSavedLayout=True,
        children=[]
    ),
])

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

Thanks for the reply.

Try this code below that uses regular callbacks, click on the slider and then on the graph, the slider is set to have a width of 6 (out of the 12 default columns) and height of 2 rows, and the graph has width of 12 (100%) and a height of 10 rows.

With the operators, it was just creating a small item and not going according to the layout definition passed

import dash
from dash import dcc, Input, Output, State, html, ctx
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import DashProxy, OperatorOutput, Operator, OperatorTransform
import dash_draggable


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


app = DashProxy(__name__,
    transforms=[OperatorTransform()], external_stylesheets=external_stylesheets)


@app.callback(
    Output("draggable", "layouts"),
    Output("draggable", "children"),
    # OperatorOutput("draggable", "children"),
    Input("graph", "n_clicks"),
    Input("slider", "n_clicks"),
    State("draggable", "children"),
    State("draggable", "layouts"),

)
def ddd (graph, slider, children, layouts):
    layouts = layouts or {"lg": []}
    children = children or []
    if ctx.triggered_id is None:
        raise PreventUpdate

    if ctx.triggered_id == "graph":
        id=f'graph-with-slider-{graph}'
        new_item = dcc.Graph(
                id=id,
                responsive=True,
                style={
                    "width":"100%",
                    "height":"100%",

                })
        layouts["lg"].append(dict(w=12, h=10, x=0, y=0, i=id))


    if ctx.triggered_id == "slider":
        id=f'year-slider-{slider}'
        years = [2000 + r for r in range(0, 10)]
        new_item = dcc.Slider(
                id=id,
                min=min(years),
                max=max(years),
                value=min(years),
                marks={str(year): str(year) for year in years},
                step=None)
        layouts["lg"].append(dict(w=6, h=2, x=0, y=0,i=id))
    children.append(new_item)
    # return layouts,   Operator().list.append(new_item)
    return layouts, children


app.layout = html.Div([
    html.Button("graph", id="graph"),
    html.Button("slider", id="slider"),
    dash_draggable.ResponsiveGridLayout(
        id='draggable',
        clearSavedLayout=True,

        children=[ ]
    ),
])



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

Ah, yes, I didn’t notice that. It seems that dash-draggable only renders the provided layout, if the children and layout props are set simultaneously (i.e. as part of the same callback). This doesn’t happen when using the OperatorTransform, and the layout is thus not applied

any idea how i can get around this?

I think the best approach would be to update dash-draggable to support updating the properties separately. Another approach would be to implement the clientside update manually, i.e.

import dash_draggable
from dash import dcc, Input, Output, State, html
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import DashProxy, MultiplexerTransform

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = DashProxy(__name__, transforms=[MultiplexerTransform()], external_stylesheets=external_stylesheets)
app.layout = html.Div([
    html.Button("graph", id="graph"),
    html.Button("slider", id="slider"),
    dcc.Store(id="relay"),
    dash_draggable.ResponsiveGridLayout(
        id='draggable',
        clearSavedLayout=True,
        children=[],
        layouts={"lg": []}
    ),
])


@app.callback(
    Output("relay", "data"),
    Input("slider", "n_clicks"),
)
def add_slider(n_click):
    if n_click is None:
        raise PreventUpdate
    uid = f'year-slider-{n_click}'
    years = [2000 + r for r in range(0, 10)]
    new_item = dcc.Slider(
        id=uid,
        min=min(years),
        max=max(years),
        value=min(years),
        marks={str(year): str(year) for year in years},
        step=None)
    return dict(layout=dict(w=12, h=2, x=0, y=0, i=uid), item=new_item)


@app.callback(
    Output("relay", "data"),
    Input("graph", "n_clicks"),
)
def add_graph(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    uid = f'graph-with-slider-{n_clicks}'
    new_item = dcc.Graph(
        id=uid,
        responsive=True,
        style={
            "width": "100%",
            "height": "100%",

        })
    return dict(layout=dict(w=12, h=10, x=0, y=0, i=uid), item=new_item)


# Do the update client-side.
func = """
function(data, layouts, children){
    layouts.lg.push(data.layout);
    children.push(data.item);
    return [layouts, children];
}
"""
app.clientside_callback(func,
                        Output("draggable", "layouts"), Output("draggable", "children"),
                        Input("relay", "data"),
                        State("draggable", "layouts"), State("draggable", "children"),
                        prevent_initial_call=True)

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

I like your solution (easier than having to try submit a PR to the dash-draggable library).
I will try to implement it and update with results

If you can’t get draggable to work, you could take a look at dragula as well.

Don’t know if you’d encounter the same rendering issue or not.

my initial code uses dragula, but it doesnt support component resizing and re-organising in a grid like system

Hmm, I guess I don’t encounter this issue because I use percentage sizes, which auto adjusts based upon outside variables.

I don’t use draggable, or dragula, but I resize and move charts within a designated area and it maintains its scale, gets bigger and smaller as necessary.

The grid layout, I understand that issue.