diff --git a/ChangeLog b/ChangeLog index f785ee9c..3caa4217 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,9 @@ ## Interface and handling +- select and visualize the first rank-sorted CopomuS mutation automatically + when browser results arrive (#317) + - select the first browser result and show its interaction visualization automatically; reuse CSV help text as result-header tooltips and store bundled examples in directly importable/exportable parameter JSON (#315) @@ -345,6 +348,15 @@ energies, restricted partition sums, and trackers. ################################################################################ ################################################################################ +261027 Alexander Mitrofanov + * gui/copomus.js, scripts/wasm/test-browser.py + * Select the first rank-sorted CopomuS mutation after populating the browser + result table and immediately show its vaRRI and energy-plot visualizations; + restore both after folding and reopening the session (issue #317). + * gui/{index.html,docs/copomus.md} + * Document the automatic initial mutation selection in the interface and + CopomuS browser guide. + 261027 Alexander Mitrofanov * gui/{results.js,sessions.js,index.html}, scripts/wasm/test-browser.py * Select the first energy-sorted interaction when a computation completes and diff --git a/gui/copomus.js b/gui/copomus.js index b9e2ddfd..97653ea4 100644 --- a/gui/copomus.js +++ b/gui/copomus.js @@ -47,6 +47,7 @@ class MutationVisualization { constructor(root) { this.root = root; this.viewer = new InteractionVisualization(root, 'mutation-'); + this.frame = this.viewer.frame; this.title = root.querySelector('[data-role=mutation-visualization-title]'); this.plot = root.querySelector('[data-role=mutation-energy-plot]'); } @@ -111,7 +112,7 @@ export class CopomusResultsView { label.append(input, document.createTextNode(key)); this.picker.append(label); } this.search.value = ''; this.key = this.columns.includes('rank') ? 'rank' : this.columns[0]; this.direction = 1; - this.root.hidden = false; this.render(); return this.candidates.length; + this.root.hidden = false; this.render(true); return this.candidates.length; } selectedCandidates() { @@ -125,8 +126,16 @@ export class CopomusResultsView { }); } - render() { + render(selectFirst = false) { const rows = this.selectedCandidates(), columns = this.columns.filter((key) => this.visible.has(key)); + // A completed design opens on the top-ranked mutation; later table changes + // keep that selection, matching prediction and evaluation result handling. + if (selectFirst && rows.length) { + this.selected = rows[0]; + // Session views populate before their template is attached. Defer Plotly + // to the session-expansion hook in that case so it can measure its panel. + if (this.root.isConnected) this.visualization.show(this.selected, this.request); + } this.header.replaceChildren(); const viewHeading = document.createElement('th'); viewHeading.scope = 'col'; viewHeading.setAttribute('aria-label', 'Mutation visualization'); this.header.append(viewHeading); for (const key of columns) { diff --git a/gui/docs/copomus.md b/gui/docs/copomus.md index 8c037d4f..e9c74d67 100644 --- a/gui/docs/copomus.md +++ b/gui/docs/copomus.md @@ -37,16 +37,17 @@ folding runs; larger screens remain available through `python/CopomuS.py`. ## Results and reproducibility -The sortable/filterable table contains the complete extended CopomuS TSV. Select -**View** to show the wild-type interaction once in vaRRI, with both designed -substitutions annotated at their original signed sequence coordinates in dark -green. A compact Plotly bar chart beside it compares the `ww`, `wm`, `mw` and -`mm` interaction energies and marks `ww` with a dotted baseline; unavailable -interactions remain empty. Session ZIPs contain `parameter.json` and -`copomus-mutations.tsv`. The JSON keeps native IntaRNA settings and records the -task and CopomuS settings under `gui`, so Load restores the complete editable -run. As with other GUI sessions, all data stays in the tab and disappears on -reload unless downloaded. +The sortable/filterable table contains the complete extended CopomuS TSV. The +first rank-sorted mutation is selected automatically when results arrive; select +another row or **View** button to change it. The visualization shows the wild-type +interaction once in vaRRI, with both designed substitutions annotated at their +original signed sequence coordinates in dark green. A compact Plotly bar chart +beside it compares the `ww`, `wm`, `mw` and `mm` interaction energies and marks +`ww` with a dotted baseline; unavailable interactions remain empty. Session ZIPs +contain `parameter.json` and `copomus-mutations.tsv`. The JSON keeps native +IntaRNA settings and records the task and CopomuS settings under `gui`, so Load +restores the complete editable run. As with other GUI sessions, all data stays +in the tab and disappears on reload unless downloaded. The WASM workflow vendors no generated runtime files in Git. Build staging downloads Pyodide 314.0.7 from its npm release, verifies the pinned SHA-256, diff --git a/gui/index.html b/gui/index.html index d982ccb6..e6876e68 100644 --- a/gui/index.html +++ b/gui/index.html @@ -109,7 +109,7 @@

