Skip to content

Commit

Permalink
Launching app
Browse files Browse the repository at this point in the history
  • Loading branch information
dvalters committed Mar 5, 2024
1 parent 16dd646 commit 0b4d151
Showing 1 changed file with 262 additions and 25 deletions.
287 changes: 262 additions & 25 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Set up the CSS
pn.extension(
sizing_mode="stretch_width",
css_files=["app/hazard.css"],
css_files=["app/hazards.css"],
notifications=True,
)

Expand Down Expand Up @@ -48,15 +48,15 @@ class tomrapApp(param.Parameterized):
The file input widget for the calibration CSV file.
""",
)
json_filename = param.String(
default="experiment_metadata.json",
metadata_file_input = param.Parameter(
doc="""
The filename which is given to the JSON file upon .
The file input widget for the metadata JSON file.
""",
)
figure_range_slider = param.Parameter(
json_filename = param.String(
default="experiment_metadata.json",
doc="""
The slider which is used for changing the range of days which are included in the Bokeh figure.
The filename which is given to the JSON file upon .
""",
)

Expand All @@ -78,9 +78,6 @@ def __init__(self, **params) -> None:

# Define default element sizes for the program
self.default_sizes = {
"figure_range_slider": {
"width": 500,
},
"bokeh_pane_row": {
"height": 500,
},
Expand Down Expand Up @@ -112,11 +109,6 @@ def __init__(self, **params) -> None:
),
"json_filename": "experiment_metadata.json",
"bokeh_figure": None,
"figure_range_slider": {
"start": 0,
"end": 100,
},
"sensor_names_table": pd.DataFrame(),
"last_points_number": 50,
"averages_table": pd.DataFrame(),
"csv_filename": "average_calibrated_pressures.csv",
Expand All @@ -138,6 +130,153 @@ def assign_default_data(self) -> None:
self.df_average_pressures = self.default_values["df_average_pressures"]


def update_metadata_input(self, metadata: dict) -> None:
"""
Function to update the input values in the input fields for experiment metadata.
"""
# Create datetime.date objects for the date picker input fields
for date_name in self.date_fields:
if date_name in metadata and type(metadata[date_name]) == str:
metadata[date_name] = datetime.datetime.strptime(
metadata[date_name],
"%Y-%m-%d",
).date()

metadata_input_names = [
"weighting_factor",

]
input_fields = [
self.weighting_factor,
]

for metadata_input_name, input_field in zip(metadata_input_names, input_fields):
if metadata_input_name in metadata:
input_field.value = metadata[metadata_input_name]


def view(self): # -> pn.Column | pn.Row:
"""
Function to create the layout of the Panel application.
"""
left_sidebar_divider = pn.pane.HTML(
margin=(10, 5, 10, 5),
height=1,
sizing_mode="stretch_width",
css_classes=["left_sidebar_divider"],
)

self.left_sidebar = pn.Column(
left_sidebar_divider,
pn.pane.Markdown(
"## Set Datasources",
margin=self.sidebar_title_margin,
),
left_sidebar_divider,
pn.pane.Markdown(
"### Set Exposure Data Folder",
margin=self.sidebar_title_margin,
),
self.pressures_file_input,
pn.pane.Markdown(
"### Set Geosure Data Folder",
margin=self.sidebar_title_margin,
),
self.calibration_file_input,
self.update_data_button,
left_sidebar_divider,
pn.pane.Markdown(
"### Set Parameters",
margin=self.sidebar_title_margin,
),
self.metadata_file_input,
self.update_metadata_button,

# Create the collapsible box for metadata input
pn.Card(
self.weighting_factor,
title="Edit Metadata",
collapsed=True,

),

pn.pane.Markdown(
"### Download Metadata",
margin=self.sidebar_title_margin,
),

self.param.json_filename,
left_sidebar_divider,
pn.layout.VSpacer(),
width=300,
margin=0,
css_classes=["bgs_blue_background"],
)
self.left_sidebar_show = self.left_sidebar

# We define the bokeh_pane sizing here because otherwise it shrinks
self.bokeh_pane_row = pn.Row(
self.bokeh_pane,
height=self.default_sizes["bokeh_pane_row"]["height"],
margin=5,
)

self.centre_view = pn.Column(
# Elements here are placed into separate rows and cols to allow us to specify sizes
self.bokeh_pane_row,
pn.Row(
pn.Column(
width=230,
),
pn.Column(
height=50,
),
align="center",
margin=5,
),
pn.Row(
pn.Column(
"# Additional Information",
# Placing self.param.csv_filename will create an input field
# for the csv filename which corresponds to the csv_download widget
),
pn.Column(
),
margin=5,
),
width=self.default_sizes["centre_view"]["width"],
margin=5,
)

self.right_sidebar = pn.Column(
pn.layout.VSpacer(),
width=250,
margin=10,
css_classes=["right_sidebar"],
)

# The overall layout is 2 rows, the header and the main page
ui_layout = pn.Column(
pn.Row(
self.left_sidebar_button,
self.toggle_page_size_button,
pn.layout.HSpacer(),
"#Extended Toolbox for Multihazard Risk Analysis in Python",
pn.layout.HSpacer(),
height=80,
css_classes=["custom_header", "bgs_blue_background"],
),
# The application is a row containing the sidebars and the centre view
pn.Row(
self.left_sidebar,
self.centre_view,
self.right_sidebar,
),
margin=0,
)
return ui_layout


def create_ui_elements(self) -> None:
"""
Function to create the UI elements of the application
Expand All @@ -163,32 +302,130 @@ def create_ui_elements(self) -> None:
self.pressures_file_input = pn.widgets.FileInput(
accept=".csv",
)
self.weighting_factor = pn.widgets.TextInput(
name="Weighting Factor",
)
self.calibration_file_input = pn.widgets.FileInput(
accept=".csv",
)
self.update_data_button = pn.widgets.Button(
name="Update Data",
)
self.update_metadata_button = pn.widgets.Button(
name="Update Metadata",
)
self.last_points_input = pn.widgets.IntInput(
name="Number of last points included in average",
value=self.default_values["last_points_number"],
)
self.update_table_button = pn.widgets.Button(
name="Update Table",
)
self.averages_table = pn.pane.DataFrame(
self.default_values["averages_table"],
index=False,
sizing_mode="stretch_both",
)

