Use of tab deactivates trigger

This is my stylized code:

import panel as pn
import param

pn.extension()

class ParkLab(param.Parameterized):

StartTime1_s = param.Number(0.000, step=0.001, precedence=0)
UpdateGraphsTrigger = param.Integer(default=0)

@pn.depends(ParkLab.param.UpdateGraphsTrigger)
def updateGraphs(i):

print(‘updateGraphs:’, i)
tabs = pn.Tabs((‘Beginning’, pn.pane.Markdown(‘Test’)),(‘StartTime1_s’, ParkLab.param.StartTime1_s))
tabs = pn.pane.Markdown(‘Test’)
return tabs

caption = pn.pane.Markdown(‘Portal’, width=450)
Cp = pn.Column(caption, ParkLab.param.UpdateGraphsTrigger, updateGraphs)
Cp

Please note the one commented out tabs and the ‘tabs’ without comment.

Desired behavior: when I modify UpdateGraphsTrigger by clicking on the box for example on the up arrow, updateGraphs is run and this is shown e.g., because it prints

When just the Markdown(‘Test’) is returned by updateGraphs, this is what happens.

But if you uncomment the pn.Tabs statement and comment the one with pn.pane, then updateGraphs is not called when I modify UpdateGraphsTrigger.

How can this be? How can I fix this?

