holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.63k stars 505 forks source link

Custom Layers for DeckGL panes #3707

Open lmcinnes opened 2 years ago

lmcinnes commented 2 years ago

Currently PyDeck has support for custom layers. See, for example, their documentation here. This can be quite powerful since Deck.gl itself makes it relatively easy to build custom layers via CompositeLayers. For my own use I created a a level-of-detail-text-layer that allows for layered text annotations that appear and disappear at varying zoom levels (see https://github.com/lmcinnes/lod-text-layer). I was hoping to be able to use such layers in panel as well as in pydeck.

PyDeck manages this by taking a list of dictionaries with libraryName and resourceURI of extra javascript to use when defining layers. Passing in something similar to a panel DeckGL pane would certainly provide basic parity with PyDeck. It may also be possible to pass is custom javascript functions that can then be used as callbacks within a deck -- allowing more functionality to be pushed to the frontend javascript and avoiding some potentially unnecessary roundtrips to the server -- but that is solidly in the category of useful extras.

Including the relevant javascript bundle for the custom layer in an HTML container or in the panel configuration seemed like the obvious way to arrange this. Unfortunately panel uses the JSONConverter functionality to build the deck, and that keeps track, via a configuration dictionary, of the classes that can be converted to layers. This is documented here, and includes the possibility both of custom classes, and of custom functions.

Because of this it is necessary to inject custom libraries into this configuration, which is currently defined within deckgl.ts. I spent some time decoding how PyDeck arranges this, and it works as follows: the custom_libraries are encoded into the template used for rendering the html and passed to createDeck. In turn createDeck has code that iterates through the custom libraries and extracts classes from them; adding them to the configuration (see here). In principle I would hope it would be possible to let the DeckGL pane have a custom_libraries param that looks like PyDeck's custom_libraries list of dicts and pass that through so that deckgl.ts can make use of it in a similar way. Specifically if extractClasses could be made generic (taking a library, rather than just hard-coded with deck, and the initialize, that creates the JSONConverter, could perform a similar approach to PyDeck, adding classes extracted from custom_libraries, then I think this could work reasonably straightforwardly, but there are likely gremlins lurking in the details.

I have tried doing this myself, but my lack of familiarity with typescript, javascript infrastructure, and lack of understanding of exactly how the transfer of information between the python classes and typescript parts of panel works, are more than enough to defeat my efforts. I am trying to learn more about how to do this (but it is a deep rabbit hole). Alternatively I would be happy to work with someone to make this happen.

philippjfr commented 2 years ago

Really appreciate the thorough writeup and investigation. Seems like the best way forward is to simply try adding the custom_libraries property and then copy the approach used by the jupyter widget to extract the classes. If there's Gremlins lurking we'll encounter them pretty quickly.

lmcinnes commented 2 years ago

Is this something you have time to try? My initial attempts ran into the difficulty that I was unsure how to get a string in python giving a uri of a javascript library turned into something that allowed access to that library as a javascript module on the typescript side of things. I assume there are natural ways to arrange for this, but I lack familiarity with panel/bokeh to know what the right patterns are.

philippjfr commented 2 years ago

Yes, I'll try to investigate. You're right that dynamically loading the libraries is probably the complex part.