Since I just finished creating one of my most polished apps yet (Year vs Climatology - a Hugging Face Space by ahuang11) and I think it has a lot of examples of best practices (mostly personal opinions based on my experience using HoloViz), I randomly decided to do a terribly unpolished brain dump! For a more coherent version by ChatGPT, scroll down below
inheriting from pn.viewable.Viewer instead of param.Parameterized allows you to call the class directly, as if it’s a native Panel object, instead of calling a method (e.g. ClimateApp().view().servable())
set throttled=True so that sliders are only triggering callbacks on mouse-up, and not at every step
to make an app feel more responsive, use onload so that the web page doesn’t hang while the slow things are loading–users can immediately see a loading indicator upon visiting to know that it’s processing
you can use pn.Param to convert the class’ params to widgets, but sometimes working with nested dicts is a pain so I use from_param classmethod instead which allows syncing up widget values with param values
use label to rename widget names if you go the route of pn.Param or from_param
Don’t forget to use self.param.NAME_OF_PARAM instead of self.NAME_OF_PARAM if you want some param to be watched/bound (note the extra .param.)
instead of recreating the panel object every time pn.bind runs, [update the object and value] (app.py · ahuang11/year-vs-climatology at main) on the pre-initialized placeholders! This can reduce flickers and optimize runtime slightly
simply wrap hv.DynamicMap on pn.bind to prevent the plot from resetting its zoomed range upon update; there are some bugs related to this (e.g. title/ylabel won’t update so workaround)
don’t forget to use pn.depends with watch=True if the method doesn’t return anything (i.e. return None or no return statement at all)
a template’s modal(pop up container) is pretty useful for screen estate if you don’t know where else to put something
You can also bind to HoloViews streams and the streams also have param (hv.DynamicMap streams=... and pn.bind have some similarities and differences, although I guess that doesn’t say much)
A more coherent version by ChatGPT
“”"
After completing the development of what I consider one of my most refined applications yet (Year vs Climatology - a Hugging Face Space by ahuang11), I realized it encompasses numerous examples of best practices, mostly drawn from my experience using HoloViz. In a spontaneous decision, I decided to compile a somewhat unpolished brain dump of these practices:
Instead of inheriting from param.Parameterized, opting for inheritance from pn.viewable.Viewer allows direct invocation of the class, resembling a native Panel object. This eliminates the need for calling a method like ClimateApp().view().servable().
To ensure sliders only trigger callbacks upon mouse-up, set throttled=True.
Start by instantiating the initial layout with placeholder pn.Columns in __init__, then populate it later in onload.
Enhance app responsiveness by employing onload to prevent webpage hang-ups during slow loading processes. Users can immediately observe a loading indicator upon visiting, signaling ongoing processing.
Manage loading states effectively by encapsulating widget activation within a try/finally block. This ensures widgets are always reactivated upon completion or in the event of errors.
Utilize pn.Param or from_param classmethod for converting class parameters to widgets, with the latter particularly useful for handling nested dictionaries.
Renaming widget names can be achieved using label when utilizing pn.Param or from_param.
Ensure proper param watching/binding by using self.param.NAME_OF_PARAM instead of self.NAME_OF_PARAM, noting the additional .param..
Optimize runtime and reduce flickers by updating object and value on pre-initialized placeholders instead of recreating the panel object every time pn.bind runs.
Preserve plot zoom ranges during updates by wrapping hv.DynamicMap with pn.bind. This prevents the plot from resetting zoom, although some bugs may necessitate workarounds for issues like title/ylabel updating.
Enhance app speed using @pn.cache, but remember that pn.cache activation depends on input kwargs.
Starting with small iterations, gradually building up functionality. The panel serve app.py --autoreload command proves handy in this regard.
Focusing initially on layout, ensuring the core structure looks correct.
Introducing interactivity with pn.bind or pn.depends once the layout is satisfactory.
I continuously iterate through these steps when implementing new features, beginning with creating placeholder widgets/panes, verifying visual correctness, and finally adding interactivity. If any of these concepts require clarification, feel free to ask!
“”"
I agree with this approach, but I would clarify that it applies if your class contains Panel code (not just Param code). I.e., you should just use Parameterized while your class contains only Param items, but once you throw in anything to do with Panel (such as a view() method that returns a Panel layout, or it has explicit Panel widgets) then you should go ahead and make it a Viewer so that you aren’t duplicating the code that’s already in Viewer.
And I noticed that it suggests a pattern of having output, view, and panel methods on parameterized classes that are intended to be used with panel. I was thinking, this pattern could be used as a design pattern beyond the use of pipelines.
I’ve had trouble understanding the difference between view + panel, but I think your example helps me understand it much better (view is the main of the template and panel is the sidebar so to speak!)
I am wondering though, what output would be for more complicated apps?
@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