panel icon indicating copy to clipboard operation
panel copied to clipboard

Update wording describing how Panel layouts get converted to pn.panel()

Open kcpevey opened this issue 5 years ago • 8 comments

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.

kcpevey avatar Jul 27 '20 14:07 kcpevey

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.

jbednar avatar Jul 27 '20 14:07 jbednar

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...

kcpevey avatar Jul 29 '20 14:07 kcpevey

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

kcpevey avatar Aug 04 '20 21:08 kcpevey

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

kcpevey avatar Aug 04 '20 21:08 kcpevey

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.

kcpevey avatar Aug 04 '20 21:08 kcpevey

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 avatar Aug 05 '20 12:08 kcpevey

@kcpevey Just noting that both of your previous examples are using watch=True when they shouldn't be.

philippjfr avatar Aug 07 '20 10:08 philippjfr

Ah yes. The rule: "never use watch=True when you're returning something"

kcpevey avatar Aug 07 '20 13:08 kcpevey