Show and Tell - Dash Leaflet

Thanks Emil,

I want to make use of a clientside callback in order to open a new tab.
I use the following callback:

dash_app.clientside_callback(
    """
    function(feature) {
           ...
    }
    """,
    Output(component_id='hidden_div', component_property= 'children'),
    Input(component_id="geojson", component_property="click_feature"),
    prevent_initial_call=True
)

However, when I start the Dash app, even though I set prevent_initial_call=True, the clientside callback is fired
and returns the error
TypeError: Cannot read properties of undefined (reading ā€˜childrenā€™)
Because the click_feature is still None, since nothing was clicked yet.

Is there a way to avoid the clientside callback from initial firing?

Thanks :slight_smile:

I canā€™t say without seeing the app code, but my guess is that the callback fires due to dynamic layout generation (then the prevent_initial_call flag doesnā€™t work). I believe the solution in this case would be to implement a guard in the JS code that aborts the callback if the feature is none.

I tried defining the function in the .js file in my assets/ folder as indicated in the docs of clientside callbacks and after fixing some JS bugs, it works perfectly smooth.

So, for those of you who are trying to open a new tab when clicking on the Leaflet marker, I wrote the following clientside callback:

dash_app.clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='open_listing_url'
    ),
    Output(component_id='hidden_div', component_property= 'children'),
    Input(component_id="geojson", component_property="click_feature"),
    prevent_initial_call=True
)

And add in the .js file in the /assets folder the following function:

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        open_listing_url: function(feature) {
            if (feature !== undefined){
                // Store in an array the keys of the dict feature.properties
                const keys_of_properties = Object.keys(feature.properties);
                if (keys_of_properties.includes('listing_url')) {
                    window.open(feature.properties['listing_url']);
                    return 'feature with properties!';
                } else {
                    return 'feature without props';
                }
            } else{
                return 'feature is  undefined :(';
            }
        }
    }
});

Cheers!

1 Like

So I am trying to update the position of a couple of minicharts on my map based on the n_intervals. Now if I uncheck the box which is in viewState and then check it again the chart moves location. Otherwise it stays in the same location. The data is changing inside the pie graph but not moving along side of the icon it represents.

@app.callback(
    Output('autonomyState', 'children'),
    Input("time", "value"), 
    Input('viewState', 'value')
)
def show_autonomy(tick, viewState):
    
    autonomyList = []
    
    
    if 'autonomy_on' in viewState:
        # need to find the lat/long based on each RCV vehicle position
        for veh in list_Vehicles:
            if "RCV02" in veh.vname:
                location = veh.find_auto_pie_location(tick)
                autonomyList.append(dl.Minichart(location[0], location[1], type="pie", id="pie", colors=color_array))
            if "RCV04" in veh.vname:
                location = veh.find_auto_pie_location(tick)
                autonomyList.append(dl.Minichart(location[0], location[1], type="pie", id="pie2", colors=color_array))

    return autonomyList

Now I have the app.clientside_callback

app.clientside_callback(
    """
    function(n_intervals, value){
        const fakeData = () => [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()]
        return [fakeData(), fakeData()]
    }
    """, 
    [Output("pie2", "data")], 
    [Output("pie", "data")], 
    Input("trigger", "n_intervals"),
    Input("time", "value"))

Hello, I am trying to delay the data loading of the map after the start of a pan/zoom event.
I would like to delay for some time the triggering of the callback while it keeps receiving the zoom and center values.
After this time, it should read the state of those and load the data.

I know that I could use those component properties as State for my callback, but I need also them to trigger this kind of ā€˜timerā€™.

Would it be possible to do such a thing?
Thanks for your time :slight_smile:

I guess it should be possible to do in Dash, but I think youā€™ll need to use client side callbacks to achieve an acceptable performance for this kind of use case. It might be easier/more elegant to implement the behaviour in the React layer. If you choose to do so, I will be happy to look at a PR :slight_smile:

Hi Emil,
I am sorry but I have very little experience with JS.
Do you mean to implement a JS function in the .js script of the assets, that somehow reads the Leaflet ā€™ dragstartā€™ event and counts the time?
Thanks.

Yes, something like; record viewport and current time (e.g. in a store); compare viewport values with stored values; if they are the same, compare time stamps; if enough time has passed, invoke callback; otherwise just record pan/zoom values and current time again. So, some bookkeeping, but nothing complex.

1 Like

I will try it.
Thanks Emil!

If the underlying issue (i.e. the reason you would like to delay the callback) is that the callback takes too long, you might want to look at the blocking callback feature of dash-extensions,

1 Like

Hi, I have a problem and would really appreciate it if someone could help.
I have two tabs on my dashboard, and a map on one of the tabs. If I draw something on the map using edit control and switch tabs, the shape would be gone when I go back to the first tab. How could I prevent this?

