Commit 35e2421c authored by Mathieu Giraud's avatar Mathieu Giraud
Browse files

Merge branch 'feature-c/warnings_view' into 'dev'

New view, Warnings

Closes #4165

See merge request !1240
parents 35f11698 018a24a0
Pipeline #736006 passed with stages
in 50 minutes and 23 seconds
......@@ -2755,3 +2755,16 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warning_span {
min-width: 100px;
display: inline-block;
text-align: right;
cursor: pointer;
}
.warning_span:hover {
color: #fdf6e3;
font-style: oblique;
}
.warnings_section_unpresent {
color: #586e75;
}
......@@ -2755,3 +2755,16 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warning_span {
min-width: 100px;
display: inline-block;
text-align: right;
cursor: pointer;
}
.warning_span:hover {
color: #002b36;
font-style: oblique;
}
.warnings_section_unpresent {
color: #93a1a1;
}
......@@ -2754,6 +2754,13 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warnings_section_unpresent {
color: #93a1a1;
}
.warnings_line {
display: flex;
margin: 4px;
}
body {
line-height: 20px;
}
......
......@@ -2423,6 +2423,13 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warnings_section_unpresent {
color: #586e75;
}
.warnings_line {
display: flex;
margin: 4px;
}
.segmenter .axisBox > span {
margin: 0 3px;
background: #FFFFFF;
......
......@@ -2754,6 +2754,19 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warning_span {
min-width: 100px;
display: inline-block;
text-align: right;
cursor: pointer;
}
.warning_span:hover {
color: #002b36;
font-style: oblique;
}
.warnings_section_unpresent {
color: #93a1a1;
}
#mid-container {
font-size: 125%;
}
......
......@@ -2808,4 +2808,20 @@ div.tooltip {
.dropdown-check-list.visible .rs-items {
display: block;
}
.warning_span {
min-width:120px;
display: inline-block;
text-align: right;
cursor: pointer;
}
.warning_span:hover {
color: @select;
font-style: oblique;
font-weight: bold;
}
.warnings_section_unpresent {
color: @secondary
}
\ No newline at end of file
......@@ -372,6 +372,15 @@
</div>
<div class="menu" id="warnings_menu"> warnings
<div class="selector" id="warnings_selector">
<div class="menu_box">
<div id="warnings_list"></div>
</div>
</div>
</div>
<div id="debug_menu" class="menu" style="display : none">
development
<div class="selector">
......
......@@ -451,11 +451,16 @@ Aligner.prototype = {
cloneT.getElementsByClassName("delBox")[0].onclick = function () {
self.removeSequence(cloneID);
};
if (this.m.clone_info == cloneID)
cloneT.getElementsByClassName("infoBox")[0].className += " infoBox-open";
cloneT.getElementsByClassName("infoBox")[0].onclick = function () {
self.m.displayInfoBox(cloneID);
}
var span_info = cloneT.getElementsByClassName("infoBox")[0]
span_info.id = `aligner_info_${cloneID}`
span_info.onclick = function () {self.m.displayInfoBox(cloneID)}
var dom_content = clone.getWarningsDom()
span_info.classList = dom_content.className
span_info.firstChild.classList = dom_content.icon
span_info.firstChild.title = dom_content.title
cloneT.getElementsByClassName("axisBox")[0].color = clone.getColor();
this.fillAxisBox(cloneT.getElementsByClassName("axisBox")[0], clone);
......@@ -572,6 +577,14 @@ Aligner.prototype = {
}
var dom_content = this.m.clone(cloneID).getWarningsDom()
var info_align = document.getElementById(`aligner_info_${this.m.clone(cloneID).index}`)
if (info_align != null) {
info_align.classList = dom_content.className
info_align.firstChild.classList = dom_content.icon
info_align.firstChild.title = dom_content.title
}
this.updateDom(list)
.updateButton()
......
......@@ -103,6 +103,8 @@ function loadAfterConf() {
"js/tag",
"js/tokeniser",
"js/indexedDom",
"js/warnings",
"js/warnings_data",
// Speed test
"js/speed_test",
"js/form_builder",
......
......@@ -107,15 +107,17 @@ Clone.prototype = {
COVERAGE_WARN: 0.5,
EVALUE_WARN: 0.001,
isWarned: function () {
isWarnedBool: function () {
return this.warn.length > 0
},
/**
* settings_leveled (Boolean) Use level as setted in the localstorage of not
* @return {string} a warning class is set on this clone
*/
isWarned: function () {
var wL = this.warnLevel()
if (wL >= WARN) {
return warnTextOf(wL)
}
if (wL){ return warnText[wL] }
if (this.hasSeg('clonedb')) {
if (typeof(this.seg.clonedb['']) != 'undefined') // TODO: use a stored number of occurrences
......@@ -127,6 +129,17 @@ Clone.prototype = {
return false
},
/**
* Return true if this clone hava at least one warning with this code
*/
haveWarning: function(warn_code){
if (sum(this.warn.map((warn) => { return warn_code == warn.code ? 1 : 0}))) {
return true
}
return false
},
computeWarnings: function() {
if (this.getCoverage() < this.COVERAGE_WARN)
......@@ -136,14 +149,12 @@ Clone.prototype = {
this.warn.push({'code': 'Wxx', 'level': warnLevels[WARN], 'msg': 'Bad e-value (' + this.eValue + ')' });
},
/**
* Return maximum level of warning (number format), using setted warning level in localStorage when present
*/
warnLevel: function () {
var level = 0
for (var i = 0; i < this.warn.length; i++) {
level = Math.max(level, warnLevelOf(this.warn[i].level))
}
return level
// get list of warn code
return this.m.getWarningLevelFromList(this.warn)
},
warnText: function () {
......@@ -158,6 +169,19 @@ Clone.prototype = {
return items.join('\n')
},
getWarningsDom: function(){
var dom = {}
if (this.isWarnedBool() && this.warnLevel()) {
dom.className = this.isWarned() ;
dom.icon = 'icon-warning-1'
dom.title = this.warnText()
} else {
dom.icon = 'icon-info'
dom.title = 'clonotype information'
}
return dom
},
/**
* return clone's most important name, shortened
* @return {string} name
......@@ -1590,7 +1614,7 @@ Clone.prototype = {
html += "</tr>"
//warnings
if (this.isWarned()) {
if (this.isWarnedBool()) {
html += header("warnings", undefined, time_length)
var warnings = {}
// Create a dict of all warning present, and add each sample with it
......@@ -1873,13 +1897,11 @@ Clone.prototype = {
span_info.onclick = function () {
self.m.displayInfoBox(self.index);
}
// console.default.log( `${this.index} - ${this.isWarnedBool()}, ${this.warnLevel()}; => ${this.isWarnedBool() && this.warnLevel()}`)
var dom_content = this.getWarningsDom()
span_info.classList = dom_content.className
span_info.appendChild(icon(dom_content.icon, dom_content.title))
if (this.isWarned()) {
span_info.className += " " + this.isWarned() ;
span_info.appendChild(icon('icon-warning-1', this.warnText()));
} else {
span_info.appendChild(icon('icon-info', 'clonotype information'));
}
}
// Gather all elements
......
......@@ -412,6 +412,19 @@ List.prototype = {
if (val > 100) this.index_data[key].innerHTML = val.toFixed(0);
if (val < 100) this.index_data[key].innerHTML = val.toPrecision(3);
}
for (var id = 0; id < this.m.clones.length; id++) {
var clone = this.m.clones[id]
span_info = document.getElementById("clone_infoBox_"+id)
span_info.innerHTML = ""
span_info.className = ""
if (clone.isWarnedBool() && clone.warnLevel()) {
span_info.className = clone.isWarned() ;
span_info.appendChild(icon('icon-warning-1', clone.warnText()));
} else {
span_info.appendChild(icon('icon-info', 'clonotype information'));
}
}
},
/**
......@@ -533,6 +546,14 @@ List.prototype = {
if (this.m.clusters[cloneID].length < 2) display = false
document.getElementById("cluster"+cloneID).style.display = "none";
}
var info_list = document.getElementById(`clone_infoBox_${clone.index}`)
var dom_content = clone.getWarningsDom()
if (info_list != null) {
info_list.classList = dom_content.className
info_list.firstChild.classList = dom_content.icon
info_list.firstChild.title = dom_content.title
}
}
},
......@@ -758,7 +779,7 @@ List.prototype = {
* */
sortListBy:function(fct){
self = this;
var list = jQuery('.list')
var list = jQuery('#list_clones > .list')
var value = {};
for (var i=0; i<list.length; i++){
var id = list[i].getAttribute("id")
......@@ -776,7 +797,7 @@ List.prototype = {
sortListByGene: function(gene_type) {
self = this;
var list = jQuery('.list')
var list = jQuery('#list_clones > .list')
var sort = list.sort(function (a, b) {
var idA = a.getAttribute("id");
var idB = b.getAttribute("id");
......
......@@ -94,8 +94,10 @@ try {
var list_clones = new List("list", "data", m, db); // List of clones
var sp = new ScatterPlot("visu", m, db); // Scatterplot (both grid and bar plot view)
var sp2;
var segment = new Aligner("segmenter", m, db); // Segmenter
var sp_export = new ScatterPlot("visu3", m, db, undefined, hidden=true); // Scatterplot used for export render
var segment = new Aligner("segmenter", m, db); // Segmenter
var warnings = new Warnings("warnings_list", m, db); // Warnings menu
m.warnings = warnings
/* Similarity
......
......@@ -1212,7 +1212,7 @@ changeAlleleNotation: function(alleleNotation, update, save) {
multiSelect: function (list) {
if (list.length == 0) return;
console.log("select() (clone " + list + ")");
console.log("select() (clone " + list + "); "+list.length+"x clones");
var tmp = []
for (var i = 0; i < list.length; i++) {
......@@ -1545,6 +1545,7 @@ changeAlleleNotation: function(alleleNotation, update, save) {
/**
* Return a table of each warning, with number of clone and reads by type of warning
* if timeId equal -1, return summed warnings table for all samples
* @param {integer} timeID - time/sample index
* @return {Object} Dict of warning, with list of clonotype and sum of reads by type of warning
* */
......@@ -1562,29 +1563,202 @@ changeAlleleNotation: function(alleleNotation, update, save) {
return msg
}
if (timeID == undefined){
timeID = -1
}
var warned = {}
for (var cloneId in this.clones){
var clone = this.clone(cloneId)
if (clone.isWarned()){
this.clones.forEach( (clone) => {
if (clone.isWarnedBool()){
for (var warn_pos in clone.warn){
var warn = clone.warn[warn_pos]
if (Object.keys(warn).length == 0){
continue
}
if (clone.reads[timeID] != 0){
if (timeID != -1 && clone.reads[timeID] != 0){
if (warned[warn.code] == undefined){
warned[warn.code] = {"clones": [], "reads": 0, "msg": ""}
warned[warn.code] = {"clones": [], "reads": 0, "msg": getCleanedWarningName(warn.msg)}
}
if (warned[warn.code].clones.indexOf(clone.index) == -1){
warned[warn.code].clones.push(clone.index)
warned[warn.code].reads += clone.reads[timeID]
}
} else if (timeID == -1){
if (warned[warn.code] == undefined){
warned[warn.code] = {"clones": [], "reads": 0, "msg": getCleanedWarningName(warn.msg)}
}
if (warned[warn.code].clones.indexOf(clone.index) == -1){
warned[warn.code].clones.push(clone.index)
warned[warn.code].reads += sum(clone.reads)
}
warned[warn.code].clones.push(clone.index)
warned[warn.code].reads += clone.reads[timeID]
warned[warn.code].msg = getCleanedWarningName(warn.msg)
}
}
}
}
})
return warned
},
changeWarningLevel: function(subwarn_code, operation){
var current_level = this.getWarningLevelFromCode(subwarn_code)
if (operation == "decrease" && current_level > 0){
current_level -= 1
} else if (operation == "increase" && current_level < 2){
current_level += 1
}
Object.keys(warnings_data).forEach( (category) => {
Object.keys(warnings_data[category]).forEach( (warn_code) => {
if (warn_code == subwarn_code) {
localStorage.setItem(`warn_${warn_code}`, current_level)
}
})
})
this.update()
console.default.log(`change warn level after; ${subwarn_code}, ${current_level}`)
},
/**
* Toogle a warning between is current level and info (level 0)
* Update the icon to show a warning or not
*/
toogleWarningFilter: function(subwarn_code){
this.setWarningFilterStatus(subwarn_code, !this.getWarningFilterStatusFromCode(subwarn_code))
this.update()
},
/*
* Return the maximum level of warning from a list of warning
* Computed from the setted level of warning
*/
getWarningLevelFromList: function(warn_list){
var maximum = 0
var value;
warn_list.forEach( (warn) => {
value = this.getWarningLevelFromWarn(warn)
if (value != undefined && value > maximum) {
maximum = value
}
})
return maximum
},
/**
* Return the level of a warning depending of the localstorage declaration
* Can use old fashion warnings and new warnings type.
* If warn level is undefined, return 1 by default (old type)
*/
getWarningLevelFromWarn: function(warn){
if (warn == 0 ) {return 0} // Error in fuse merge, by already in production
var value = 2
if (!("code" in warn) && !("msg" in warn) && !("level" in warn) ){ // empty
return 0
} else if (warn.code != undefined && warn.code != ""){
value = this.getWarningLevelFromCode(warn.code)
} else if (warn.level == undefined){ // Old style value
value = 1
} else {
value = parseInt(Object.keys(warnText).find(key => warnText[key] === warn.level))
}
if (this.getWarningFilterStatusFromCode(warn.code)){ return 0}
return value
},
/**
* Return the level of a warning depending of the localstorage declaration
* Can use old fashion warnings and new warnings type.
* If warn level is undefined, return 1 by default (old type)
* @param (string) warn_code The warning code to search
*/
getWarningLevelFromCode: function(warn_code){
var level = 2
if (warn_code == undefined){
return level
} else if (localStorage.getItem(`warn_${warn_code}`) ) {
level = parseInt(localStorage.getItem(`warn_${warn_code}`))
} else {
Object.keys(warnings_data).forEach( (category) => {
Object.keys(warnings_data[category]).forEach( (sub_warn_code) => {
if (warn_code == sub_warn_code){
level = warnings_data[category][warn_code].level;
}
})
})
}
if (this.getWarningFilterStatusFromCode(warn_code)){ return 0 }
return level
},
/**
* Return the filter status of a warning depending of the localstorage declaration
* If warn level is undefined, return false by default
* @param (string) warn_code The warning code to search
*/
getWarningFilterStatusFromCode: function(warn_code){
if (warn_code == undefined){
return false
} else if (localStorage.getItem(`warn_filter_${warn_code}`) ) {
return localStorage.getItem(`warn_filter_${warn_code}`) === "true"
}
return false
},
/**
* Set the filter status of a warning by his warning code
* @param (string) warn_code The warning code to set
* @param (Boolean) status The status to set
*/
setWarningFilterStatus: function(warn_code, status){
localStorage.setItem(`warn_filter_${warn_code}`, status)
},
getClonesWithWarningCode: function(warn_code, only_present=false){
var selection = [];
for (var i = 0; i < this.clones.length; i++) {
var clone = this.clones[i]
if (clone.haveWarning(warn_code) &&
(!only_present || (only_present && clone.getSize(this.t))) ){
selection.push( i )
}
}
return selection
},
/**
* Allow to select all clone that have a warning with the specified code
* only_present arg allow to filter selection with clones that have at least one read for the current sample
**/
selectCloneByWarningCode: function(warn_code, only_present=false){
this.unselectAll()
var selection = this.getClonesWithWarningCode(warn_code, only_present);
this.multiSelect(selection)
},
/**
* For all presetn warning in localstorage, give the original value as setted in warnings_data
*/
resetWarnings: function(){
var warnings_class = Object.keys(warnings_data)
for (var i = 0; i < warnings_class.length; i++) {
const warning_section = warnings_data[warnings_class[i]]
/* jshint ignore:start */
Object.keys(warning_section).forEach( (subwarn_code) =>{
var subwarn = warning_section[subwarn_code]
if (localStorage.getItem(`warn_${subwarn_code}`)){
localStorage.setItem(`warn_${subwarn_code}`, subwarn.level)
}
this.setWarningFilterStatus(subwarn_code, false)
})
/* jshint ignore:end */
}
// reset menu
warnings.update()
console.default.log( "TODO - reset warnings level" )
},
exportSampleInfo: function(timeID){
convertContent = function( value ){
if (value[0] == header){
......
......@@ -669,6 +669,14 @@ warnLevels = {
}
warnTexts = { }
warnText = {
0: "",
1: "warn",
2: "alert",
3: "error",
4: "fatal",
5: "ok"
}
for (var key in warnLevels) {
warnTexts[warnLevels[key]] = key ;
......
/*
* This file is part of Vidjil <http://www.vidjil.org>,
* High-throughput Analysis of V(D)J Immune Repertoire.
* Copyright (C) 2013-2017 by VidjilNet consortium and Bonsai bioinformatics
* at CRIStAL (UMR CNRS 9189, Université Lille) and Inria Lille
* Contributors:
* Florian Thonier <florian@vidjil.org>
* The Vidjil Team <contact@vidjil.org>
*