From de6137004c30b23bf8f8a60819b070afeca8f1b8 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 20 Nov 2020 11:08:02 +0100 Subject: [PATCH] Improve the layout system for the ventilation schemes on the ExpertApp. --- cara/apps/expert.py | 154 +++++++++++++++++++++++++++----------------- cara/models.py | 2 + 2 files changed, 98 insertions(+), 58 deletions(-) diff --git a/cara/apps/expert.py b/cara/apps/expert.py index 1be5330a..07ca84cd 100644 --- a/cara/apps/expert.py +++ b/cara/apps/expert.py @@ -25,7 +25,45 @@ def widget_group(label_widget_pairs): labels, widgets_ = zip(*label_widget_pairs) labels_w = widgets.VBox(labels) widgets_w = widgets.VBox(widgets_) - return widgets.HBox([labels_w, widgets_w]) + return widgets.VBox([widgets.HBox([labels_w, widgets_w])]) + + +WidgetPairType = typing.Tuple[widgets.Widget, widgets.Widget] + + +class WidgetGroup: + def __init__(self, label_widget_pairs: typing.Sequence[WidgetPairType]): + self.labels = [] + self.widgets = [] + self.add_pairs(label_widget_pairs) + + def set_visible(self, visible: bool): + for widget in (self.labels + self.widgets): + if visible: + widget.layout.visible = True + widget.layout.display = 'flex' + else: + widget.layout.visible = False + widget.layout.display = 'none' + + def pairs(self) -> typing.Sequence[WidgetPairType]: + return zip(*[self.labels, self.widgets]) + + def add_pairs(self, label_widget_pairs: typing.Sequence[WidgetPairType]): + labels, widgets_ = zip(*label_widget_pairs) + self.labels.extend(labels) + self.widgets.extend(widgets_) + + def build(self): + labels_w = widgets.VBox(self.labels) + widgets_w = widgets.VBox(self.widgets) + return widgets.VBox( + [ + widgets.HBox( + [labels_w, widgets_w], + ), + ], + ) #: A scenario is a name and a (mutable) model. @@ -216,24 +254,28 @@ class ModelWidgets(View): widget = collapsible( [widget_group( - [[widgets.Label('Room volume (m3'), room_volume]] + [[widgets.Label('Room volume (m³)'), room_volume]] )], title='Specification of workplace', ) return widget - def _build_outsidetemp(self,node): - outside_temp = widgets.IntSlider(value=10., min=-10., max=30.) + def _build_outsidetemp(self, node) -> WidgetGroup: + outside_temp = widgets.IntSlider(value=10, min=-10, max=30) def outsidetemp_change(change): - node.values = (change['new']+273.15,) - outside_temp.observe(outsidetemp_change, names=['value']) - return widgets.VBox([ - widgets.HBox([widgets.Label('Outside temperature (oC)', - layout=widgets.Layout(width='150px')), outside_temp]), - ]) + node.values = (change['new'] + 273.15, ) - def _build_window(self, node): + outside_temp.observe(outsidetemp_change, names=['value']) + auto_width = widgets.Layout(width='auto') + return WidgetGroup([ + [ + widgets.Label('Outside temperature (℃)', layout=auto_width,), + outside_temp, + ], + ]) + + def _build_window(self, node) -> WidgetGroup: period = widgets.IntSlider(value=node.active.period, min=0, max=240) interval = widgets.IntSlider(value=node.active.duration, min=0, max=240) inside_temp = widgets.IntSlider(value=node.inside_temp.values[0]-273.15, min=15., max=25.) @@ -256,69 +298,62 @@ class ModelWidgets(View): 'Fixed': self._build_outsidetemp(node.outside_temp), 'Daily variation': self._build_month(node), } - for name, widget in outsidetemp_widgets.items(): - widget.layout.visible = False outsidetemp_w = widgets.ToggleButtons( options=outsidetemp_widgets.keys(), ) def toggle_outsidetemp(value): - for name, widget in outsidetemp_widgets.items(): - widget.layout.display = 'none' + for name, widget_group in outsidetemp_widgets.items(): + widget_group.set_visible(False) - #node.dcs_select(value) - - widget = outsidetemp_widgets[value] - widget.layout.visible = True - widget.layout.display = 'block' + widget_group = outsidetemp_widgets[value] + widget_group.set_visible(True) outsidetemp_w.observe(lambda event: toggle_outsidetemp(event['new']), 'value') toggle_outsidetemp(outsidetemp_w.value) - return widgets.VBox( - [ - widgets.HBox([widgets.Label('Frequency of opening (min)', - layout=widgets.Layout(width='150px')), period]), - widgets.HBox([widgets.Label('Duration of opening (min)', - layout=widgets.Layout(width='150px')), interval]), - widgets.HBox([widgets.Label('Inside temperature (oC)', - layout=widgets.Layout(width='150px')), inside_temp]), - widget_group([[widgets.Label('Outside temp.'), outsidetemp_w]]) - ] + list(outsidetemp_widgets.values()) - ) + auto_width = widgets.Layout(width='auto') + result = WidgetGroup([ + [ + widgets.Label('Interval between openings (minutes)', layout=auto_width), + period, + ], + [ + widgets.Label('Duration of opening (minutes)', layout=auto_width), + interval, + ], + [ + widgets.Label('Inside temperature (℃)', layout=auto_width), + inside_temp, + ], + [ + widgets.Label('Outside temperature scheme', layout=auto_width), + outsidetemp_w, + ] + ]) + for sub_group in outsidetemp_widgets.values(): + result.add_pairs(sub_group.pairs()) + return result def _build_mechanical(self, node): - period = widgets.IntSlider(value=node.active.period, min=0, max=240, step=5) - interval = widgets.IntSlider(value=node.active.duration, min=0, max=240, step=5) q_air_mech = widgets.IntSlider(value=node.q_air_mech, min=0, max=1000, step=5) - def on_period_change(change): - node.active.period = change['new'] - - def on_interval_change(change): - node.active.duration = change['new'] - def q_air_mech_change(change): node.q_air_mech = change['new'] # TODO: Link the state back to the widget, not just the other way around. - period.observe(on_period_change, names=['value']) - interval.observe(on_interval_change, names=['value']) q_air_mech.observe(q_air_mech_change, names=['value']) - return widgets.VBox( - [ - widgets.HBox([widgets.Label('Frequency of opening (min)', - layout=widgets.Layout(width='150px')), period]), - widgets.HBox([widgets.Label('Duration of opening (min)', - layout=widgets.Layout(width='150px')), interval]), - widgets.HBox([widgets.Label('Flow rate (m(3)/h)', - layout=widgets.Layout(width='150px')), q_air_mech]), - ] - ) + auto_width = widgets.Layout(width='auto') + return widgets.VBox([widget_group([ + [ + widgets.Label('Flow rate (m³/h)', layout=auto_width), + q_air_mech, + ], + ])]) - def _build_month(self, node): + def _build_month(self, node) -> WidgetGroup: month_choice = widgets.Select(options=list(data.GenevaTemperatures.keys()), value='Jan') @@ -326,8 +361,10 @@ class ModelWidgets(View): node.outside_temp = data.GenevaTemperatures[change['new']] month_choice.observe(on_month_change, names=['value']) - return widget_group( - [[widgets.Label("Month"), month_choice]] + return WidgetGroup( + [ + [widgets.Label("Month"), month_choice], + ] ) def _build_activity(self, node): @@ -379,7 +416,7 @@ class ModelWidgets(View): def _build_ventilation(self, node): ventilation_widgets = { - 'Natural': self._build_window(node._states['Natural']), + 'Natural': self._build_window(node._states['Natural']).build(), 'Mechanical': self._build_mechanical(node._states['Mechanical']), } for name, widget in ventilation_widgets.items(): @@ -391,13 +428,14 @@ class ModelWidgets(View): def toggle_ventilation(value): for name, widget in ventilation_widgets.items(): + widget.layout.visible = False widget.layout.display = 'none' node.dcs_select(value) widget = ventilation_widgets[value] widget.layout.visible = True - widget.layout.display = 'block' + widget.layout.display = 'flex' ventilation_w.observe(lambda event: toggle_ventilation(event['new']), 'value') toggle_ventilation(ventilation_w.value) @@ -417,7 +455,7 @@ baseline_model = models.ExposureModel( concentration_model=models.ConcentrationModel( room=models.Room(volume=75), ventilation=models.WindowOpening( - active=models.PeriodicInterval(period=120, duration=120), + active=models.PeriodicInterval(period=120, duration=15), inside_temp=models.PiecewiseConstant((0,24),(293.15,)), outside_temp=models.PiecewiseConstant((0,24),(283.15,)), cd_b=0.6, window_height=1.6, opening_length=0.6, @@ -457,7 +495,7 @@ class CARAStateBuilder(state.StateBuilder): ) # Initialise the HVAC state s._states['Mechanical'].dcs_update_from( - models.HVACMechanical(models.PeriodicInterval(0, 0), 500.) + models.HVACMechanical(models.PeriodicInterval(period=24*60, duration=24*60), 500.) ) return s diff --git a/cara/models.py b/cara/models.py index 232cbb5a..8fcf242b 100644 --- a/cara/models.py +++ b/cara/models.py @@ -65,6 +65,8 @@ class PeriodicInterval(Interval): duration: int def boundaries(self) -> typing.Tuple[typing.Tuple[float, float], ...]: + if self.period == 0 or self.duration == 0: + return tuple() result = [] for i in np.arange(0, 24, self.period / 60): result.append((i, i+self.duration/60))