/* The static tables beside the browser: the named features and their published evidence, and the annotation sources. All of it is read out of data/sae/index.json. Captions are authored in sae-features.html as the first child of each table, so filling a table keeps the caption that is already there. */ (async function () { const named = document.getElementById('named-table'); const prov = document.getElementById('feat-prov'); const sources = document.getElementById('sources-body'); if (!named) return; const esc = value => String(value ?? '').replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch])); /* A caption has to stay the table's first child, so it is lifted out and put back ahead of the generated head and body. */ const fill = (table, html) => { const caption = table.querySelector('caption'); table.innerHTML = (caption ? caption.outerHTML : '') + html; }; /* The report's own vocabulary for what a feature resolves. */ const LEVEL = { site: 'base-level', range: 'region-level', motif: 'motif', pattern: 'pattern', }; /* Reading order: the detectors the report validates against annotation, then the cis-element motifs and repeat patterns, then the two candidates it rejects. Anything the index adds later lands in a fourth group rather than disappearing. */ const GROUPS = [ { title: 'Validated against annotation', ids: [4131, 2634, 586, 7708, 3930, 2618, 1200] }, { title: 'Cis-element motifs and repeat patterns', ids: [4567, 145, 5804, 1474, 5349] }, { title: 'Other named features', ids: [] }, ]; /* Best detector of its class in all four annotated genomes. */ const BEST_IN_CLASS = new Set([4131, 586, 7708]); /* MCC gets its own numeric column, so it comes off the front of the evidence string; the validation-selected threshold is stated once in the caption. */ function splitEvidence(text) { const raw = String(text ?? '').trim(); const hit = /^MCC\s+([0-9.]+)/.exec(raw); return { mcc: hit ? hit[1] : '' }; } try { const index = await (await fetch('data/sae/index.json')).json(); const s = index.sae; prov.textContent = `${s.model_name}, layer ${s.layer}, ${s.d_sae} features, k = ${s.k}`; prov.title = `${s.sae_id} · sha256 ${s.sae_sha256.slice(0, 16)}… · ${s.threshold_mode} threshold ${s.sae_threshold.toFixed(4)}`; const features = new Map(Object.entries(index.curated_features) .map(([id, f]) => [Number(id), f])); const grouped = new Set(GROUPS.flatMap(g => g.ids)); GROUPS[GROUPS.length - 1].ids = [...features.keys()].filter(id => !grouped.has(id)); const row = id => { const f = features.get(id); const { mcc } = splitEvidence(f.evidence); const label = String(f.label ?? '').replace(/\s*\(rejected\)$/, ''); return `` + `f${id}${esc(label)}` + `${esc(LEVEL[f.kind] || f.kind)}` + // A dash, not a blank: an empty cell reads as an oversight. The title // still says which kind of absence this is. `${mcc ? esc(mcc) : '–'}` + ''; }; fill(named, 'FeatureNamed forLevel' + 'MCC' + GROUPS.filter(g => g.ids.length).map(g => `${g.title}` + g.ids.filter(id => features.has(id)).map(row).join('') + '').join('')); if (sources) { if (index.sources) renderSources(sources, index.sources); else renderPendingSources(sources, index.windows); } } catch (error) { fill(named, 'The feature table could not be loaded.'); if (window.console) console.error(error); } /* The verified releases, URLs, citations and licences, once index.sources carries them. */ function renderSources(host, src) { // Two registers per species: the annotation the per-base tracks were built from, // and the most current annotation on the SAME assembly. A newer assembly cannot be // overlaid on these coordinates, so it is named separately rather than used. const badge = r => r.currency === 'upgraded' ? 'newer release applied' : 'already current'; let html = `

${esc(src.intro)}

`; html += '
' + '' + '' + ''; for (const r of src.species) { html += '' + `` + `` + `` + `` + `` + ''; } html += '
' + esc(src.caption) + '
SpeciesAssemblyTracks built fromGene models shownStatus
${esc(r.binomial)}${esc(r.assembly)}
${esc(r.assembly_accession)}
${esc(r.annotation_build)}${r.url ? `${esc(r.annotation_current)}` : esc(r.annotation_current)}` + `${r.released ? `
released ${esc(r.released)}` : ''}
${badge(r)}
'; html += '
Per-species detail, licences and what supersedes each assembly'; for (const r of src.species) { html += `

${esc(r.binomial)}

`; if (r.currency_note) html += `

${esc(r.currency_note)}

`; if (r.superseded_by) html += `

Superseded by: ${esc(r.superseded_by)}

`; if (r.licence) html += `

Licence: ${esc(r.licence)}

`; if (r.citation) html += `

${esc(r.citation)}

`; html += '
'; } html += '
'; if (src.usage_registers) html += renderUsageRegisters(src.usage_registers); if (src.notes && src.notes.length) { html += '
Notes on assembly currency and coordinates
'; } if (src.citations && src.citations.length) { html += '
Citations
'; } host.innerHTML = html; } /* The registers that answer whether a site is used, as opposed to whether an annotation carries it. They are a separate table because they are a separate question, and because each one covers only Arabidopsis: the verdict a window shows is only ever as wide as the register behind it. */ function renderUsageRegisters(reg) { let html = '

Usage registers

' + `

${esc(reg.intro)}

`; if (reg.verdicts && reg.verdicts.length) { html += '
' + reg.verdicts.map(v => `
${esc(v.label)}
${esc(v.gloss)}
` ).join('') + '
'; } html += '
' + `` + '' + ''; for (const r of reg.rows) { html += '' + `` + `` + `` + `` + ''; } html += '
${esc(reg.caption)}
RegisterAnswersVersionCovers
${r.url ? `${esc(r.name)}` : esc(r.name)}${esc(r.role)}${esc(r.version)}${esc(r.scope)}
'; html += '
What each register contains, and what it ' + 'cannot answer'; for (const r of reg.rows) { html += `

${esc(r.name)}

` + `

${esc(r.content)}

` + (r.caveat ? `

Cannot answer: ${esc(r.caveat)}

` : '') + (r.citation ? `

${esc(r.citation)}

` : '') + '
'; } html += '
'; if (reg.note) html += `

${esc(reg.note)}

`; return html; } /* Until then: the assemblies the windows themselves name, and nothing more. */ function renderPendingSources(host, windows) { const rows = new Map(); for (const w of windows) { const key = `${w.species_binomial}|${w.assembly}`; rows.set(key, (rows.get(key) || 0) + 1); } const body = [...rows.entries()] .sort((a, b) => a[0].localeCompare(b[0])) .map(([key, n]) => { const [binomial, assembly] = key.split('|'); return `${esc(binomial)}${esc(assembly)}` + `${n}`; }).join(''); host.innerHTML = '
' + '' + `${body}
The four annotated genomes, with the windows ' + 'each contributes. The AT1G65170 window is cut from TAIR10 and carries the ' + 'Araport11 gene model the report\'s figure draws.
SpeciesAssemblyWindows
` + '

The annotation release, download URL, citation and licence behind each ' + 'of these assemblies are being verified and will be listed here.

'; } })();