📣 Initial release of Dash Deck, a library for rendering webgl & 3D maps with pydeck and deck.gl in Dash

Awesome extension, so much potential.

Has anyone had success changing the basemap?

Assume the following data passed to deck

{
    "initialViewState": {
        "longitude": -122.45,
        "latitude": 37.8,
        "zoom": 6.6
    },
    "views": [
        {
            "@@type": "MapView",
            "mapStyle": "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json"
        }
    ],
    "layers": [
        {
            "@@type": "TextLayer",
            "getColor": [0,255,255],
            "data": [
                {
                    "position": [ -122.45, 37.8 ],
                    "text": "Hello World"
                }
            ]
        }
    ]
}

This example works fine on the deck playground ( https://deck.gl/playground/ ), however the same basemap swap doesn’t work in dash_deck. Any hints on how to change the basemap to a non-mapbox source?

I added extensive testing for mapbox styles but not other styles. Since the library is based on pydeck, I’d recommend checking out how they do it, and use the resultingpdkobject as input to the dash deck. You can check out the explorer for more examples involving pydeck.

Hello,

Thanks so much for this project, it’s really awesome!
I wonder if there’s a way to dynamically get the current ViewState as the user pans throughout the map. I’d like to load a fairly large amount of data and it would be nice to render it dynamically.

Thanks!

Has anyone gotten figure animation to work with pydeck/dash-deck as shown in the deck.gl trips layer example? There is probably some way to do it with dcc.interval and callbacks, but I imagine that would be slower than desirable. Maybe there some way to write some client-side js to run the animation loop?

@karosc I think that the simplest approach would really be dcc.Interval, and you are right that it will be slow once it’s hosted on a server. If you want to accomplish it on the clientside, one possible way would be a custom component using dash-component-boilerplate and reproduce that behavior by using the dash-deck npm package.

Another hypothetical way would be to use clientside callback to assign a loop that would call an animate function with a loop, which in this case might work since you would be able to access window.requestAnimationFrame. Moreover, implementing the setTime function using pure JavaScript might be less elegant without hooks (i.e. you will have to get the element by id and manually set its currentTime prop)

@nicholas-m Sorry for the late response. In the API reference you will find dragStartEventand dragEndEvent, which isn’t the same thing as the view state but might be able to give you enough information about where the user has moved (e.g. through deltas).

Thanks for getting back to me! That makes sense to me and I will look into doing a clientside callback. On another topic, I am having trouble positioning the deck map well. Like kforris above, I want my map to be located within a specific div in my document, but it will not follow the document flow because the deck objects come out in an absolute position. Is there any way for these deck maps to have relative positioning so I don’t need to worry about repositioning them if I add more content above?

You will need to wrap it inside a html div (container):

deck_container = html.Div(
    dash_deck.DeckGL(
        r.to_json(), id="deck-gl", tooltip=TOOLTIP_TEXT, mapboxKey=r.mapbox_key
    ),
    style={"height": "400px", "width": "100%", "position": "relative"},
)

See this demo I just added that uses dash-deck inside a bootstrap container:

That did the trick! Not sure why I couldn’t get it working before, but your example was exactly what I needed. Thanks so much!

@xhlu I am accessing the data attribute of my DeckGL object in a clientside callback and for some reason, the returned object is a stringified json rather than a js object which makes it slow to manipulate as I have to parse it then re-stringify it. Is there a reason why this data attribute is stored as a string rather than an object?

I think that’s how the clientside callback is designed, and I unfortunately do not have any insights on that; I think the dash-core team would know that better than I do.

If you are doing heavy conversion, maybe you could store your data object in the window instead, and use the clientside callback to access the index of the object stored in the window?

Thanks again for your help xhlu. Loving dash deck so far. One comment I have is that right clicking the map always generates a context menu, which I feel interferes with the 3D mapping experience. This could be remedied with some html/js a la

<div oncontextmenu="return false;"> in the deck-container parent div or

appending some js to the application a la
document.getElementById('deck_container').addEventListener('contextmenu', evt => evt.preventDefault());

The problem I am having is appending js to the app results in this js firing before the deck-container is loaded, thus the context menu is not disabled. I tried setting the div attrubute using the kwarg unpacking method **{'oncontextmenu':'return false;'}, but I guess that attribute is not yet supported by dash.

Thoughts on this?

1 Like

You could modify the source code of the component (see dash-deck/CONTRIBUTING.md) and disable it inside the rendering function. Another approach (which i’m not sure if it’d work) is to check inside a clientside callback if the div exists, and if it does to add the event listener. Obviously that callback would need to be triggered by some sort of callback, which in this case I guess could be a html.Button or a dcc.Interval. Finally it seems like html.Div has a contextmenu prop, but I’m unsure how it works exactly but it might be worth exploring.

Nice, I have a working solution. But calling black to format at the end has changed a bunch of formatting in the existing code that I did not change myself. Do you have the black version number you are working with?

Thanks, for anyone looking in the future, it is possible to pass a dict to dash_deck.DeckGL instead of a str, then use the dict directly in the clientside callback. The “issue” was that the to_json method of pydeck returns a str.

pydeck has some util functions to serialize the different object, it is also faster than converting to a json str.

from pydeck.bindings.json_tools import default_serialize
1 Like

Any plans on upgrading pydeck to 0.6 to make Carto the default basemap in dash-deck? This would remove the requirement for mapbox token.

Thanks
Brent

1 Like

Would love the upgrade to 0.6 as well!

Hello I am new to dash-deck, and I am currently working on a project to interactively project 1M plus datapoints using plotly dash.

I managed to build the map using pydeck and incorporated it in a html.Div() as mentioned above in some of the discussion threads. However I am not able to efficiently use the callbacks, as in my call back I am returning a new json rendered map (asking to redraw the whole thing over again every time a filter to be changed). my current basic layout is having a hexagonlayer map with a slider to change the size of the hexagon.


here is a sample of the data I started with (basically some lng/lats)- original dataset has more than 10M points with extra features:
{0: [-73.88237, 40.767281],
 1: [-73.8459137, 40.84584539999999],
 2: [-74.03995549999999, 40.6280321],
 3: [-73.9614156, 40.6711639],
 4: [-73.8459137, 40.84584539999999],
 5: [-73.9092079, 40.7422696],
 6: [-73.7525064, 40.6808144],
 7: [-73.96642369999999, 40.6024728],
 8: [-118.2513821, 34.04436260000001],
 9: [-73.8459137, 40.84584539999999],
 10: [-73.8936568, 40.7124245],
 11: [-73.86616889999999, 40.6728365],
 12: [-73.724429, 40.73412099999999],
 13: [-73.724429, 40.73412099999999],
 14: [-73.88421869999999, 40.6615298],
 15: [-73.8871125, 40.6589543],
 16: [-73.7525064, 40.6808144],
 17: [-73.7800216, 40.7139847],
 18: [-73.99492, 40.621994],
 19: [-74.03878379999999, 40.6150368],
 20: [-73.920355, 40.6587468],
 21: [-74.1645396, 40.6198977],
 22: [-73.92049399999999, 40.6469995],
 23: [-73.7390612, 40.7073529],
 24: [-73.8847139, 40.8687028],
 25: [-73.92049399999999, 40.6469995],
 26: [-73.8459137, 40.84584539999999],
 27: [-73.933052, 40.5948416],
 28: [-73.79152169999999, 40.7688436],
 29: [-73.84066, 40.880436],
 30: [-73.7525064, 40.6808144],
 31: [-73.9223354, 40.6216635],
 32: [-73.9223354, 40.6216635],
 33: [-73.9223354, 40.6216635],
 34: [-73.9223354, 40.6216635],
 35: [-73.9223354, 40.6216635],
 36: [-73.9223354, 40.6216635],
 37: [-73.9223354, 40.6216635],
 38: [-73.9223354, 40.6216635],
 39: [-73.9223354, 40.6216635],
 40: [-73.88421869999999, 40.6615298],
 41: [-73.994796, 40.6906308],
 42: [-73.89217560000002, 40.7398612],
 43: [-73.9930308, 40.7244612],
 44: [-74.122231, 40.613041],
 45: [-73.8655869, 40.7202579],
 46: [-73.746861, 40.6975088],
 47: [-73.8813086, 40.9532636],
 48: [-73.7800216, 40.7139847],
 49: [-73.9694054, 40.7577363]}

My initial call back code is below

@app.callback(
    [Output('SliderHeader', 'children'), Output('deck-gl', 'children')],
    Input('hexagonSizeSlider', 'value')
)

def hexagonSizeSlider(hexagonSizeSlider_value):
    
    #creating a slider value to be visible on screen
    slider_value= "Hexagon Size: {}".format(hexagonSizeSlider_value)

    #creating the map layers
    #creating the hexagon layer
    hexagonlayer= pydeck.Layer(
        "HexagonLayer",
        final,
        get_position= ['lon', 'lat'],
        auto_highlight= True,
        elevation_scale= 10,
        pickable= True,
        elevation_range= [0,50],
        extruded= True,
        coverage= 1
        )

    #creating a geojson layer for NYC
    geojson_color= [3, 252, 211]
    geojson= pydeck.Layer(
        "GeoJsonLayer",
        nyc_geo,
        opacity= 0.05,
        stroked= True,
        filled= True,
        extruded= False,
        wireframe= True,
        get_line_color= geojson_color,
        get_fill_color= geojson_color
    )
    #set viewport location
    view_state= pydeck.ViewState(
        longitude= -73.935242,
        latitude= 40.730610,
        zoom=9,
        min_zoom= 5,
        max_zoom= 15,
        pitch= 40.5,
        bearing= 0)

    #creating a tooltip
    tooltip= {"html": '<b>Borough:</b> {Boro Code}',
            'style': {'color' :  'white'}}
    #render
    r= pydeck.Deck(layers= [geojson,  hexagonlayer],
                initial_view_state= view_state,
                #    tooltip= True,
                height= 700,
                )
    return slider_value, r

I then tried to use r.update() but not sure where should it fit in the callback, and had no luck in getting it work. I get errors and no maps appears

@app.callback(
    Output('SliderHeader', 'children'),
    Input('hexagonSizeSlider', 'value')
)
def hexagonSizeSlider(hexagonSizeSlider_value):
    #creating a slider value to be visible on screen
    slider_value= "Hexagon Size: {}".format(hexagonSizeSlider_value)

    #create a deck.gl hexagonlayer
    hexagonlayer.coverage= [float(hexagonSizeSlider_value)]

    r.update()
    return slider_value

I would also like to filter upon more features and have the the map adapt quickly, another small sample of a dataset with more features. For instance, only showing points for selected Borough, Vehicle Make or a date

	Issue Date	lat	         lon	    Vehicle Make	Boro Code
0	2019-06-12	40.767281	-73.882370	CHEVR       	4
1	2019-06-17	40.845845	-73.845914	HONDA	        2
2	2019-06-17	40.628032	-74.039955	KIA	            3
3	2019-06-17	40.671164	-73.961416	NISSA	        3
4	2019-06-18	40.845845	-73.845914	SUBAR	        2

I would appreciate your advice on how to optimize the data update without having to redraw the map every single time. and any tips on how to filter, aggregate on other features (Date, vehicle make, Borough Code).

Thanks in advance :slight_smile:

@mgendia, do you find a solution to your problem? I’m trying something similar but It doesn’t work

If you don’t need the 3D effects, you might consider the GeoJSON component in dash-leaflet. It supports efficient interactivity (via client side code).