diff --git a/.gitattributes b/.gitattributes index cb6403b9..ab03fb2f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ # Keep the third-party parser separate in reviews and preserve upstream bytes. src/bin/nlohmann/json.hpp linguist-vendored src/bin/nlohmann/LICENSE.MIT linguist-vendored -whitespace +gui/vendor/plotly-cartesian.min.js linguist-vendored +gui/vendor/plotly.css linguist-vendored +gui/vendor/plotly-LICENSE.txt linguist-vendored -whitespace diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml index 8befa501..f951d568 100644 --- a/.github/workflows/build-wasm.yml +++ b/.github/workflows/build-wasm.yml @@ -90,6 +90,9 @@ jobs: test -s _site/api/index.html test -s _site/gui/index.html test -s _site/gui/wasm/intarna.wasm + test -s _site/gui/vendor/plotly-cartesian.min.js + test -s _site/gui/vendor/plotly.css + test -s _site/gui/licenses/Plotly.txt - uses: actions/upload-artifact@v4 with: name: intarna-pages-preview diff --git a/ChangeLog b/ChangeLog index eb63eea4..54b1340f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -14,6 +14,13 @@ ## Interface and handling +- refine GUI seed and CSV control widths, filter examples by task, cap result + table height and use dark active tabs for additional output views (PR #303 review) + +- compact and left-align GUI parameter rows, split CSV controls into their own + tab, describe every additional output format and plot energy/probability files + with sequence names, source tabs and PNG export (PR #287 review) + - reorganize GUI parameters, add conditional seed/accessibility controls and grouped CSV defaults, expand FASTA limits and refine result headers (PR #299) @@ -289,6 +296,34 @@ energies, restricted partition sums, and trackers. ################################################################################ 261026 Alexander Mitrofanov + * gui/app.js, gui/{index.html,styles.css}, scripts/wasm/test-browser.py + * place Explicit seeds beside a half-width Seed constraint selector and halve + the CSV sort selector width; verify desktop/mobile control geometry + * show only examples for the selected task, retain complete CSV downloads while + scrolling result tables above 500 px, and match output tab selection styling + to the parameter tabs (PR #303 maintainer review) + * gui/docs/{json-workflow,step3-frontend-ui,step4-results-and-deployment}.md + * document task-specific examples, compact controls and the bounded result table + +261026 Alexander Mitrofanov + * gui/{app,options,sessions,output-visualization}.js, + gui/{index.html,styles.css} + * compact bounded parameter rows while preserving their grouping and align + wrapped labels above left-aligned controls + * move CSV sorting/column selection into a dedicated tab between Output and + Model & Energy; add content/use tooltips to all additional output formats + + show sequence-aware Plotly profiles, heatmaps and selected-spot bars beside + native file content; add header downloads and verified PNG export + * .gitattributes, gui/vendor/, scripts/wasm/stage.sh, + .github/workflows/build-wasm.yml + + vendor and checksum Plotly.js Cartesian 4.1.2, stage its MIT license and + verify both assets in the combined Pages artifact + * gui/tests/output-visualization.test.mjs, scripts/wasm/test-browser.py, + README.md, gui/docs/ + + cover signed categorical axes, file/sequence mapping, malformed data, + responsive compact layout, plot rendering and PNG downloads in real + Chromium/Firefox with the existing WASM engine + * address https://github.com/BackofenLab/IntaRNA/pull/287#issuecomment-6416573560 * src/IntaRNA/SeededPartitionFunction.cpp * Select sparse batch anchors by scanning admitted seeds instead of probing every possible pair in every source span; retain the grid lookup for dense diff --git a/README.md b/README.md index ce94edbd..aeaab169 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Predict short RNA pairs or evaluate supplied structures locally with the [browse The browser accepts up to 50 records per strand, 2–5,000 nucleotides each, with longest selected query × longest selected target at most 1,000,000. Prediction defaults to 10 interactions per pair and includes opening and seed energies; evaluation selects all energy columns. Computed runs stay in separate, foldable sessions under Results in the current tab. Name and select sessions to restore their inputs and compare results; editing or resetting parameters keeps earlier runs. Select an interaction to view it in vaRRI, or download a session’s ZIP with inputs, compiled versions, reproducible parameters and all outputs. Sessions are temporary and are cleared when the page is reloaded. +Minimum-energy and interaction-probability output files include local Plotly visualizations with PNG export; every additional file retains its original text view and download. See the [WebAssembly build instructions](gui/docs/step1-wasm-compilation.md) and [interface/deployment notes](gui/docs/step4-results-and-deployment.md). diff --git a/gui/app.js b/gui/app.js index 446a7f60..c95fcff2 100644 --- a/gui/app.js +++ b/gui/app.js @@ -90,16 +90,22 @@ function fieldRow(body, fields, suffix = '') { for (const definition of GROUPS) { const body = group(definition.key, definition.label); if (definition.key === 'seed-options') { + const primary = element('div', '', 'seed-primary-row'); const wrapper = element('div', '', 'field seed-mode'); const label = element('label', 'Seed constraint'); label.htmlFor = 'seed-mode'; const select = element('select'); select.id = 'seed-mode'; for (const [value, name] of [['none', 'Disable seed constraint'], ['explicit', 'Explicit seed'], ['identify', 'Identify seeds']]) { const option = element('option', name); option.value = value; select.append(option); } - select.value = 'identify'; wrapper.append(label, select); body.append(wrapper); + select.value = 'identify'; wrapper.append(label, select); + primary.append(wrapper, renderField(FIELDS.find((field) => field.key === 'seedTQ'))); + body.append(primary); } if (definition.rows) { - for (const row of definition.rows) fieldRow(body, row); + for (const row of definition.rows) { + if (definition.key === 'seed-options' && row[0].key === 'seedTQ') continue; + fieldRow(body, row); + } } else fieldRow(body, definition.fields); if (definition.strand) { const p = definition.strand; @@ -120,10 +126,10 @@ function checkbox(container, key, labelText, description) { if (description) label.title = description; label.append(input, document.createTextNode(labelText)); container.append(label); return input; } -const outputBody = byId('output-options').querySelector('.group-body'); -outputBody.append(element('h3', 'CSV output columns', 'subheading')); -outputBody.append(element('p', 'seq1 is the target; seq2 is the query. Hover over a column name for details. Select columns for the table and CSV download; table visibility can be changed after computation.', 'input-help')); -const selectionTools = element('div', '', 'selection-tools'); outputBody.append(selectionTools); +const csvBody = byId('csv-options').querySelector('.group-body'); +csvBody.append(element('h3', 'CSV output columns', 'subheading')); +csvBody.append(element('p', 'seq1 is the target; seq2 is the query. Hover over a column name for details. Select columns for the table and CSV download; table visibility can be changed after computation.', 'input-help')); +const selectionTools = element('div', '', 'selection-tools'); csvBody.append(selectionTools); const selectColumns = (selection) => { csvOrder = [...selection]; for (const input of csvInputs) input.checked = selection.includes(input.value); inputsChanged(); }; @@ -142,16 +148,17 @@ for (const group of CSV_GROUPS) { inputsChanged(); }); legend.append(button); } - const picker = element('div', '', 'checkbox-grid csv-columns'); section.append(legend, picker); outputBody.append(section); + const picker = element('div', '', 'checkbox-grid csv-columns'); section.append(legend, picker); csvBody.append(section); for (const key of group.keys) { const column = CSV_CATALOG.find((column) => column.key === key); csvInputs.push(checkbox(picker, key, key, column.description)); } } +const outputBody = byId('output-options').querySelector('.group-body'); outputBody.append(element('h3', 'Additional output files', 'subheading')); outputBody.append(element('p', 'Reports are computed with the same settings. ASCII, detailed and ensemble reports each run a fresh engine instance and can take extra time.', 'input-help')); const outputPicker = element('div', '', 'checkbox-grid'); outputBody.append(outputPicker); -for (const output of EXTRA_OUTPUTS) outputInputs.push(checkbox(outputPicker, output.key, output.label)); +for (const output of EXTRA_OUTPUTS) outputInputs.push(checkbox(outputPicker, output.key, output.label, output.description)); const generalBody = group('general-options', 'CLI-only'); generalBody.append(element('p', 'Process-level settings (threads, log paths, parameter-file paths and terminal help) are available in the CLI.')); const docs = element('a', 'Full CLI parameter documentation'); docs.href = 'https://github.com/BackofenLab/IntaRNA#readme'; generalBody.append(docs); @@ -172,6 +179,9 @@ function updateLength(name) { } function updateAvailability() { const evaluation = getTask() === 'evaluation'; + for (const button of byId('example-options').querySelectorAll('[data-example-task]')) { + button.hidden = button.dataset.exampleTask !== getTask(); + } byId('evaluation-input').hidden = !evaluation; byId('task-help').textContent = evaluation ? 'Evaluate supplied structures for one query and one target. Prediction constraints are ignored.' : 'Predict interactions for all query–target sequence pairs.'; byId('compute').textContent = evaluation ? 'Evaluate interactions' : 'Compute interactions'; @@ -313,6 +323,7 @@ async function loadExamples() { container.replaceChildren(); for (const example of examples) { const button = element('button'); button.type = 'button'; + button.dataset.exampleTask = example.request.task; button.append(element('strong', example.name), element('span', example.description)); button.addEventListener('click', () => { if (byId('inputs').disabled) return; @@ -321,6 +332,7 @@ async function loadExamples() { }); container.append(button); } + updateAvailability(); } catch (error) { container.replaceChildren(element('p', `${error.message} Reload to try again.`, 'input-help')); } } loadExamples(); diff --git a/gui/docs/json-workflow.md b/gui/docs/json-workflow.md index df5f7dfb..1c2d9dee 100644 --- a/gui/docs/json-workflow.md +++ b/gui/docs/json-workflow.md @@ -68,7 +68,8 @@ uses native defaults for omitted parameters. Inactive controls keep their edits in the form for switching modes, but their values are omitted from executed and stored settings. Previously computed sessions remain available. Empty FASTA fields show the short demonstration sequences as placeholders only. The Examples -disclosure menu follows the Input Parameters label. +disclosure menu follows the Input Parameters label and lists only examples for +the selected Prediction or Evaluation task. Maintainers edit `gui/examples/index.json` (an array of local JSON filenames). Each listed file contains `name`, `description`, and `configuration`, where the diff --git a/gui/docs/step3-frontend-ui.md b/gui/docs/step3-frontend-ui.md index e0436b75..3e41faf5 100644 --- a/gui/docs/step3-frontend-ui.md +++ b/gui/docs/step3-frontend-ui.md @@ -14,8 +14,13 @@ groups, followed by the compute button, results, additional files and versions. All working content is left aligned; two equally sized strand editors collapse into one column on mobile. The shared institutional header is the strongest visual element. The wrapping tab bar identifies parameter groups. Numeric inputs -are 9 rem wide within a responsive grid of compact controls; text/data fields -retain useful width. +are at most 9 rem wide within bounded, left-aligned row grids; select/text inputs +use compact type-specific tracks and text/data fields retain useful width. Rows +keep their scientific grouping without stretching across the complete panel, +use 16 px horizontal gaps and bottom-align labels above their controls. +The Seed tab places the constraint selector (12 rem) beside Explicit seeds on +desktop; the CSV sort selector uses a 19 rem track. Both controls stack at mobile +widths. The institutional link reads “Bioinformatics - University of Freiburg” and opens the group website. Footer and table notes use the available width, wrapping only when the viewport requires it. @@ -24,7 +29,7 @@ when the viewport requires it. Group logo IntaRNA package / RNA–RNA interaction analysis University logo RNA–RNA interactions Computations stay on your device Task: Prediction / Evaluation -[ Sequences | Seed | Output | Model & Energy | Query | Target | CLI-only ] +[ Sequences | Seed | Output | CSV | Model & Energy | Query | Target | CLI-only ] [ Active panel: e.g. query + target, start indices, evaluation structures ] Compute Input Parameters: Examples / Store / Load / Reset Results / Show-Hide / name / ZIP download / delete @@ -49,7 +54,10 @@ one), and accepts 0–100. Zero enables accessibility-only output. the worker boundary. Query/target retain their distinct interior-loop defaults (16 and 10). Meta-options such as `acc`, `accW`, `accL`, `intLenMax` and `intLoopMax` are represented by per-strand controls to avoid conflicting values. -FASTA headers supply sequence names instead of `qId`/`tId`. `outCsvSort` selects a native download sort column; table sorting is independent. +FASTA headers supply sequence names instead of `qId`/`tId`. The dedicated CSV +tab contains `outCsvSort` and the interaction-table column choices; Output keeps +prediction filters and additional file formats. `outCsvSort` selects a native +download sort column; table sorting is independent. CSV uses a fixed semicolon delimiter. Process settings (threads, file/log paths, parameter files, help/version commands) are replaced by the worker lifecycle, local data inputs, downloads and displayed @@ -68,6 +76,8 @@ Function, with per-group selection actions and name-only labels with description tooltips. Prediction adds ED1, ED2, seedE, seedStart1 and seedStart2 to the native CSV columns; evaluation selects all energy outputs. JSON records these browser defaults explicitly so native replay retains the same output. +Every additional output checkbox has a descriptive tooltip covering its content, +format and intended use, including the model restrictions shown in its label. Prediction accepts plain sequence or up to 50 FASTA records per strand. Evaluation requires one query and target and 1–100 predefined structures. diff --git a/gui/docs/step4-results-and-deployment.md b/gui/docs/step4-results-and-deployment.md index 0d665211..451181bf 100644 --- a/gui/docs/step4-results-and-deployment.md +++ b/gui/docs/step4-results-and-deployment.md @@ -23,10 +23,30 @@ This is relative suboptimality, not a free-energy change or probability. Every heading is a keyboard-operable sort button with `aria-sort`. Search covers all returned values. A separate checklist shows/hides table columns after the run; it does not change selected CSV output. All identifiers, output previews -and diagnostics render with `textContent`. Selected extra files appear below the -table with bounded text previews and complete downloads, including SVG source. +and diagnostics render with `textContent`. The table has a 500 px maximum height +with vertical scrolling for longer results and a sticky heading; CSV downloads +still contain every selected row. Selected extra files appear below the +table with their original filename, covered sequence/sequence-pair names, a +header-level download, and bounded text previews, including SVG source. Empty output is successful and still permits CSV/accessibility downloads. +## Additional output visualizations + +Plot-capable additional CSV files open on a Visualization tab, with the original +File Content in an adjacent tab. Query/target minimum-energy and spot-probability +profiles use line plots; pair minimum-energy and all-spot probability matrices +use heatmaps; explicitly requested spot probabilities use a bar plot. Plot axes +use the literal coordinate strings from IntaRNA rather than numerical scales, so +signed indices cross from -1 to 1 without inventing coordinate zero. Plot titles +and additional-file headers identify the target/query records covered by native +`-t#q#` or `-s#` filename suffixes. + +Plotly renders locally from the pinned Cartesian bundle. A separate Export PNG +button produces a 1400 × 850 image for the active plot; the normal file download +and reproducible ZIP retain untouched native data. Plots render lazily when their +file is unfolded, resize with restored sessions, and are purged when a session is +deleted. Non-plot formats keep the direct content view. + ## Selected interaction in vaRRI Each row has a keyboard-accessible View button; clicking a row also selects it. @@ -91,15 +111,20 @@ python3 scripts/wasm/test-browser.py http://127.0.0.1:8765 --browser firefox ``` The staging script includes matched WASM assets, only the application source -files needed at runtime, compiled version metadata and dependency notices. Build-time `build-info.json` +files needed at runtime, compiled version metadata and dependency notices. It +also verifies and stages the pinned Plotly.js 4.1.2 Cartesian bundle and MIT +license; no CDN is used. Build-time `build-info.json` and `build-info.txt` record actual CLI IntaRNA/ViennaRNA/Boost versions, source commit and tracked modifications, SDK version and asset SHA-256 digests. Staging checks these digests and preserves the original metadata; it never replaces the compiled revision with a later staging checkout. The footer displays versions. It needs the same activated SDK and dependency build directory as the build. -No compiled artifacts are committed. HTTP(S), module workers and WebAssembly +No generated WASM artifacts are committed; the reviewed minified Plotly browser +distribution is the only new runtime artifact. HTTP(S), module workers and WebAssembly are required; `file://` and legacy browsers are unsupported. No backend, -SharedArrayBuffer, credentials, CDN or cross-origin headers are needed. +SharedArrayBuffer, credentials, CDN or cross-origin headers are needed. CSP +keeps scripts/styles same-origin and allows ephemeral blob images solely for +PNG export. The existing repository already publishes its user guide and API with Pages. Publishing a second independent artifact would overwrite them. Therefore @@ -156,6 +181,21 @@ GitHub's platform checks independently cover GCC 14 release/debug and Apple Clang. Deployment itself occurs only after merge; local browser tests do not claim a live Pages deployment. +### PR #287 visualization review verification, 2026-10-26 + +- `npm test --prefix gui`: all 25 protocol, results, output-visualization, + archive, upload and worker tests pass. Profile and matrix cases cover signed + categorical coordinates, sequence names, missing values and malformed data. +- Playwright 1.64.0 with Chromium 156 and Firefox 157 passes the full real-WASM + suite, including the compact desktop/mobile parameter layout, separate CSV + tab, output tooltips, native-file headers, Plotly rendering and a PNG whose + signature is checked after download. The staged GUI uses the matching + revision `7378801` engine; scientific code and compiled assets are unchanged. +- Desktop screenshots for every parameter tab and the completed session were + visually reviewed; the 390 px viewport has no document overflow. +- Plotly 4.1.2 is pinned by npm integrity and bundle SHA-256, staged locally with + its MIT license. Shell/Python syntax and `git diff --check` pass. + ### PR #287 review verification, 2026-10-21 - Emscripten 4.0.23 rebuild using the pinned official dependency archives passes diff --git a/gui/index.html b/gui/index.html index a77b74e7..275f8b1f 100644 --- a/gui/index.html +++ b/gui/index.html @@ -4,10 +4,12 @@ - + IntaRNA · RNA–RNA interactions + + @@ -73,7 +75,7 @@