Thanks for the questions, please use triple backticks (i.e. ```) around your code in future.

It’s really not clear to me what you’re trying to achieve here, however if you’re going to be writing classes you should probably go all the way and use methods too:

import panel as pn
import param

pn.extension()

class ParkLab(param.Parameterized):

    StartTime1_s = param.Number(0.000, step=0.001, precedence=0)
    UpdateGraphsTrigger = param.Integer(default=0)

    @pn.depends('UpdateGraphsTrigger')
    def updateGraphs(self):
        return pn.Tabs(('Beginning', pn.pane.Markdown('Test')),
                       ('StartTime1_s', self.param.StartTime1_s))

lab = ParkLab()
caption = pn.pane.Markdown('Portal', width=450)
pn.Column(caption, lab.param.UpdateGraphsTrigger, lab.updateGraphs)

That being said even this is a really weird pattern. If you find yourself returning entire layouts from some function or method you’re usually doing something wrong. So really I’d like more of a description of what you’re trying to achieve here so I can provide some more detailed advice on how to structure your code.

Thank you. My reply is late as I was pulled into something else.

I am not so sure your code works, but I must say I am confused. This is what I have tried:

  1. Everything in the class as per your suggestion.
import panel as pn
import param

pn.extension()

class ParkLab(param.Parameterized):
    StartTime1_s = param.Number(0.000, step=0.001, precedence=0)
    UpdateGraphsTrigger = param.Integer(default=0)  
    
   
    @pn.depends('UpdateGraphsTrigger')
    def updateGraphs(self):
        print('updateGraphs:', self.UpdateGraphsTrigger)
        #return pn.Tabs(('Beginning', pn.pane.Markdown('Test ' + str(self.UpdateGraphsTrigger))),
        #               ('StartTime1_s', ParkLab.param.StartTime1_s))
        return pn.pane.Markdown('Test ' + str(self.UpdateGraphsTrigger)) 

lab = ParkLab()
caption = pn.pane.Markdown('Portal', width=450)
pn.Column(caption, ParkLab.param.UpdateGraphsTrigger, lab.updateGraphs)

The print statement is only executed once and the Markdown that generates 'Test ’ + str(self.UpdateGraphsTrigger) never changes (text on screen always stays at ‘Test 0’, even when the UpdateGraphsTrigger has been modified and shows a different number in its own control). Does the text ‘Test 0’ change when you run the code and manually modify the UpdateGraphsTrigger (even when no tabs are used)? Or is there something I do wrong?

  1. Back to my old code with the trigger outside of the class
import panel as pn
import param

pn.extension()

class ParkLab(param.Parameterized):
    StartTime1_s = param.Number(0.000, step=0.001, precedence=0)
    UpdateGraphsTrigger = param.Integer(default=0)

@pn.depends(ParkLab.param.UpdateGraphsTrigger)
def updateGraphs(i):
    print('updateGraphs:', i)
    #tabs = pn.Tabs(('Beginning', pn.pane.Markdown('Test '+ str(i) )),('StartTime1_s', ParkLab.param.StartTime1_s))
    tabs = pn.pane.Markdown('Test ' + str(i))
    return tabs

caption = pn.pane.Markdown('Portal', width=450)
pn.Column(caption, ParkLab.param.UpdateGraphsTrigger, updateGraphs)

Now, irrespective of whether I use tabs or not, the ‘Test 0’ changes when I change the UpdateGraphsTrigger. But when I don’t use tabs, the print statement is executed repeatedly but when I use tabs, it only prints once.

Yes, putting everything in the class is better. This was a holdover from something that previously did not work when I put it inside the class and I just had not wanted to put in the time to figure out why not. As you can see from the new example in this reply, there are still differences when you put a function as a method to a class vs when you put it separately (as far as I can tell).

As to the design pattern: advice is more than welcome. I actually have a more involved user interface. See attached picture.

The goal is to visualize 2 sets of time series of data which is held in csv files. I used to do this with VBA in excel but the code had become unreadable and slow because of the large volume of data to be handled.

These csv files are organized in a directory tree. Users can select subdirectories using a combination of the location field and the file field. In addition, for each such subdirectory, the time axis of the graphs must be shifted (to synchronize with video) and the amount of the shift can be indicated in Timing Offset. Finally, for both locations, up to 7 additional graphs can be displayed and the user can select which ones with the last row of UI elements (which is used for both location/file patterns selected). These 7 are grouped in 2 groups (PD and ACC).

The buttons on the right are to save the configuration (mainly the timing offset but also which graphs have been selected) and to update the parameters Timing Offset and selected graphs (per your suggestion in another question I asked on this forum).

So, when a user updates a parameter such as the Timing Offset (or any of the others), the graphs need to be re-drawn.

Currently, I use all params to return updated holoviews graphs to panel, similar to the stylized code I submitted. I have a class that holds the data from the csv files and then the visualization class.

What would be a better design pattern to do so?

Any ideas re. how to do this better would be appreciated.

@philippjfr Sorry to bug you about this - is there anything more from me you need to provide feedback?

With an additional question: the code to produce above graphs works on Linux (Ubuntu 18.04). I have now ported it to run in Jupyter Lab on Windows 10, but the @pn.depends(Parklab.param.file0, watch=True) does not trigger on Windows 10 (installed via Anaconda; Jupyter lab v1.2.6; python 3.7.7; 64-bit).

file0 = param.FileSelector(default=FILE0, path=…) per my other example. If it helps, I can try to create a stylized example.

I used to get extremely fast replies from @philippjfr but this is not longer the case. Does this mean holoviz/param etc… is no longer supported? Or: is @philippjfr OK?

@piet8stevens I’d like to point out that this is not a commercial helpdesk nor a educational program. Furthermore, you already received an answer which imho was sufficient to solve this issue.
The code below should do what I think you desire: it reprints when updating the widget.

As mentoined, when using classes, best to go all the way and use methods which update the objects.
Using the Param class, make sure to always update the layout, rather than return a fully new one. The new layout will be in its default state, which becomes very obvious in tabs objects. Check out topic Panel Tabs as item in panel Column switches to first tab on widget interactions for another clear example of this.

@philippjfr In the (pending) full documentation of Param I suggest to clearly highlight the approach difference of returning a layout and moifying it. The straightforward @depends() + some_function() which is used throught the panel documentation typically returns a layout, which is to be avoided using the Param class approach!

import panel as pn
import param

pn.extension()

class ParkLab(param.Parameterized):

    StartTime1_s = param.Number(0.000, step=0.001, precedence=0)
    UpdateGraphsTrigger = param.Integer(default=0)
    caption = pn.pane.Markdown('Portal', width=450)
    layout = pn.Param()
    Cp = pn.Param()

    def __init__(self,**params):
        super().__init__(**params)
        self.layout = pn.Tabs(('Beginning', updateGraphs),
                       ('StartTime1_s', self.param.StartTime1_s))
        self.Cp  = pn.Column(self.caption, 
                       self.param.UpdateGraphsTrigger, 
                       self.layout)
        
    @pn.depends('UpdateGraphsTrigger',watch=True)
    def updateGraphs(self):
        print('Test'+str(self.UpdateGraphsTrigger))
        combined_text = pn.pane.Markdown('Test'+str(self.UpdateGraphsTrigger))
        return combined_text
lab = ParkLab()
lab.Cp
1 Like

I just think there are more channels, users and things going on. And Philipp sits in the middle of the web with a lot to do.

I’m hoping we can all help him so he can keep driving Holoviz forwards.

For me there is also a discourse technical thing. If I just skim through new issues they are marked as old/ read and I might forget to come back to them.

1 Like

This was in fact on my list to look over but during the week I rarely have time to give anything but cursory answers.

I did not intend to complain to or push anyone. If it was perceived as such, my apologies. I am very grateful for Holoviz/Param etc… and also the support on this forum. I was just a bit nervous due to the contrast in responsiveness between a month ago and now - fearing that I was betting on a dead horse and I am glad this is not the case.

2 Likes