Skip to content

Draft: UI stability improvements and data catalog updates - #37

Draft
theoellingsen wants to merge 45 commits into
mainfrom
IntakeLoader
Draft

Draft: UI stability improvements and data catalog updates#37
theoellingsen wants to merge 45 commits into
mainfrom
IntakeLoader

Conversation

@theoellingsen

Copy link
Copy Markdown
Collaborator

Introduces stability improvements to the UI widget and updates the data catalog ingestion. Tagging @headmetal @charles-turner-1 @Whyborn just so you can have a quick look if you get the chance.

Key Changes:

  • Updated data.py to utilise the use_datastore function from the ACCESS-intake catalog package, removed scheduler set up.

UI Stability improvements:

  • Replaced pn.bind() with dedicated buttons to trigger dataset plotting.

  • Pre-loaded the dataset selector UI upon widget initialisation (toggling visibility when required) to resolve reliability issues.

  • Separated the plotting logic into two distinct functions for reference and user datasets to accommodate their different handling requirements.

Currently working on updating the plots so that some more useful plots can be generated, with more general bug fixes also to come.

…he intake catalogs use_datastore function

Making changes to _check_for_new_data and _build_new_catalog to use the intake catalogs use_datastore function, rather than using bespoke functions.
Adds logic to convert AliasedESMCatalog to raw ESMIntake catalog object if passed into the _build_data_object() function. Also allows for the plotting of reference datasets that are single model variants, previously only plotted datasets that had multiple model variants.
Improve reliability of dataset selection appearing when running widget

Force widget synchronisation rather than relying on .append()

Hopefully will stop issue with dataset selection not appearing as it will synchronise every time.

Revert "Force widget synchronisation rather than relying on .append()"

This reverts commit 47716bd.
Splits the plot_dataset function into 2 different functions, for user dataset and reference dataset. Update ref_figure_exists variable in correct spot so that the variable change in for the ref figure, should trigger the update. It doesn't actually fix the problem. Fixed some typos etc.,  as well.
Change from using pn.bind() function for updating plots, as it was not reliably working and seemed to be a major cause for the instability of the widget. Added plot dataset buttons instead, so the user can select a variable and decide when to replot. This appears to work much more reliably. Also made updates to the user feedback, so that there is more information when data is being loaded, etc.
Remove redundant code left from scheduler, all stuff that was unreachable before anyway

@charles-turner-1 charles-turner-1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple of minor suggestions and style things.. looking good though!

Comment thread pyproject.toml Outdated
Comment thread pyproject.toml Outdated
Comment thread src/med_diagnostics/test.py Outdated
Comment thread src/med_diagnostics/test.py Outdated
# Testing the _build_new_catalog function with a non-existent directory and a model type of "OM2"
#_build_new_catalog("blah not a directory", "ESM15")

session = med_diagnostics.session.CreateModelDiagnosticsSession(model_type='CM2', model_path='path/to/your/live/model/data/output', period=20) No newline at end of file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figure we'll need to create some sort of mock for this - again probably one to talk about next week

Comment thread src/med_diagnostics/ui.py Outdated
theoellingsen and others added 18 commits August 18, 2026 15:45
Co-authored-by: Charles Turner <52199577+charles-turner-1@users.noreply.github.com>
Co-authored-by: Charles Turner <52199577+charles-turner-1@users.noreply.github.com>
Co-authored-by: Charles Turner <52199577+charles-turner-1@users.noreply.github.com>
Co-authored-by: Charles Turner <52199577+charles-turner-1@users.noreply.github.com>
Add new plot options, allows user the choice between plotting a line graph and a heatmap. Adds choices for user to select x axis and y axis (when selecting a heatmap).
Adds button to remove plots, appends new plots rather than overwrites, groups them by whether they are user or reference plots
Add user options to select how data is sliced, finds which dimensions remain after x/y axis are chosen, and prompts the user to choose how they are sliced
Error handling for some user choices that break things and minor bug fixes
…ls etc)

Adds important information to plot caption, generated from if the data is sliced, and what model and dataset the plot is created from
…e doing that

Making more robust error handling that handles errors caused by invalid entries in the UI.
…rence datasets

Add functionality to plot user data overlayed with any number of reference datasets. Adds UI to add reference model datasets to a dictionary, and plots them.
add animation option to dropdown for model plotting

Revert "add animation option to dropdown for model plotting"

This reverts commit 39d59c4.

Revert "Attempt at adding animated plots"

This reverts commit 91456e2.

Reapply "Attempt at adding animated plots"

This reverts commit 2bc879a.

Reapply "add animation option to dropdown for model plotting"

This reverts commit b373623.

Add animation axes choices for user dataset

Fix so that HTML actually loads in widget