I would also like to know if it is possible to implement a callback, so that when drawing a shape on a map, the previously drawn shape is deleted, so that there could only be one shape on the map at a time.

Thanks in advance!

Are you re-rendering the map when you change between tabs? Maybe you could add an MWE demonstrating the issue. You should be able to delete shapes via the editToolbar prop.

Sorry I just started using dash so iā€™m not sure what you mean by re-rendering. Here is a MWE:

import dash
from dash import Dash, dcc, html
import dash_leaflet as dl

app = Dash(__name__)
app.layout = html.Div([
    dcc.Tabs(id="tabs", children=[
        dcc.Tab(value="a", label="a", children=[]),
        dcc.Tab(value="b", label="b", children=[
            dl.Map(center=[56, 10], children=[
                dl.TileLayer(), dl.FeatureGroup([
                    dl.EditControl(id="edit_control")]),
            ], style={'width': '50%', 'height': '50vh'}, id="map"),
        ])
    ])
])

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

Hi, Basically looking for some pointers, as I currently donā€™t really have an idea how to approach my problem.
Iā€™m building something in which there is datatable on the left, and a leaflet map on the right.

The map contains a couple of layers, one with markers.
More info on the markers is shown on the left, in the table.

I would like to make something that when a row in the datatable is clicked, the corresponding marker changes color.

I found some stuff on dl.GeoJson where they seem to use the hideout argument to do something like this, in combination with a cliensite function changing the color, and an onclick trigger.

Should this also work for my issue? (changing the input to a datatable on click?) Can a single marker in a layer be changed of color, or does the whole layer need to be regenerated?

Like I said, some simple pointers, where to dig into are fine.

I have implemented this kind of flow a few times. I have used two design variants,

  • A single GeoJSON layer where selected point(s), as defined via the hideout prop, are rendered differently using custom pointToLayer function
  • Two GeoJSON layers. One static with all points. The other on top showing only selected points. as defined via the hideout prop, with the interactive filtering achieved a custom filter function

Besides defining the rendering logic (as outline above), youā€™ll also need to

  • Ensure each element has a unique identifier, which is present in the markers properties, and the table data
  • On table click attach a callback that sets the hideout prop to the unique id of the row clicked (this is what will update the GeoJSON layer(s))

Probably a dumb question, but Iā€™m starting with a dl.LayerGroup having a list of markers.
Does that make sense?

The hideout prop is specific for geoJSON? Or should I build the markers using a GeoJson setup?

Would you happen to have some code examples?

Is this one a good one to look at?

It depends on the number of markers. If there are not too many (say, < 100), a list of markers is fine. If you have many markers, the GeoJSON layer is the better choice.

If you use a list of markers, the same principles apply, but itā€™s a bit simpler, as you can just target the selected marker directly, when you want to change the appearance. Hence you donā€™t need to write any js yourself.

I would recommend looking at the example in the docs,

http://dash-leaflet.herokuapp.com/

Okay thanks for the feedback.
This example? " Interactivity via the hideout prop" ?

I honestly donā€™t see how I should implement that?

app.clientside_callback("function(x){return x;}", Output("geojson", "hideout"), Input("dd", "value"))

This function above needs to be changed to attack a specific id then? (being the id of the marker?)
Is that correct?

Iā€™m still struggling to see the bigger picture.
I have a callback to retrieves a possible id of a marker from a clicked dash datatable. How to proceed?
I probably need to make that one as well client-side? and not serverside, just as the marker update?

Friends I am working on a map made with Dash-Leaflet but it turns out that I am capturing data with
EditControl from the frontend, I capture that data from the callback, I store that information in the browserā€™s memory and pass it to a function to later be able to validate if within those polygons there is any point that I have in another list, I am investigating the documentation and I canā€™t find how to perform that validation, I need help.

I have an example in geopandas that works, but geopandas is very heavy and the server is frozen, I hope you can help me find the best way so that the code does not saturate the server.


pdv_geo = gpd.GeoDataFrame(pdv, geometry=gpd.points_from_xy(pdv.Longitud, pdv.Latitud))

cities = pdv_geo.sjoin(barrios, how="left", predicate='intersects')

resuelt = cities.drop(columns=["GLOBALID", "Shape_Length", "Shape_Area", "SCATIPO", "index_right"])

resuelt.to_excel("dataset/datos_unificados_completos.xlsx", index=False, sheet_name="pdv")

Any help on this one?
Iā€™ve extracted the active cell from datatable, and this returns a dict, with a row_id as one of itā€™s keys.

But I fail to see how I can link that to an output?
I think I would need something like:

@app.callback(
    Input({'type': ??Active_cell??, 'row_id': MATCH}, 'id'),
    Output({'type': "marker", 'index': MATCH}, 'icon'}
)
def function_test(in):
    return ({"iconUrl": "assets/score.jpg", "iconSize": [25, 41]})

But I have now idea how to actually implement the input and output to work together.