Closed kcpevey closed 5 years ago
Here is a little example that demonstrates how to do this:
class B(param.Parameterized):
c = param.Number(default=10)
class A(param.Parameterized):
b = param.ClassSelector(class_=B)
@param.depends('b.c', watch=True)
def example(self):
print('A.example was called because b.c changed to', self.b.c)
b = B()
a = A(b=b)
b.c = 12
If you make projections a parameter of your adhViz2 class, you can listen to changes on that subobject using a special string syntax, which is basically the name of the parameter, then '.' and then the name of the parameter on the subobject. So in your example that might look like this:
class adhViz2(param.Parameterized):
""" AdH visualization class"""
time = param.ObjectSelector()
result_label = param.ObjectSelector()
projection = param.ClassSelector(class_=projections)
@param.depends('time', 'result_label', 'projection.projection')
def time_mesh(self):
current_projection = self.projection.projection
custom_crs = ...
data_points = self.mesh_points.add_dimension(self.result_label, 0,
self.model[self.result_label].sel(times=self.time).data,
vdim=True)
return gv.TriMesh((self.tris[['v0', 'v1', 'v2']], data_points), label=self.result_label, crs=custom_crs)
Your time_mesh
method will now depend on the projection parameter of the projection
instance that you set on adhViz2
instance's projection parameter. If you want to listen to all parameter changes on the subobject you can change that to:
@param.depends('time', 'result_label', 'projection')
Hope that makes sense.
Makes sense. Those are all things I tried, but it wasn't working for me. However, I just realized that my player widget is not longer interacting with my map. I need to figure out how I managed to break that and it will (hopefully) make your suggestions work ('projection.projection')
I fixed the time widget. My param.depends call is successfully seeing changes on the widget and causing my method to execute. However, sometime after the construction of the panel pieces but before (or during?) visualization l'm getting this error:
Python failed with the following traceback:
/Users/rdchlkcp/miniconda3/envs/earthsim/lib/python3.6/site-packages/pyviz_comms/__init__.py _handle_msg L264
/Users/rdchlkcp/ers/gitHub/panel/master/panel/panel/viewable.py _comm_change L410
/Users/rdchlkcp/ers/gitHub/panel/master/panel/panel/viewable.py _change_event L420
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py inner L939
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py set_param L2224
/Users/rdchlkcp/ers/gitHub/param/master/param/param/parameterized.py set_param L1077
ValueError: 'end' is not a parameter of Time
However, I don't have Time capitilized in my code anywhere and I don't think I have and end
variable anywhere either. I suspect this is coming from:
time = pn.panel(self.adh_viz, parameters=['time'], widgets={'time': pn.widgets.DiscretePlayer}, show_name=False)
May be an issue with the DiscretePlayer widget, I only merged that yesterday so haven't done extensive testing. Does it work if you remove the widgets={...}
bit?
removing widgets={..}
produces different errors.
ValueError
from beforeVM4011:221 Uncaught TypeError: Cannot read property 'style' of null
at d (<anonymous>:221:5350)
at Object.<anonymous> (<anonymous>:221:5435)
at <anonymous>:222:14925
at Array.forEach (<anonymous>)
at <anonymous>:222:14903
at Array.forEach (<anonymous>)
at L (<anonymous>:222:14849)
at <anonymous>:222:16380
at Array.forEach (<anonymous>)
at B (<anonymous>:222:16360)
d @ VM4011:221
(anonymous) @ VM4011:221
(anonymous) @ VM4011:222
(anonymous) @ VM4011:222
L @ VM4011:222
(anonymous) @ VM4011:222
B @ VM4011:222
e @ VM4011:222
VM4011:221 Uncaught TypeError: Cannot read property 'style' of null
at d (<anonymous>:221:5350)
at Object.<anonymous> (<anonymous>:221:5500)
at <anonymous>:222:14925
at Array.forEach (<anonymous>)
at <anonymous>:222:14903
at Array.forEach (<anonymous>)
at L (<anonymous>:222:14849)
at <anonymous>:222:15601
at Array.forEach (<anonymous>)
at W (<anonymous>:222:15556)
Both of the new issues feel like they are originating with my code so I'll dig into that.
I now suspect that I have fundamental misunderstanding of how param.depends/panel/parameters all work together. This is my class:
class viewResults(param.Parameterized):
# objects inherited from the previous page
load_sim_widget = param.ClassSelector(class_=model.load_adh_simulation)
att_widget = param.ClassSelector(class_=model.attributes)
proj_widget = param.ClassSelector(class_=projections.projections)
select_widget = param.ClassSelector(class_=existingSimulation)
# objects passed in from the previous page output
adh_viz = param.ClassSelector(class_=model.adhViz2)
adh_model = param.ClassSelector(class_=adhModel)
# objects carried in
wmts_widget = param.ClassSelector(class_=display_opts.wmts)
def __init__(self, wmts_widget, **params):
super(viewResults, self).__init__(wmts_widget=wmts_widget, **params)
# what to pass out of this page
@param.output()
def output(self):
pass
# how to build this page
def panel(self):
return self.run()
# what to do with the stuff in this page
@param.depends('select_widget.reservoir_level', watch=True)
def run(self):
# get the coorinate system
coord_sys = self.proj_widget.get_crs()
directory = 'filepath/' ## hard coded!!
proj_name = self.select_widget.project_name + str(self.select_widget.reservoir_level)
self.adh_viz = model.adhViz2(projection=coord_sys)
self.adh_viz.read_model(directory, project_name=proj_name, netcdf=True)
meshes = self.adh_viz.create_animation()
print(self.adh_viz.model.Depth.data)
# Define function which applies colormap and color_range
def apply_opts(obj, colormap, color_range):
return obj.options(cmap=colormap).redim.range(**{obj.vdims[0].name: color_range})
viz = PolyAndPointAnnotator(path_type=gv.Path,
crs=ccrs.GOOGLE_MERCATOR,
point_columns=['depth_elevation'],
poly_columns=['name']
)
# Apply the colormap and color range dynamically
dynamic = hv.util.Dynamic(rasterize(meshes), operation=apply_opts, streams=[Params(cmap_opts), Params(display_range)]
).options(colorbar=True, height=600, width=600) * self.wmts_widget.view() * viz.polys * viz.points
# time = pn.panel(self.adh_viz, parameters=['time'], widgets={'time': pn.widgets.DiscretePlayer}, show_name=False)
time = pn.panel(self.adh_viz, parameters=['time'], show_name=False)
hv_panel = pn.panel(dynamic)
map_pane = pn.Column(hv_panel[0], time)
result = pn.panel(self.adh_viz, parameters=['result_label'], show_name=False)
disp_tab = pn.Column(self.wmts_widget,
pn.Pane(cmap_opts, show_name=False),
pn.Pane(display_range, show_name=False),
result,
name=proj_name) # todo removed mesh_opts temporarily
tool_pane = pn.Tabs(("Simulation", select_widget),
("unused", disp_tab))
return pn.Row(map_pane, pn.Spacer(width=100), tool_pane)
@param.depends('select_widget.reservoir_level', watch=True)
is working like it should, causing run() to execute when reservoir_level is changed. I can see from print(self.adh_viz.model.Depth.data)
that it did indeed reload a different model. So the underlying data in self.adh_viz has changed properly.
A couple of things are happening that I don't understand. Once I change the reservoir_level widget (and touch the time slider to make the map refresh), the map no longer shows data (only tiles). However, if run() executed and adh_viz has the new data, why doesn't it visualize?
I felt like maybe the displayed stuff just wasn't reloading when I execute run() so I added name=proj_name
to the disp_tab
and sure enough, when I change reservoir_level, the displayed name does NOT change. I expected ALL of the widgets constructed inside of run() to be reconstructed, but that doesn't appear to be happening.
Can you give me any insight into all of this?
I suspect this is indeed a misunderstanding, by declaring watch=True
you are telling it to rerun the method when the parameter changes, but since it just returns the object that change has no effect at all, it simply calls the method and then discards the output.
What you have to do instead is create another panel, that watches the changes to parameters on the run method. In your case that would look something like this:
def panel(self):
return pn.panel(self.run)
The panel created from self.run
will now watch the parameters it depends on for changes and update the panel when it changes. You should then also remove the explicit watch=True
declaration.
watch=True
is meant for methods that have some side-effect, e.g. if you want the change in one parameter to trigger a change another parameter.
Now I can see how I was just discarding the output.
I changed it to:
@param.depends('select_widget.reservoir_level')
def panel(self):
return pn.panel(self.run())
@param.depends('select_widget.reservoir_level')
def run(self):
...
And now my map has data when I change the reservoir_level, but my disp_widget still isn't updating with the proj_name which makes me think this still isn't working correctly. Do I have my param.depends correct?
No, two things wrong there, first of all the panel method should not have any dependencies, secondly you should not call self.run
because once you call it panel cannot re-execute it when the dependencies change. So it should be:
def panel(self):
return pn.panel(self.run)
It works!!! Whew. That was painful. Thanks for your help!!
I'm working on applying
@param.depends
in my classes. I have the following bits of working code:where
I can use param.depends with
time
andresult_label
because they are parameters in this class. But how would I go about addingprojections.UTM_zone_num
to that param.depends call since I only have the class instance? (note: this is just an example concept)I imagine I'll need to access the parameters within the class, but given that param.depends wants a string, it doesn't seem like that's possible? @philippjfr Any advice?