Trying animation without html with just the hvplot panel object, cleaner if it works

Add scrubber to animation

Lazy load the animation

Add reference animation functionality, and change colour map for animated plots to match xarray heatmap default

Update ref UI so that animation options appear when they are supposed to.

Initialise fig and fig animated in ref plot data button click function

ensure ref plot is actually plotting ref animation rather than user data animation

Update to only show the dimensions available to that variable in plot options

Update animation plots to use variable as title, and include captions with slice information.

Fix animation figure title, make animation bigger?
Adding new option to plot user vs ref data heatmaps on a grid together.
…data and reference data. Works for heat maps and line graphs

Add functionality allowing user to plot the difference between their data and reference data. Works for heat maps and line graphs
Add toggles for long/ short var names, for all different plot types

Add multiplot variable long name toggle

Add toggle for reference variable too

Add toggle for reference variable too
…h as SeaIce can load.

Cleaning stuff up a bit, adding docstrings, and ensuring datasets such as SeaIce can load. Also fixes issue with animation that caused plot to not be created for SeaIce datasets.

@charles-turner-1 charles-turner-1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one main comment from me about the length of the ui file - I think we'd probably benefit from simplifying that. I've added a few comments about how we might do that.

With that said, doing so before we have tests on it is a sketchy proposal, so let's not jump the gun there.

