From e1a9e10902de4d927f4c2c87b6c27e6303fa1ead Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Wed, 16 Jul 2025 18:48:48 -0400 Subject: add js version rendering. still needs download --- .gitignore | 1 + csv.mjs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 41 +++++++++++++++++++++++++++++++ main.css | 7 ++++++ main.mjs | 7 ++++++ pnit-form.mjs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ worker.js | 53 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 265 insertions(+) create mode 100644 .gitignore create mode 100644 csv.mjs create mode 100644 index.html create mode 100644 main.css create mode 100644 main.mjs create mode 100644 pnit-form.mjs create mode 100644 worker.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15d7aef --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +inputs diff --git a/csv.mjs b/csv.mjs new file mode 100644 index 0000000..b096ab4 --- /dev/null +++ b/csv.mjs @@ -0,0 +1,79 @@ +export default class { + constructor() { + this.state = this.awaitingTokenOrComma; + this.inProgress = ''; + this._rowResults = []; + this._results = []; + } + + push(c) { + this.state(c); + } + + get results() { + return this._results.concat([this._rowResults.concat([this.inProgress])]); + } + + awaitingTokenOrComma(c) { + switch (c) { + case ',': + // empty column + this._rowResults.push(''); + break; + case '\n': + this._results.push(this._rowResults); + this._rowResults = []; + break; + default: + if (/\s/.test(c)) { + return; + } + this.inProgress += c; + this.state = this.awaitingComma; + } + } + + awaitingComma(c) { + switch (c) { + case ',': + this._rowResults.push(this.inProgress); + this.inProgress = ''; + this.state = this.awaitingTokenOrComma; + break; + case '\r': + break; + case '\n': + this._results.push(this._rowResults.concat(this.inProgress)); + this._rowResults = []; + this.inProgress = ''; + this.state = this.awaitingTokenOrComma; + break; + case '"': + this.state = this.checkForDoubleQuote; + break; + default: + this.inProgress += c; + } + } + + checkForDoubleQuote(c) { + this.inProgress += c + switch (c) { + case '"': + this.state = this.awaitingComma; + break; + default: + this.state = this.awaitingQuote; + } + } + + awaitingQuote(c) { + switch (c) { + case '"': + this.state = this.awaitingComma; + break; + default: + this.inProgress += c; + } + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..8ae4fa2 --- /dev/null +++ b/index.html @@ -0,0 +1,41 @@ + + + + + + + partial nucleotide identity threshold + + + +

partial nucleotide identity threshold

+ +
+ + % +
+ + +
+ + +
+ + + + + + + + + + + + + +
foobarpnit
foobarpnit
+
+ + + + diff --git a/main.css b/main.css new file mode 100644 index 0000000..b6ae759 --- /dev/null +++ b/main.css @@ -0,0 +1,7 @@ +form { + margin-bottom: 2ex; +} + +input[name='pni-threshold'] { + text-align: right; +} diff --git a/main.mjs b/main.mjs new file mode 100644 index 0000000..8bebffd --- /dev/null +++ b/main.mjs @@ -0,0 +1,7 @@ +import PNITForm from './pnit-form.mjs'; + +function init() { + PNITForm.register(); +} + +document.addEventListener('DOMContentLoaded', init); diff --git a/pnit-form.mjs b/pnit-form.mjs new file mode 100644 index 0000000..c1fa16c --- /dev/null +++ b/pnit-form.mjs @@ -0,0 +1,77 @@ +const worker = new Worker('worker.js', { type: 'module' }); + +export default class extends HTMLElement { + static name = 'x-pnit-form'; + static register() { + console.debug('Registering Element', this.name, this); + customElements.define(this.name, this); + } + + get form() { + return this.querySelector('form'); + } + + get ignoreHeader() { + return this.form.querySelector('input[name="ignore-header"]').checked; + } + + get threshold() { + return Number(this.form.querySelector('input[name="pni-threshold"]').value); + } + + get files() { + const inputs = Array.from(this.form.querySelectorAll('input[type="file"]')); + return inputs.flatMap(inp => Array.from(inp.files)); + } + + get resultTable() { + return document.getElementById('pnit-results'); + } + + connectedCallback() { + console.debug('PNITForm#connectedCallback', this); + + worker.onmessage = this.handleWorkerMessage.bind(this); + worker.onmessageerror = this.handleWorkerMessageError.bind(this); + worker.onerror = this.handleWorkerError.bind(this); + + this.form.onchange = this.process.bind(this); + this.form.onchange(); + } + + handleWorkerMessage(e) { + const row = document.createElement('tr'); + e.data.forEach(v => { + const col = document.createElement('td'); + col.innerText = v; + row.appendChild(col); + }); + this.resultTable.appendChild(row); + } + + handleWorkerMessageError(e) { + console.error('onmessageerror', e); + } + + handleWorkerError(e) { + console.error('onerror', e); + } + + process() { + console.debug('PNITForm#process', this); + + this.resultTable.innerHTML = ''; + + if (isNaN(this.threshold)) { + console.error('threshold invalid'); + return; + } + this.files.forEach(file => { + worker.postMessage({ + ignoreHeader: this.ignoreHeader, + threshold: this.threshold, + file + }); + }); + } +} diff --git a/worker.js b/worker.js new file mode 100644 index 0000000..77a861b --- /dev/null +++ b/worker.js @@ -0,0 +1,53 @@ +import CSVParse from './csv.mjs'; + +let ignoreLines = 0; +let sequenceNames = []; + +function process(threshold, row) { + const name = row[0]; + if (name === undefined || /^\s*$/.test(name)) { + return; + } + if (ignoreLines > 0) { + ignoreLines--; + return; + } + sequenceNames.push(name); + + let i = 0; + for (const col of row) { + const val = Number(col); + if (isNaN(val)) { + continue; + } + if (val >= threshold && name !== sequenceNames[i]) { + postMessage([name, sequenceNames[i], val]); + } + i++; + } +} + +onmessage = e => { + console.debug('got message', e); + console.debug('reading file'); + + console.debug('e.data', e.data); + const { ignoreHeader, threshold, file } = e.data; + console.debug('threshold', threshold, 'file', file); + + const reader = new FileReader(); + reader.onload = e => { + const parser = new CSVParse(); + for (const c of e.target.result) { + parser.push(c); + } + if (ignoreHeader) { + ignoreLines = 1; + } + parser.results.forEach(row => process(threshold, row)); + } + reader.progress = e => { + console.debug('reader.progress', e); + } + reader.readAsText(file); +} -- cgit v1.3