web-components/i-gene/components/datadisplay.js

326 lines
19 KiB
JavaScript
Raw Normal View History

2024-11-20 18:43:24 +01:00
class DataDisplay extends HTMLElement{
#rootdoc = null;
#source = null;
#filter = {
mode : "out",
distance: 15,
enzyme: "",
zeromatches : false
}
#timeout = null;
#export = ""
constructor(){
super()
this.#rootdoc = this.attachShadow({ "mode" : "open"})
this.render()
}
show(source){
this.#source = source
this.render()
}
render(){
if(this.#source == null){
this.#rootdoc.innerHTML = `<h5>Nothing to show</h5>`
}else{
const s = this.#source
this.#rootdoc.innerHTML = `
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
#table-container{
max-height: 500px;
overflow-y: auto;
}
table, th, td {
user-select: none;
}
thead > tr {
position: sticky;
top: 0;
}
tr.table-row-nomatch{
opacity: 0.5;
text-decoration: line-through;
}
tr.table-row-match{
cursor: pointer;
}
td.rank-td{
min-width:8rem;
max-width:8rem;
width:8rem;
}
</style>
<div class="d-flex flex-column" style="gap:10px">
<h5>${s.getHeader()}</h5>
<div class="filters d-flex flex-wrap align-items-center justify-content-between">
<div class="d-flex flex-wrap align-items-center" style="gap:5px">
<fieldset>
<legend>Pair of two guides
<span name="help" style="line-height:3px" class="btn border-primary bg-warning text-primary position-relative p-2 m-2 rounded-circle">?</span>
<div style="max-width: 30%;z-index: 100;" class="help-text d-none position-absolute bg-white border rounded border-primary shadow p-2">
<div>
<h4 class="text-decoration-underline">Pair of two guides</h4>
<p class="small">Analysis of the imported data to find a pair of two gRNAs in the maximum desired distance. By clicking on the gRNA, the gRNAs that can be used as a pair are shown. The number of available gRNAs as a pair is shown as a blue square.</p>
</div>
<div class="d-flex flex-column gap-1">
<h5>PAM OUT</h5>
<img src="https://cdn.dev.d4science.org/i-gene/resources/pamout.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align: justify;">The PAM sites of the pair of two gRNAs face outwards. The distance between them is calculated from the 5 end of the one gRNA till the 5 end of the other gRNA.</p>
</div>
<div class="d-flex flex-column gap-1">
<h5>PAM IN</h5>
<img src="https://cdn.dev.d4science.org/i-gene/resources/pamin.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align: justify;">The PAM sites of the pair of two gRNAs face inwards. The distance between them is calculated from the 3 end of the one gRNA till the 3 end of the other gRNA.</p>
</div>
<div>
<h5>Any PAM</h5>
<p class="small" style="text-align: justify;">All the different directions of the two gRNAs and the configurations of the PAM sites are considered, including the configurations PAM OUT and PAM IN.</p>
</div>
</div>
</legend>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-out" value="out" ${this.#filter.mode == 'out' ? 'checked' : ''}>
<label class="form-check-label" for="pam-out">PAM out</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-in" value="in" ${this.#filter.mode == 'in' ? 'checked' : ''}>
<label class="form-check-label" for="pam-in">PAM in</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="pam-any" value="any" ${this.#filter.mode == 'none' ? 'checked' : ''}>
<label class="form-check-label" for="pam-any">Any PAM</label>
</div>
</fieldset>
<fieldset>
<legend>Restriction Enzyme
<span name="help" style="line-height:3px" class="btn border-primary bg-warning text-primary position-relative p-2 m-2 rounded-circle">?</span>
<div style="max-width: 30%;z-index: 100;" class="help-text d-none position-absolute bg-white border rounded border-primary shadow p-2">
<div class="d-flex flex-column gap-1">
<h4 class="text-decoration-underline">Restriction Enzyme</h4>
<img src="https://cdn.dev.d4science.org/i-gene/resources/re.png" class="border rounded mx-auto" title="PAM OUT" alt="pam out"/>
<p class="small" style="text-align:justify;">Analysis of the imported data to find gRNAs in a maximum distance of a selected restriction enzyme. The distance between the gRNA and the restriction enzyme is calculated as the closest distance between the 5 or 3 end of the restriction enzyme and the 5 or 3 end the gRNA. By clicking on the gRNA, the accurate distance between the gRNA and the restriction enzyme is shown in a grey square. The means that the restriction site is located upstream of the gRNA recognition site.</p>
</div>
</div>
</legend>
<div style="display:inline-flex;align-items:center;gap:1rem;">
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="pam" id="re" value="re" ${this.#filter.mode == 're' ? 'checked' : ''}>
</div>
<!--div class="form-group">
<input id="restriction-enzyme" list="restriction-enzyme-datalist" disabled/>
<small id="restriction-enzyme-feedback" class="form-text text-muted"></small>
<datalist id="restriction-enzyme-datalist">
</datalist>
</div-->
<div>
<nw-smart-input id="restriction-enzyme"></nw-smart-input>
</div>
</div>
</fieldset>
<fieldset>
<legend>Maximum distance</legend>
<input class="form-control" style="max-width:6rem" type="number" name="distance" min="1" step="1" id="distance" value="${this.#filter.distance}" placeholder="A number distance">
</fieldset>
</div>
<fieldset>
<legend></legend>
<div class="d-flex flex-wrap align-content-baseline" style="gap:10px">
<div class="col form-check form-switch">
<input class="form-check-input" type="checkbox" value="" id="toggle-zero-matches">
<label class="form-check-label" for="toggle-zero-matches">Show items with 0 matches</label>
</div>
<div class="col">
<button name="tsvexport2" class="btn btn-primary">Download TSV</button>
</div>
<div class="col">
<button name="export2ws" title="Export to D4Science Workspace" class="btn btn-primary">
<svg viewBox="0 96 960 960" style="width: 32px; height: 32px; fill: white;">
<path d="M140 796h680V516H140v280Zm540.118-90Q701 706 715.5 691.382q14.5-14.617 14.5-35.5Q730 635 715.382 620.5q-14.617-14.5-35.5-14.5Q659 606 644.5 620.618q-14.5 14.617-14.5 35.5Q630 677 644.618 691.5q14.617 14.5 35.5 14.5ZM880 456h-85L695 356H265L165 456H80l142-142q8-8 19.278-13 11.278-5 23.722-5h430q12.444 0 23.722 5T738 314l142 142ZM140 856q-24.75 0-42.375-17.625T80 796V456h800v340q0 24.75-17.625 42.375T820 856H140Z"/>
</svg>
WS Export
</button>
</div>
</div>
</fieldset>
</div>
<div id="table-container">
${this.renderTable()}
</div>
</div>
`
const helps = Array.prototype.slice.call(this.#rootdoc.querySelectorAll("span[name='help']"))
helps.forEach(h=>{
h.addEventListener("click", ev=>{
ev.target.parentElement.querySelector(".help-text").classList.toggle("d-none")
})
})
document.addEventListener("keydown", ev=>{
if(ev.key === "Escape"){
const hts = Array.prototype.slice.call(this.#rootdoc.querySelectorAll(".help-text"))
hts.forEach(ht=>{
if(!ht.classList.contains("d-none")) ht.classList.add("d-none");
})
}
})
this.#rootdoc.querySelector("div.filters").addEventListener("change", ev=>{
const tgt = ev.target
if(tgt.id === "pam-any" || tgt.id === "pam-in" || tgt.id === "pam-out" || tgt.id === 're') {
if(tgt.id === "pam-any" || tgt.id === "pam-in" || tgt.id === "pam-out"){
this.#rootdoc.querySelector("#restriction-enzyme").disabled = true;
} else if(tgt.id === "re" ) {
this.#rootdoc.querySelector("#restriction-enzyme").disabled = false;
}
this.#filter.mode = tgt.value
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
}
})
this.#rootdoc.querySelector("div.filters").addEventListener("input", ev=>{
const tgt = ev.target
if(tgt.id === "distance"){
if(this.#timeout == null){
this.#timeout = window.setTimeout(()=>{
this.#filter.distance = Number(this.#rootdoc.querySelector("#distance").value)
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable()
this.#timeout = null;
}, 1000);
}
}
})
this.#rootdoc.querySelector("button[name='tsvexport2']").addEventListener("click", ev=>{
this.exportTable2("\t")
})
this.#rootdoc.querySelector("button[name='export2ws']").addEventListener("click", ev => {
this.export2Workspace("\t");
})
this.#rootdoc.querySelector("#table-container").addEventListener("click", ev=>{
var root = ev.target.parentElement
while(root != null && !root.getAttribute("data-rank")){
root = root.parentElement
}
if(root == null) return;
const rank = root.getAttribute("data-rank")
if(rank){
Array.prototype.slice.call(this.#rootdoc.querySelectorAll(`#table-container tr[data-rel-rank='${rank}']`)).forEach(tr=>tr.classList.toggle("d-none"))
}
})
this.#rootdoc.querySelector("#toggle-zero-matches").addEventListener("change", ev=>{
const tgt = ev.target
this.#filter.zeromatches = tgt.checked
this.toggleVisibility()
})
const reSelect = this.#rootdoc.querySelector("#restriction-enzyme");
reSelect.data = iGeneIndexer.restrictionEnzymes.filter(re=>!re.sequence.includes('?') && !re.sequence.includes('('))
/*const reSelect = this.#rootdoc.querySelector("#restriction-enzyme");
const reSelectFeedback = this.#rootdoc.querySelector("#restriction-enzyme-feedback");
const reSelectDatalist = this.#rootdoc.querySelector("#restriction-enzyme-datalist");
reSelectDatalist.innerHTML = iGeneIndexer.restrictionEnzymes.reduce((acc, e)=>{
return acc + `
<option ${e.sequence.includes('?') || e.sequence.includes('(') ? 'disabled' : ''} value="${e.sequence}">${e.name} (${e.sequence})</option>
`
}, '<option vallue=""></option>')*/
/*iGeneIndexer.restrictionEnzymes.forEach(enzyme => {
const newOption = document.createElement("option");
newOption.text = enzyme.name + ' (' + enzyme.sequence + ')';
newOption.value = enzyme.sequence;
if (enzyme.sequence.includes('?') || enzyme.sequence.includes('(')) {
newOption.disabled = true;
}
reSelectDatalist.add(newOption);
});*/
// reSelect.addEventListener("change", ev => {
// this.#filter.enzyme = reSelect.value;
// const o = reSelectDatalist.querySelector("option[value='" + reSelect.value + "']")
// reSelectFeedback.textContent = o ? o.textContent : reSelect.value
// this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
// })
}
}
toggleVisibility(){
const trs = Array.prototype.slice.call(this.#rootdoc.querySelectorAll("tr.table-row-nomatch"))
trs.forEach(tr=>tr.classList.toggle('d-none'))
}
updateEnzyme(enzyme){
this.#filter.enzyme = enzyme
setTimeout(() => {
this.#rootdoc.querySelector("#table-container").innerHTML = this.renderTable();
}, 100);
}
renderTable() {
const renderer = this.#source.getRenderer(this.#filter, "html")
return renderer.renderTable()
}
constructFilename() {
const indexer = this.#source.getIndexer();
const filter = this.#filter;
let filename = 'export_';
filename += 'genome=' + indexer.getGenome() + '_chr=' + indexer.getChromosome() + '_pam=' + indexer.getPAM();
filename += '_dist=' + filter.distance + '_mode=pam-' + filter.mode + (filter.mode === 're' ? '_(enz=' + filter.enzyme + ')' : '');
return filename;
}
exportTable(sep){
const renderer = this.#source.getRenderer(this.#filter, "tsv")
if(renderer != null){
this.#export = renderer.renderTable()
var element = document.createElement('a');
element.setAttribute('href', 'data:text/tab-separated-values;charset=utf-8,' + encodeURIComponent(this.#export));
element.setAttribute('download', this.constructFilename() + '.tsv');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
exportTable2(sep){
const renderer = this.#source.getRenderer(this.#filter, "tsv2")
if(renderer != null){
this.#export = renderer.renderTable()
var element = document.createElement('a');
element.setAttribute('href', 'data:text/tab-separated-values;charset=utf-8,' + encodeURIComponent(this.#export));
element.setAttribute('download', this.constructFilename() + '.tsv');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
async export2Workspace(sep) {
const renderer = this.#source.getRenderer(this.#filter, "tsv2")
if(renderer != null){
const folderName = "i-Gene-Matcher";
const date = new Date();
const fileName = this.constructFilename() + '_' + date.toISOString().substring(0, 11) + date.toLocaleTimeString().replaceAll(":", "-") + ".tsv";
const file2export = renderer.renderTable();
const workspace = new D4SWorkspace("https://api.d4science.org/workspace");
const wsId = await workspace.getWorkspace(null);
const iGeneFolder = await workspace.checkOrCreateFolder(wsId, folderName, "I-Gene tool export folder", null);
if(await workspace.uploadFile(iGeneFolder, fileName, "I-Gene Matcher Export work of " + date, file2export, "text/tab-separated-values", null)) {
alert("File successfully saved on D4Science Workspace as:\n\n/" + folderName + "/" + fileName);
} else {
alert("An error occurred during the file upload to workspace, check browser console for more details");
}
}
}
}
window.customElements.define('igene-data-display', DataDisplay);