Compensatory mutation settings

Compensatory-mutation visualization

Select View on a mutation to inspect its annotated interaction and energy states.

-

vaRRI shows the wild-type interaction with the designed query and target substitutions annotated in dark green. The chart compares the wild-type, single-mutant and compensatory energies.

+

The first ranked mutation is selected automatically. vaRRI shows the wild-type interaction with the designed query and target substitutions annotated in dark green. The chart compares the wild-type, single-mutant and compensatory energies.

diff --git a/scripts/wasm/test-browser.py b/scripts/wasm/test-browser.py index b8fdeadf..e12d1066 100644 --- a/scripts/wasm/test-browser.py +++ b/scripts/wasm/test-browser.py @@ -724,16 +724,18 @@ def predict(): priority_up = page.get_by_role('button', name='Move Query accessibility earlier in ranking priority') priority_up.click() priority_up.click() + mutation_visualization_count = len(visualization_urls) predict() - mutation_session = active + mutation_session = page.locator(f'.session[data-session-id="{active.get_attribute("data-session-id")}"]') mutation_view = mutation_session.locator('[data-role=mutation-results-view]') expect(mutation_view).to_be_visible() expect(mutation_view.locator('tbody tr')).to_have_count(1) expect(mutation_view.locator('tbody tr')).to_contain_text('G1C&C10G') - mutation_view.locator('tbody tr').click() + expect(mutation_view.locator('tbody tr button')).to_have_attribute('aria-pressed', 'true') expect(mutation_view.locator('[data-role=mutation-varri-frame]')).to_be_visible() expect(mutation_view.locator('[data-role=mutation-energy-plot]')).to_be_visible() expect(mutation_view.locator('[data-role=mutation-energy-plot] .barlayer')).to_be_visible() + assert len(visualization_urls) == mutation_visualization_count + 1 assert mutation_view.locator('[data-role=mutation-energy-plot] .xtick').all_text_contents() == ['ww', 'wm', 'mw', 'mm'] expect(mutation_view.locator('[data-role=mutation-energy-plot] .ytitle')).to_contain_text('E [kcal/mol]') mutation_url = mutation_view.locator('[data-role=mutation-varri-frame]').get_attribute('src') @@ -741,6 +743,12 @@ def predict(): assert mutation_query['sequence'] == ['CCCUCCUCCC&GGGAGGAGGG'] assert mutation_query['mutations'] == ['1:10G:006400,2:1C:006400'] assert mutation_view.locator('[data-role=mutation-varri-frame]').bounding_box()['x'] < mutation_view.locator('[data-role=mutation-energy-plot]').bounding_box()['x'] + mutation_session.locator('[data-role=toggle]').click() + expect(mutation_session.locator('[data-role=panel]')).not_to_be_visible() + mutation_session.locator('[data-role=toggle]').click() + expect(mutation_view.locator('[data-role=mutation-varri-frame]')).to_be_visible() + expect(mutation_view.locator('[data-role=mutation-energy-plot]')).to_be_visible() + assert len(visualization_urls) == mutation_visualization_count + 2 if args.screenshot: path = Path(args.screenshot) page.screenshot(path=str(path.with_name(f'{path.stem}-mutation.png')), full_page=True)