/* vim: set ts=2 sts=2 sw=2 expandtab: */ const WATNA_BASES = [ 'A', 'C', 'G', 'T' ]; const WATNA_NTCS = [ 'AA00', 'AA01', 'AA02', 'AA03', 'AA04', 'AA05', 'AA06', 'AA07', 'AA08', 'AA09', 'AA10', 'AA11', 'AA12', 'AA13', 'AAS1', 'AB01', 'AB02', 'AB03', 'AB04', 'AB05', 'AB1S', 'AB2S', 'BA01', 'BA05', 'BA08', 'BA09', 'BA10', 'BA13', 'BA16', 'BA17', 'BB00', 'BB01', 'BB02', 'BB03', 'BB04', 'BB05', 'BB07', 'BB08', 'BB10', 'BB11', 'BB12', 'BB13', 'BB14', 'BB15', 'BB16', 'BB17', 'BB1S', 'BB20', 'BB2S', 'BBS1', 'IC01', 'IC02', 'IC03', 'IC04', 'IC05', 'IC06', 'IC07', 'OP01', 'OP02', 'OP03', 'OP04', 'OP05', 'OP06', 'OP07', 'OP08', 'OP09', 'OP10', 'OP11', 'OP12', 'OP13', 'OP14', 'OP15', 'OP16', 'OP17', 'OP18', 'OP19', 'OP1S', 'OP20', 'OP21', 'OP22', 'OP23', 'OP24', 'OP25', 'OP26', 'OP27', 'OP28', 'OP29', 'OP30', 'OP31', 'OPS1', 'ZZ01', 'ZZ02', 'ZZ1S', 'ZZ2S', 'ZZS1', 'ZZS2' ]; let _DONT_TOUCH_preloaded = false; let _DONT_TOUCH_fragment_paths_cache = {}; let fragments_data = null; function _mkNtcPaths(ntc, seq) { const fragId = mkFragId(ntc, seq); const mkPath = name => `${ntc}/${seq}/${fragId}_${name}`; return { root: './data', structures: { reference: mkPath('reference_scale.pdb'), base: mkPath('base_waterpeaks.pdb'), nucleotide: mkPath('dinu_waterpeaks.pdb'), phosphate: mkPath('phos_waterpeaks.pdb'), }, densityMaps: { base: mkPath('base_water.map.ccp4'), nucleotide: mkPath('dinu_water.map.ccp4'), phosphate: mkPath('phos_water.map.ccp4'), } }; } function _mkStructurePaths(structId) { const mkPath = name => `${structId}/${name}`; return { root: './data/predictions', structures: { reference: mkPath(`${structId}_origDNA_with_symRelWaters.pdb`), base: mkPath('base_waterpeaks.pdb'), nucleotide: mkPath('nucl_waterpeaks.pdb'), phosphate: mkPath('phos_waterpeaks.pdb'), }, densityMaps: { base: mkPath('base_water.map.ccp4'), nucleotide: mkPath('nucl_water.map.ccp4'), phosphate: mkPath('phos_water.map.ccp4'), }, }; } function _watna_check_waters(obj) { if (typeof obj !== 'object') return false; for (const key in obj) { if (typeof key !== 'string') return false; if (!WATNA_NTCS.includes(key)) return false; const inner = obj[key]; if (typeof inner !== 'object') return false; for (const ikey in inner) { const blocks = ikey.split('_'); if (blocks.length !== 2) return false; if (!WATNA_BASES.includes(blocks[0]) || !WATNA_BASES.includes(blocks[1])) return false; const val = inner[ikey]; if (typeof val !== 'number') return false; if (val < 0) return false; } } return true; } /* https://alienryderflex.com/hsp.html */ function _watna_luminance(r, g, b) { r /= 255.0; g /= 255.0; b /= 255.0; return Math.sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b); } function _watna_fg_color(r, g, b) { return _watna_luminance(r, g, b) < 0.57 ? 'white' : 'black'; } async function watna_browse_add(instId, ntc, seq) { try { await WVApi.add(instId, mkFragId(ntc, seq), _mkNtcPaths(ntc, seq), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]); } catch (e) { console.log(e); throw e; } } async function watna_fetch_data() { try { const resp = await fetch('fragments_data.json'); if (!resp.ok) throw new Error(`Cannot fetch water data: ${resp.statusText}`); const json = await resp.json(); if (json.threshold_hard === undefined || json.threshold_soft === undefined || json.waters === undefined) { throw new Error('Invalid fragments_data format'); } if (typeof json.threshold_hard !== 'number' || typeof json.threshold_hard !== 'number' || !_watna_check_waters(json.waters)) { throw new Error('Invalid fragments_data format'); } if (json.threshold_hard < 0 || json.threshold_soft <= json.threshold_hard) { throw new Error('Invalid fragments_data format'); } fragments_data = { getSoftThreshold: () => { const thresh = json.threshold_soft; return thresh; }, getHardThreshold: () => { const thresh = json.threshold_hard; return thresh; }, getWaters: (ntc, seq) => { const waters = json.waters; const ret = waters[ntc][seq]; if (ret === undefined) throw new Error('Invalid NtC/seq combination'); return ret; } }; } catch (e) { throw e; } } function mkFragId(ntc, seq) { return `${ntc}_${seq}`; } async function watna_fragment_clicked(fragElem, instId, ntc, seq, referenceNameText, errorReportCallback) { if (!_DONT_TOUCH_preloaded) return; const fragId = mkFragId(ntc, seq); if (WVApi.has(instId, fragId)) { await WVApi.remove(instId, fragId); } else { try { await WVApi.add(instId, fragId, { text: referenceNameText, transform: true }, _mkNtcPaths(ntc, seq), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]); watna_set_elem_color(fragElem, instId, fragId); } catch (e) { console.log(e); if (errorReportCallback) { errorReportCallback(e); } } } } async function watna_structure_clicked(fragElem, instId, structId, referenceName, errorReportCallback) { const fragId = structId; // Seemingly useless but what if we change this later? if (WVApi.has(instId, fragId)) { await WVApi.remove(instId, fragId); } else { try { await WVApi.add(instId, fragId, referenceName, _mkStructurePaths(structId), [ 'reference', 'base', 'phosphate' ], [ 'base', 'phosphate' ]); watna_set_elem_color(fragElem, instId, fragId); } catch (e) { console.log(e); if (errorReportCallback) { errorReportCallback(e); } } } } function watna_init(instId, configuration) { WVApi.init(instId, configuration); } async function watna_preload(instId, ntcs, progressCallback) { if (fragments_data === null) throw new Error('Fragments data have not been loaded yet'); const toPreload = []; const sequences = watna_sequences(); const thrHard = fragments_data.getHardThreshold(); const getWaters = fragments_data.getWaters; for (const ntc of ntcs) { for (const seq of sequences) { const waterCnt = getWaters(ntc, seq); if (waterCnt >= thrHard) { const fragId = mkFragId(ntc, seq); toPreload.push({ fragId, paths: _mkNtcPaths(ntc, seq) }); } } } try { await WVApi.load(instId, toPreload, progressCallback); } catch (errors) { console.log(errors); } finally { _DONT_TOUCH_preloaded = true; } } function watna_rerender(instId) { WVApi.forceRerender(instId); } function watna_sequences() { const sequences = []; for (const ba of WATNA_BASES) { for (const bb of WATNA_BASES) { sequences.push(`${ba}_${bb}`); } } return sequences; } function watna_set_elem_color(elem, instId, fragId) { const colors = WVApi.fragmentColors(instId, fragId, 'rgb'); if (colors) { const [ r, g, b ] = colors.base; elem.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; elem.style.color = _watna_fg_color(r, g, b); } } function watna_set_on_colors_changed_callback(instId, callback) { WVApi.setOnFragmentColorsChangedCallback(instId, callback); } function watna_set_on_removed_callback(instId, callback) { WVApi.setOnFragmentRemovedCallback(instId, callback); }