Draft: UI stability improvements and data catalog updates - #37
Draft: UI stability improvements and data catalog updates#37theoellingsen wants to merge 45 commits into
Conversation
…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.
setting figure_exists to true
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
left a comment
There was a problem hiding this comment.
Left a couple of minor suggestions and style things.. looking good though!
| # 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 |
There was a problem hiding this comment.
I figure we'll need to create some sort of mock for this - again probably one to talk about next week
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.
… reference model 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
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
This is definitely getting way too long - but let's not try to refactor/simplify before we've written tests.
| 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)) |
There was a problem hiding this comment.
These are duplicating the same styles, so you could consider doing something like
| 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") | |
| self.x_axis_dropdown = pn.widgets.Select() | ||
| self.y_axis_dropdown = pn.widgets.Select() |
There was a problem hiding this comment.
| 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?
| self.ref_x_axis_dropdown = pn.widgets.Select() | ||
| self.ref_y_axis_dropdown = pn.widgets.Select() |
There was a problem hiding this comment.
| 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.
| 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) |
There was a problem hiding this comment.
| 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?
| """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()): |
There was a problem hiding this comment.
| for var in list(self.dataset.keys()): | |
| for var in self.dataset.keys(): |
No need to coerce to a list here
| 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: |
There was a problem hiding this comment.
| 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.
| if self.ref_variable_toggle.value == True: | ||
| self.ref_long_names = {} | ||
| for var in list(self.ref_dataset.keys()): |
There was a problem hiding this comment.
| 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(): |
There was a problem hiding this comment.
I won't put any more of this style of suggestion here so I don't swap you, but one to look out for!
| 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' |
There was a problem hiding this comment.
These could be extracted to shared helpers, eg.
| 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
e4bde03 to
5bc26ba
Compare
Adding testing for ui.py
6da3dc9 to
247d63a
Compare
|
@theoellingsen there's a lot to read here so I'm gonna get some in roads from the AI 😅 |
There was a problem hiding this comment.
🟡 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
UserInterfaceplotting flows (user, reference, and overlay/multiplot), including slice-selection UI and hvplot-based animations. - Updated live catalog building/checking to rely on
use_datastoreand expanded supported model builders. - Added a large
tests/test_UI.pysuite 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.
|
Copilots review comments mostly look sensible to me. Have a read through and see if they make sense to you Theo! |
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.
f7c2df8 to
2cd8cfc
Compare
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:
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.