diff options
| author | Brian Cully <bjc@spork.org> | 2025-07-16 18:48:48 -0400 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-07-16 18:48:48 -0400 |
| commit | e1a9e10902de4d927f4c2c87b6c27e6303fa1ead (patch) | |
| tree | 516d92bd5bf204308919d57698277b7bb9e08078 | |
| parent | 5da918119bb2e352f10060a36d9c7e580fa8695e (diff) | |
| download | pnit-e1a9e10902de4d927f4c2c87b6c27e6303fa1ead.tar.gz pnit-e1a9e10902de4d927f4c2c87b6c27e6303fa1ead.zip | |
add js version rendering. still needs download
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | csv.mjs | 79 | ||||
| -rw-r--r-- | index.html | 41 | ||||
| -rw-r--r-- | main.css | 7 | ||||
| -rw-r--r-- | main.mjs | 7 | ||||
| -rw-r--r-- | pnit-form.mjs | 77 | ||||
| -rw-r--r-- | worker.js | 53 |
7 files changed, 265 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15d7aef --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +inputs @@ -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 @@ +<!DOCTYPE html> +<html lang='en'> + <head> + <meta charset='utf-8'> + <meta name='viewport' content='width=device-width, initial-scale=1'> + <link rel='stylesheet' href='main.css'> + <title>partial nucleotide identity threshold</title> + </head> + + <body> + <h1>partial nucleotide identity threshold</h1> + <x-pnit-form> + <form> + <label for='pni-threshold'>threshold:</label> + <input id='pni-threshold' name='pni-threshold' type='text' size='5' value='90.5'>% + <br> + <label for='ignore-header'>ignore header line:</label> + <input id='ignore-header' name='ignore-header' type=checkbox checked> + <br> + <label for='csv-input'>upload:</label> + <input id='csv-input' name='csv-input' type='file' accept='text/csv' required> + </form> + + <button>download pnit csv</button> + <table id='pnit-results'> + <tr> + <td>foo</td> + <td>bar</td> + <td>pnit</td> + </tr> + <tr> + <td>foo</td> + <td>bar</td> + <td>pnit</td> + </tr> + </table> + </x-pnit-form> + + <script type='module' src='./main.mjs'></script> + </body> +</html> 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); +} |