self.update_table_button.on_click(self.update_averages_table)
self.update_metadata_button.on_click(self.import_metadata)
# Metadata input and file download


# Centre view elements
# Bokeh figure
self.bokeh_pane = pn.pane.Bokeh(
sizing_mode="stretch_both",
)
self.figure_range_slider = pn.widgets.EditableRangeSlider(
name="Start/End",
start=self.default_values["figure_range_slider"]["start"],
end=self.default_values["figure_range_slider"]["end"],
value=tuple(self.default_values["figure_range_slider"].values()),
step=1,
# Set the width of the slider here rather than in the view
# because otherwise the slider shrinks too small
width=self.default_sizes["figure_range_slider"]["width"],
)

@pn.depends("last_points_input.value", watch=True)
def update_averages_table(self, *args) -> None:
"""
Function to update the data which is shown within the pandas dataframe table of the GUI.
The function takes *args as it may be passed an event variable which we do not use.
"""
# If there is currently a bokeh figure
if self.bokeh_pane.object:
# Get the average of the last x points
print("Updating Averages table onclick")


def toggle_left_sidebar(self, *args) -> None:
"""
Function to toggle the left sidebar to show/hide.
The function takes *args as it may be passed an event variable which we do not use.
"""
# If it is currently shown, then remove it
# We have to update an element's size other than the sidebar, otherwise Panel doesn't format
# the sidebar as it should be, changing an element seems to refresh it
if self.left_sidebar.visible:
self.left_sidebar.visible = False
self.panel_refresh_widget.height = self.panel_refresh_widget.height - 1
else:
self.left_sidebar.visible = True
self.panel_refresh_widget.height = self.panel_refresh_widget.height + 1


def toggle_page_size(self, *args) -> None:
"""
Function to toggle the size of elements on the page.
The function takes *args as it may be passed an event variable which we do not use.
"""
# Get the elements that need to be adjusted in a list
page_objects = [
self.bokeh_pane_row,
self.centre_view,
]

# If the page sizing is currently large, we reduce it
if self.page_size_large:
page_scale = 0.8
self.page_size_large = False
self.toggle_page_size_button.name = self.default_values["toggle_page_size_button"]["increase"]
else:
page_scale = 1
self.page_size_large = True
self.toggle_page_size_button.name = self.default_values["toggle_page_size_button"]["reduce"]

for page_object, object_sizes in zip(page_objects, list(self.default_sizes.values())):
if "height" in object_sizes:
page_object.height = round(object_sizes["height"] * page_scale)
if "width" in object_sizes:
page_object.width = round(object_sizes["width"] * page_scale)

def import_metadata(self, *args) -> None:
"""
Function to import metadata from a provided file into the current metadata input fields.
The function takes *args as it may be passed an event variable which we do not use.
"""
metadata_file_value = self.metadata_file_input.value
if metadata_file_value:
# The file input value is the actual data from the selected file
# It is a bytes object and so needs to be converted to a JSON object using UTF-8
json_file_data = self.metadata_file_input.value.decode("utf-8")
imported_metadata = json.loads(json_file_data)

self.update_metadata_input(imported_metadata)
self.json_filename = self.metadata_file_input.filename

# If data is currently loaded into the bokeh figure
if self.bokeh_pane.object:
self.update_figure_metadata(imported_metadata)

self.panel_notification(
self.default_values["metadata_status"]["active"],
"success",
)
logger.info("Processing metadata from %s", self.metadata_file_input.filename)

else:
self.panel_notification(
"Please upload a metadata JSON file",
"error",
)


def start_tomrap_app(local: bool = False): # -> pn.Column | pn.Row:
def start_tomrap_app(local: bool = False): # -> pn.Column | pn.Row: (Needs Python 3.10 for | operator)
"""
Function to run the application.
local determines if the app should start a local server
Expand Down

0 comments on commit 0b4d151

Please sign in to comment.