Personal opinions about best practices for Panel + HoloViews

@ahuang11, have you ever faced the problem with the geoviz/cartopy map + dynamic map not showing up in the modal pane?
All works fine in main and side panel but the modal panel stays empty when I add a pane with geoviews map and holoviews DynamicMap :man_shrugging:

I have not; are you able to reproduce with MRVE? Also maybe check browser for any errors

It works in Firefox, but not in Chrome nor Edge.

What is MRVE?

Minimal reproducible verifiable example (I guess just MRE)

1 Like

Oh, sure :slight_smile:

I ran the following code in python3.10, panel 1.3.8 and geoviews 1.11.1:

import panel as pn
from geoviews import tile_sources


class MinimalModalApp(pn.viewable.Viewer):
    def __init__(self):
        self.button = pn.widgets.Button(name="open modal")
        self.button.on_click(self.open_modal)

        self.modal = pn.Column(
            pn.pane.Markdown("### Worldcard:"),
            self.get_worldcard(),
            align="center")

        self.template = pn.template.FastListTemplate(
            main=[self.button],
            modal=[self.modal],
            title="MVPE - Worldmap not displayed in modal in Chromium based browsers (Chrome/Edge, Firefox works)"
        )

    def get_worldcard(self):
        return tile_sources.tile_sources["CartoEco"].opts(width=800, height=400)

    def open_modal(self, event):
        self.template.open_modal()

    def __panel__(self):
        return self.template


if __name__ == "__main__":
    app = MinimalModalApp()
    pn.serve(app, port=5006)

I took a quick look:

Something with xlim/ylim I think, but I couldn’t get it working yet

import panel as pn
from geoviews import tile_sources
import cartopy.crs as ccrs
import geoviews as gv


class MinimalModalApp(pn.viewable.Viewer):
    def __init__(self):
        self.button = pn.widgets.Button(name="open modal")
        self.button.on_click(self.open_modal)

        modal = pn.pane.HoloViews(self.get_worldcard())
        self.modal = pn.Column(
            pn.pane.Markdown("### Worldcard:"),
            modal,
            align="center")

        self.template = pn.template.FastListTemplate(
            main=[self.button],
            modal=[self.modal],
            title="MVPE - Worldmap not displayed in modal in Chromium based browsers (Chrome/Edge, Firefox works)"
        )

    def get_worldcard(self):
        return gv.Points((0, 0), crs=ccrs.GOOGLE_MERCATOR).opts(global_extent=True) * tile_sources.CartoDark()

    def open_modal(self, event):
        self.template.open_modal()

    def __panel__(self):
        return self.template


MinimalModalApp().servable()

It seems like it’s not just tile sources, but generic plots

import panel as pn
from geoviews import tile_sources
import cartopy.crs as ccrs
import geoviews as gv


class MinimalModalApp(pn.viewable.Viewer):
    def __init__(self):
        self.button = pn.widgets.Button(name="open modal")
        self.button.on_click(self.open_modal)

        self._map = gv.Points((0, 0)).opts(xlim=(-90, 90), ylim=(-90, 90), width=500, height=500)
        self.modal = pn.Column(
            pn.pane.Markdown("### Worldcard:"),
            pn.pane.HoloViews(self._map, width=800, height=400),
            align="center")

        self.template = pn.template.FastListTemplate(
            main=[self.button, self._map.clone()],
            modal=[self.modal],
            title="MVPE - Worldmap not displayed in modal in Chromium based browsers (Chrome/Edge, Firefox works)"
        )

    def open_modal(self, event):
        self.template.open_modal()

    def __panel__(self):
        return self.template


MinimalModalApp().servable()

Use persist() or load() if data fits in memory to prevent re-triggering computations.

I don’t understand. Which object should be persist() or load() be applied on?

See this for context.

Sorry, but I still don’t see the relation to the modal-browser issue.
Do you think because of endless dmap retriggering the object is not rendered in chrome/edge?

Oh sorry, I’m just using this thread to take notes for an eventual best practices guide; not as a reply to your question.

I would love to see how you create more complicated Holoviews objects from scratch and even more interested in how you set options. I have been struggling setting opts such as line_color or other more niche options while overlaying geofeatures and other plots (Say FilledContours, WindBarbs and coastline). It gets even trickier if you try and change them later as now it is an Overlay.

I have been struggling setting opts such as line_color or other more niche options while overlaying geofeatures and other plots

You can target specific elements: Overlay([hv_obj1, hv_obj2]).opts("Curve", line_color="red")


Collecting more best practices things here:

  • Generally, try to replace HoloViews usage with hvPlot. At a certain point of complexity, such as with the use of ‘.select’, it might be better to stick with HoloViews.
  • Almost always, try to replace the use of datashade with rasterize (read this page). Essentially, rasterize allows Bokeh to handle the colormapping instead of Datashader.
  • Remove all pn.interact usage
  • Try to avoid .param.watch() usage. This is pretty low-level and verbose approach Prefer using pn.bind(). Read this page for explanation.
  • For apps built using a class approach, when they create a view() method and call it directly, update the class by inheriting from pn.viewable.Viewer and replace view() by __panel__(). Here is an example.
3 Likes

Revising:

set loading=True/False inside a try/finally block 6 so that widgets are disabled while loading, and upon completion/something goes wrong, the widgets are always reactivated.

Philipp mentioned we could simply do with param.update

import time
import panel as pn

pn.extension()

layout = pn.WidgetBox(pn.widgets.TextInput(), pn.widgets.Button(name="Click me!"))
layout
with layout.param.update(loading=True):
    time.sleep(2)
    layout[0].value = "Loaded!"

From Marc

1 Like

Using flexbox instead of column / row

1 Like

Adding another to the list: toggling axiswise, framewise, shared_axes,

Started migrating over here; let me know if you have feedback!

1 Like

Widgets should not be used like param