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

326 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);