Update wording describing how Panel layouts get converted to pn.panel()
Is your feature request related to a problem? Please describe.
@jbednar Recently posted this in an issue response (#1494):
Just to clarify, pn.panel() is automatically called whenever Panel layouts like pn.Column() expect a Panel object, and it uses a set of heuristics to figure out what the appropriate type of Panel object is for the given item.
I've struggled with this concept (because I didn't understand what was happening) when building param.Parameterized classes with methods decorated with depends. I believe that the outermost pn.panel() can't have a depends on it which is why sometimes I have to wrap my layout method with an extra pn.panel(). It feels strange to do that, but I think this explains why (please correct me if I'm wrong).
Describe the solution you'd like
I checked the docs and a version of this concept is in the layout docs:
If the objects are not already Panel components they will each be converted to one using the pn.panel conversion method.
However, this is much less clear. I recommend replacing the sentence in the layout docs with the comment from @jbednar which is much more comprehensive and clear. I know its a very small change, but I think it's an improvement and I'd be willing to open a PR for it.
Yes, please do put any of our offhand comments into the spot in the docs where they are most needed; that's one of the most valuable jobs a user of the software can do (connecting dots that we don't realize aren't connected up!). Not sure what you mean about wrapping the layout method with pn.panel(); maybe open an issue with a concrete example showing what you're doing there.
Not sure what you mean about wrapping the layout method with pn.panel(); maybe open an issue with a concrete example showing what you're doing there.
I've been waiting to come across a minimally reproducible example of this. I can't seem to recreate in a simple example. I'll keep looking for a good example...
Example:
marker = param.Integer(default=9999)
redraw_trigger = param.Integer(default=0)
def __init__(self, **params):
super().__init__(**params)
def update_status(self):
self.marker = 4321
self.redraw_trigger += 1
@param.depends('redraw_trigger', watch=True)
def layout(self):
left_column = pn.pane.HTML(f"""
<h2>Total Operation Data</h2>
Marker Information: {self.marker}
""", sizing_mode='stretch_both')
return pn.Row(left_column)
def panel(self):
return pn.panel(self.layout)
If I just visualize layout
o = overview()
o.layout()
Then some external force calls update status:
o.update_status()
You can see that the layout doesn't get updated.
If instead I visualize the panel method:
o = overview()
o.panel()
Then call o.update_status(). You can see that the layout DOES get updated.
When I sit down and think about why this is happening, I think I understand (something about visuals being redrawn but nothing receiving them?), but this is something that I continually get tripped up over. When it's a simple app like this, I can hack around until I get it. When it's a complex app, its hours of frustration over "why doesn't it work?!? this should work!!".
Some things that would be helpful:
- @jbednar or @philippjfr 's explanation as to why this is
- some tips on how you can track something like this down when you've done it the wrong way
- including the above in some documentation
This was a secondary thing that was happening when I posted this issue about ObjectSelectors (https://discourse.holoviz.org/t/dynamically-update-objectselector-dropdown/409). You can see how it's really hard to diagnose the ObjectSelector issue when you inadvertently have a panel that's not redrawing like you think it is
This is also very important, especially in terms of my original post. If instead of a HTML pane, you just render a param in a pn.Row - i.e. replace the layout method with this:
@param.depends('redraw_trigger', watch=True)
def layout(self):
return pn.Row(self.marker)
Then you can indeed visualize with only o.layout() and the display will change when you call o.update_status()
This can be really confusing.
Also - Maybe you read the docs on HTML pane and you see that the HTML pane can be directly visualized so you change the layout method to just return that pane like this:
@param.depends('redraw_trigger', watch=True)
def layout(self):
left_column = pn.pane.HTML(f"""
<h2>Total Operation Data</h2>
Marker Information: {self.marker}
""", sizing_mode='stretch_both')
return left_column
This doesn't update even when you use the o.panel() method. It has to be wrapped in pn.Row in order to update. This doesn't make sense to me.
@kcpevey Just noting that both of your previous examples are using watch=True when they shouldn't be.
Ah yes. The rule: "never use watch=True when you're returning something"