Comment thread src/med_diagnostics/ui.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely getting way too long - but let's not try to refactor/simplify before we've written tests.

Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +37 to +40
self.status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.warning_textbox = pn.widgets.StaticText(styles={'background': 'darkred', 'font-size': '18px', 'color': 'white', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.ref_status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.ref_warning_textbox = pn.widgets.StaticText(styles={'background': 'darkred', 'font-size': '18px', 'color': 'white', 'padding': '5px'}, margin=(10, 0, 10, 0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are duplicating the same styles, so you could consider doing something like

Suggested change
self.status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.warning_textbox = pn.widgets.StaticText(styles={'background': 'darkred', 'font-size': '18px', 'color': 'white', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.ref_status_textbox = pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.ref_warning_textbox = pn.widgets.StaticText(styles={'background': 'darkred', 'font-size': '18px', 'color': 'white', 'padding': '5px'}, margin=(10, 0, 10, 0))
self.STYLES = {
"status": pn.widgets.StaticText(styles={'background': 'lightblue', 'font-size': '18px', 'color': 'black', 'padding': '5px'}, margin=(10, 0, 10, 0)),
"warning": pn.widgets.StaticText(styles={'background': 'darkred', 'font-size': '18px', 'color': 'white', 'padding': '5px'}, margin=(10, 0, 10, 0)),
}
self.status_textbox = self.STYLES.get("status")
self.ref_status_textbox = self.STYLES.get("status")
self.warning_textbox = self.STYLES.get("warning")
self.ref_status_textbox = self.STYLES.get("warning")

Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +72 to +73
self.x_axis_dropdown = pn.widgets.Select()
self.y_axis_dropdown = pn.widgets.Select()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.x_axis_dropdown = pn.widgets.Select()
self.y_axis_dropdown = pn.widgets.Select()
self.x_axis_dropdown, self.y_axis_dropdown = pn.widgets.Select(), pn.widgets.Select()

Only because the x and y axis selects are sort of conceptually the same thing. I probably wouldn't want to merge the plot_type_dropdown here, even though it's also a select - if that makes sense?

Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +79 to +80
self.ref_x_axis_dropdown = pn.widgets.Select()
self.ref_y_axis_dropdown = pn.widgets.Select()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.ref_x_axis_dropdown = pn.widgets.Select()
self.ref_y_axis_dropdown = pn.widgets.Select()
self.x_axis_dropdown, self.y_axis_dropdown = pn.widgets.Select(), pn.widgets.Select()

In fact, it looks like there are probably a heap of these. I might do something like this:

    def _setup_plot_selectors(self):
        self.ref_plot_type_dropdown = pn.widgets.Select()
        self.x_axis_dropdown, self.y_axis_dropdown = pn.widgets.Select(), pn.widgets.Select()
        self.animation_axis_dropdown = pn.widgets.Select()

And then call that throughout.

Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +309 to +321
def _update_warning_text(self, text):

"""
Update text displayed in status_textbox widget. Private.

Parameters
----------
text : str
Text to be displayed in status_textbox
"""

# Update status_textbox with text
self.warning_textbox.value = str(text)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _update_warning_text(self, text):
"""
Update text displayed in status_textbox widget. Private.
Parameters
----------
text : str
Text to be displayed in status_textbox
"""
# Update status_textbox with text
self.warning_textbox.value = str(text)
def _update_warning_text(self, text):
"""
Update text displayed in status_textbox widget. Private.
Parameters
----------
text : str
Text to be displayed in status_textbox
"""
self.warning_textbox.value = str(text)

To have these show up as docstrings they need to be right below the definition line. You also don't need the comment below. I think the indentation might be off with these too?

Comment thread src/med_diagnostics/ui.py Outdated
"""Toggles the user variable dropdown options between short names and long name."""
if self.variable_toggle.value == True:
self.long_names = {}
for var in list(self.dataset.keys()):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for var in list(self.dataset.keys()):
for var in self.dataset.keys():

No need to coerce to a list here

Comment thread src/med_diagnostics/ui.py Outdated
self.variable_toggle.label = "Display Variable Short Names"
self.multiplot_variable_toggle.label = "Display Variable Short Names"
self.multiplot_variable_toggle.value = True
elif self.variable_toggle.value == False:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif self.variable_toggle.value == False:
else:

You've done if True above, so this is probably okay to write as an else - unless it can be None too.

Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +402 to +404
if self.ref_variable_toggle.value == True:
self.ref_long_names = {}
for var in list(self.ref_dataset.keys()):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.ref_variable_toggle.value == True:
self.ref_long_names = {}
for var in list(self.ref_dataset.keys()):
if self.ref_variable_toggle.value:
self.ref_long_names = {}
for var in self.ref_dataset.keys():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't put any more of this style of suggestion here so I don't swap you, but one to look out for!

Comment thread src/med_diagnostics/ui.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment on lines +485 to +488
self.ref_keys_button.button_type = 'success'

self.clear_ref_model_data_button.name = 'Clear reference model'
self.clear_ref_model_data_button.button_type = 'primary'
self.clear_ref_model_data_button.button_type = 'danger'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be extracted to shared helpers, eg.

Suggested change
self.ref_keys_button.button_type = 'success'
self.clear_ref_model_data_button.name = 'Clear reference model'
self.clear_ref_model_data_button.button_type = 'primary'
self.clear_ref_model_data_button.button_type = 'danger'
def _set_button_type(button, type):
button.value.button_type = type
...
self._set_button_type(self.ref_keys_button, 'success')

Honest, it doesn't change the number of lines of code much, but it does help with readability & prevent any potential mishaps with not doing the exact same thing.

Make widgets get style from dictionary rather than repeating code

Shouldn't change any functionality
@charles-turner-1

Copy link
Copy Markdown
Collaborator

@theoellingsen there's a lot to read here so I'm gonna get some in roads from the AI 😅

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Confirmed functional issues in session/UI logic and correctness issues in the new test suite (including duplicate test names and incorrect assertions) should be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR refactors the Panel-based UserInterface to improve widget stability (moving away from pn.bind() to explicit button-driven plotting flows), adds extensive UI test coverage, and updates the data-catalog ingestion path to use access_nri_intake.experiment.use_datastore (while removing the background scheduler from session startup).

Changes:

  • Refactored UserInterface plotting flows (user, reference, and overlay/multiplot), including slice-selection UI and hvplot-based animations.
  • Updated live catalog building/checking to rely on use_datastore and expanded supported model builders.
  • Added a large tests/test_UI.py suite and introduced Pixi workspace/deps metadata.
File summaries
File Description
tests/test_UI.py Adds comprehensive unit tests for UI flows, plotting validity, slicing UI, and callback wiring.
src/med_diagnostics/ui.py Major UI stability refactor: explicit buttons, prebuilt selection UI, reference + overlay plotting, and animations.
src/med_diagnostics/session.py Removes APScheduler usage and simplifies session lifecycle around a single initial data load.
src/med_diagnostics/data.py Switches catalog building to use_datastore, expands supported model types, and adjusts dataset loading behavior.
src/med_diagnostics/test.py Removes placeholder test file.
pyproject.toml Adds Pixi workspace/dependencies/tasks configuration.
.gitignore Ignores Pixi envs, coverage artifacts, and .vscode/.
.gitattributes Marks pixi.lock as generated/binary-merge and configures linguist settings.
Review details
  • Files reviewed: 5/9 changed files
  • Comments generated: 10
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/med_diagnostics/session.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread src/med_diagnostics/data.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread tests/test_UI.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread src/med_diagnostics/ui.py Outdated
Comment thread tests/test_UI.py Outdated
@charles-turner-1

Copy link
Copy Markdown
Collaborator

Copilots review comments mostly look sensible to me. Have a read through and see if they make sense to you Theo!

theoellingsen and others added 3 commits September 9, 2026 12:21
Co-authored-by: Charles Turner <52199577+charles-turner-1@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…eduler, which is now removed.

Fixes issue in session._get_data() function as well. Given scheduler was removed, self.model_data was always initialised as an empty list, so the check for new data became redundant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants