Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add a button to load scenes in the interface #67

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions vivarium/interface/panel_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,45 @@ def __init__(self, config, panel_configs, panel_simulator_config, selected, etyp
self.config[i].param.watch(self.hide_non_existing, "exists", onlychanged=True)

def drag_cb(self, attr, old, new):
"""Callback for the drag & drop of entities

:param attr: (unused)
:param old: (unused)
:param new: The event containing the new positions of the entities
"""
for i, c in enumerate(self.config):
c.x_position = new['x'][i]
c.y_position = new['y'][i]

@contextmanager
def no_drag_cb(self):
"""Prevent the CDS from updating the configs when the change comes from the
server
"""
self.cds.remove_on_change('data', self.drag_cb)
yield
self.cds.on_change('data', self.drag_cb)

def get_cds_data(self, state):
"""Update the ColumnDataSource with the new data

:param state: The state coming from the server
:return: Data dictionary for the ColumnDataSource
"""
raise NotImplementedError()

def update_cds(self, state):
"""Updates the ColumnDataSource with new data from server

:param state: The state coming from the server
"""
self.cds.data.update(self.get_cds_data(state))

def create_cds_view(self):
"""Creates a ColumnDataSource view for each visibility attribute

:return: A dictionary of ColumnDataSource views for each visibility attribute
"""
# For each attribute in the panel config, create a filter
# that is a logical AND of the visibility and the attribute
return {
Expand All @@ -67,32 +89,59 @@ def create_cds_view(self):
}

def update_cds_view(self, event):
"""Updates the view of the ColumnDataSource if the visibility of an entity
changes

:param event: The event containing the changed value
"""
n = event.name
for attr in [n] if n != "visible" else self.panel_configs[0].param_names():
f = [getattr(pc, attr) and pc.visible for pc in self.panel_configs]
self.cds_view[attr].filter = BooleanFilter(f)

def update_selected_plot(self, event):
"""Updates the selected entities in the plot

:param event: The event containing the new selected entities
"""
self.cds.selected.indices = event.new

def hide_all_non_existing(self, event):
"""Hides or shows all the entities that do not exist according to the global
visibility of non-existing entities

:param event: The event containing the new global "visibility of non-existing
entities" value
"""
for i, pc in enumerate(self.panel_configs):
if not self.config[i].exists:
pc.visible = not event.new

def hide_non_existing(self, event):
"""Hides or shows an entity that does not exist depending on the global
visibility of non-existing entities

:param event: The event containing the new existence value
"""
if not self.panel_simulator_config.hide_non_existing:
return
idx = self.config.index(event.obj)
self.panel_configs[idx].visible = event.new


def update_selected_simulator(self):
"""Updates the list of selected entities in the Selection list
"""
indices = self.cds.selected.indices
if len(indices) > 0 and indices != self.selected.selection:
self.selected.selection = indices

def plot(self, fig: figure):
"""Plot the objects on the bokeh figure

:param fig: A bokeh figure
:return: The figure with the objects plotted
"""
raise NotImplementedError()


Expand Down Expand Up @@ -205,6 +254,7 @@ class WindowManager(Parameterized):
align="center", value=config_types[1:])
update_switch = pn.widgets.Switch(name="Update plot", value=True, align="center")
update_timestep = pn.widgets.IntSlider(name="Timestep (ms)", value=1, start=1, end=1000)
scene_loader = pn.widgets.FileInput(accept=".yml", name="Load scene", align="center")
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.entity_manager_classes = {EntityType.AGENT: AgentManager,
Expand All @@ -224,6 +274,10 @@ def __init__(self, **kwargs):
self.set_callbacks()

def start_toggle_cb(self, event):
"""Callback for the start/stop button

:param event: The event for the new value of the button
"""
if event.new != self.controller.is_started():
if event.new:
self.controller.start()
Expand All @@ -237,9 +291,15 @@ def entity_toggle_cb(self, event):
cc.visible = cc.name in event.new

def update_timestep_cb(self, event):
"""Callback for the timestep of the plot update

:param event: The event for the new value of the timestep
"""
self.pcb_plot.period = event.new

def update_plot_cb(self):
"""Periodic callback for the plot update
"""
for em in self.entity_managers.values():
em.update_selected_simulator()
state = self.controller.update_state()
Expand All @@ -251,12 +311,20 @@ def update_plot_cb(self):
em.update_cds(state)

def update_switch_cb(self, event):
"""Callback for the plot update switch

:param event: The event for the new value of the switch
"""
if event.new and not self.pcb_plot.running:
self.pcb_plot.start()
elif not event.new and self.pcb_plot.running:
self.pcb_plot.stop()

def create_plot(self):
"""Creates a bokeh plot for the simulator

:return: A bokeh plot
"""
p_tools = "crosshair,pan,wheel_zoom,box_zoom,reset,tap,box_select,lasso_select"
p = figure(tools=p_tools, active_drag="box_select")
p.axis.major_label_text_font_size = "24px"
Expand All @@ -270,6 +338,10 @@ def create_plot(self):
return p

def create_app(self):
"""Creates a panel app

:return: the panel app
"""
self.config_columns = pn.Row(*
[pn.Column(
pn.pane.Markdown("### SIMULATOR", align="center"),
Expand All @@ -287,7 +359,9 @@ def create_app(self):
for etype in self.entity_managers.keys()])

app = pn.Row(pn.Column(pn.Row(pn.pane.Markdown("### Start/Stop server", align="center"),
self.start_toggle),
self.start_toggle,
pn.pane.Markdown("### Load scene", align="center"),
self.scene_loader),
pn.Row(pn.pane.Markdown("### Start/Stop update", align="center"),
self.update_switch, self.update_timestep),
pn.panel(self.plot)),
Expand All @@ -296,7 +370,9 @@ def create_app(self):
return app

def set_callbacks(self):
# putting directly the slider value causes bugs on some OS
"""
Set the callbacks for all the widgets in the app
"""
self.pcb_plot = pn.state.add_periodic_callback(self.update_plot_cb,
self.update_timestep.value)
self.entity_toggle.param.watch(self.entity_toggle_cb, "value")
Expand Down
Loading