diff --git a/shrine-webclient/src/main/html/js-i2b2/cells/plugins/MedCo/MedCo_QryTool.js b/shrine-webclient/src/main/html/js-i2b2/cells/plugins/MedCo/MedCo_QryTool.js index cc32f5f30..d33234bdf 100644 --- a/shrine-webclient/src/main/html/js-i2b2/cells/plugins/MedCo/MedCo_QryTool.js +++ b/shrine-webclient/src/main/html/js-i2b2/cells/plugins/MedCo/MedCo_QryTool.js @@ -1,677 +1,687 @@ i2b2.MedCo.QT = {}; var pieResultParam = { "header": { "title": { "font": "cinzel", "text": "#patients", "color": "#000000", "fontSize": 24 }, "location": "pie-center", }, "size": { "canvasHeight": 400, "canvasWidth": 600, "pieInnerRadius": "59%", "pieOuterRadius": "63%" }, "data": { "sortOrder": "label-desc", "content": {} }, "labels": { "outer": { "format": "label-value2", "pieDistance": 20 }, "inner": { "format": "none" }, "mainLabel": { "font": "cinzel", "fontSize": 15 }, "percentage": { "font": "cinzel", "color": "#999999", "fontSize": 8, "decimalPlaces": 0 }, "value": { "font": "cinzel", "color": "#000000", "fontSize": 15, "fontWeight": 600 }, "lines": { "enabled": true, }, "truncation": { "enabled": true } }, "tooltips": { "enabled": true, "type": "placeholder", "string": "{label}: {value}, {percentage}%", "styles": { "fontSize": 15, "backgroundOpacity": 0.70, "borderRadius": 15, "padding": 7 } }, "effects": { "load": { "speed": 500 }, "pullOutSegmentOnClick": { "effect": "none", }, "highlightLuminosity": 0.3 }, "misc": { "colors": { "segmentStroke": "#000000" }, "pieCenterOffset": { "y": -50 } } } var currId = 0; // used to give a unique id to the panels function getPanelIndex(panel){ while (panel.className != 'MedCo-QueryPanel'){ if (!panel.parentElement){ return -1 } panel = panel.parentElement } var index = Array.prototype.indexOf.call(panel.parentNode.children, panel); return index } function htmlToElement(html) { var template = document.createElement('template'); html = html.trim(); // Never return a text node of whitespace as the result template.innerHTML = html; return template.content.firstChild; } i2b2.MedCo.QT.ctrlr = {}; i2b2.MedCo.QT.ctrlr.init = function(){ // Initialization of the query tool. var num_panels = 3; // start with 3 panels i2b2.MedCo.QT.panels = []; for (var i=0; i < num_panels; i++) { i2b2.MedCo.QT.ctrlr.appendPanel() } // i2b2.MedCo.QT.query.init(); }; i2b2.MedCo.QT.ctrlr.clear = function (){ // todo and ignore a possible query result? // clear all the panels var panels = i2b2.MedCo.QT.panels; for(var i = 0; i < panels.length; i++){ panels[i].ctrlr.clear() } }; i2b2.MedCo.QT.timer = { // number of milliseconds between two updates step: 100, // store the start time (in milliseconds) startTime: 0, //store the pointer to the interval object so to stop it timeinterval: null, start: function () { // hide the gif and start document.getElementById("medcoTimer").style.display = 'inline-block'; document.getElementById("MedCo-loadingGif").style.display = 'none'; var context = i2b2.MedCo.QT.timer; context.clear(); context.startTime = new Date().getTime(); context.timeinterval = setInterval(context.update, context.step); }, update: function () { var now = new Date().getTime(); document.querySelector("#medcoTimer").innerHTML = ((now-i2b2.MedCo.QT.timer.startTime)/1000).toFixed(2); }, stop: function () { clearInterval(i2b2.MedCo.QT.timer.timeinterval); i2b2.MedCo.QT.timer.timeinterval = null; }, clear: function(){ i2b2.MedCo.QT.timer.stop(); document.querySelector("#medcoTimer").innerHTML = 0; }, wait: function(){ // show the gif document.getElementById("medcoTimer").style.display = 'none'; document.getElementById("MedCo-loadingGif").style.display = 'inline'; } }; i2b2.MedCo.QT.query = { // timeout used to wait until we received all the variant ids timeout: null, // You should call this function to run a query so to make sure you received and encrypted all // the annotation ids you queried waitThenRun: function(){ // clear any previous timeout (in case the user keeps pressing "run query" we want to send only one query) clearTimeout(i2b2.MedCo.QT.query.timeout); // clear the timer in any case i2b2.MedCo.QT.timer.clear(); // show that we are preparing to send the query i2b2.MedCo.QT.timer.wait(); // wait until you receive all the variants you requested and all of them have been encrypted if (i2b2.MedCo.ctrlr.pendingVariantsQueries != 0 || !i2b2.MedCo.ctrlr.background.encryptionDone()) { // todo change names // try again later to run the query i2b2.MedCo.QT.query.timeout = setTimeout(i2b2.MedCo.QT.query.waitThenRun, 500); return } // the query can be build and sent i2b2.MedCo.QT.query.run(); }, // build and send the query run: function(){ // start the timer i2b2.MedCo.QT.timer.start(); // callback processor to run the query from definition this.MedCocallback = new i2b2_scopedCallback(); this.MedCocallback.scope = this; this.MedCocallback.callback = function(results) { // // "results" object contains the following attributes: // // refXML: xmlDomObject <--- for data processing // // msgRequest: xml (string) // // msgResponse: xml (string) // // error: boolean // // errorStatus: string [only with error=true] // // errorMsg: string [only with error=true] // stop the timer and the gif i2b2.MedCo.QT.timer.stop(); try{ if (results.error) { alert(results.errorMsg); return; } else { // extract and show the patient counts i2b2.MedCo.QT.query.showResponse(results, "MedCo-QueryResults"); } } catch(e){ alert("Error when extracting the results of the query: " + e.message) } }; + // todo change the result_output_list with the selected options: + // + // + // + // + // + // + // + // + this.MedCoparams = {}; this.MedCoparams.psm_query_definition = i2b2.MedCo.QT.query.buildQueryDefinition("MedCo_query"); this.MedCoparams.psm_result_output = "\n\n"; i2b2.CRC.ajax.runQueryInstance_fromQueryDefinition("PLUGIN:MedCo", this.MedCoparams, this.MedCocallback); }, showResponse: function(response, resultDiv){ response = response.refXML.getElementsByTagName('query_result_instance'); // first extract the data var institutions = new Array(response.length); for (var i = 0; i < response.length; i++) { try { var currInstitution = JSON.parse(i2b2.h.getXNodeVal(response[i], 'description')); var instName = Object.keys(currInstitution)[0]; i2b2.MedCo.ctrlr.background.toBeDecrypted([currInstitution[instName]["enc_count_result"]]); institutions[i] = { label: instName, enc_count_result: currInstitution[instName]["enc_count_result"] } } catch (e) { alert("Error in extracting the response." + i2b2.h.getXNodeVal(response[i], 'summary')); return } } // now wait until all ciphertext are decrypted var checkThenShow = function(){ if (i2b2.MedCo.ctrlr.background.decryptionDone()) { var resultPanel = document.getElementById(resultDiv) while (resultPanel.firstChild) { resultPanel.removeChild(resultPanel.firstChild); } var totPatientCount = 0; for (var i = 0; i < institutions.length; i++){ institutions[i].value = i2b2.MedCo.ctrlr.background.decryptionCache[institutions[i].enc_count_result]; totPatientCount += institutions[i].value; } // if there are no patients that match the query then do not display the pie chart if (totPatientCount == 0) { resultPanel.innerHTML = "No patient matches the query" return } pieResultParam.data.content = institutions; new d3pie(resultDiv, pieResultParam); } else{ setTimeout(checkThenShow, 100) } }; checkThenShow(); }, // build the xml of the query from i2b2.MedCo.QT.panels buildQueryDefinition: function(name){ // name: name for the query // get the time of the query (we concatenate the time to the name of the query) var d = new Date(); var time = d.getHours().toString() + ":" + d.getMinutes().toString() + ":" + d.getSeconds().toString() + ":" + d.getMilliseconds().toString(); var rand = Math.random()+"" var queryxml = "\n\t" + name + "@" + time + ":" + rand + "\n\tANY\n\t0"; var panels = i2b2.MedCo.QT.panels; for (var i = 0 ; i < panels.length; i++){ // there are 3 panels if (panels[i].ctrlr.isEmpty()) {continue} // if in the panel there are no parameters the go to the next panel // open a panel object in which we put all its concepts queryxml += "\n\t\n\t\t" + "" + i + "\n\t\t" + "100\n\t\t" + "" + (panels[i].model.exclude ? 1 : 0) + "\n\t\t" + "ANY\n\t\t" + "1\n\t\t"; // put the all the concepts in the query for (var j = 0; j < panels[i].model.content.length; j++){ // take one "row" at a time and take encrypted values if sensitive var item_keys = []; var row = panels[i].model.content[j]; var dateConstraint = panels[i].model.date.model; if (row.sensitive){ // the row contains sensitive values for (var z=0; z" + item_keys[z] + "\n\t\t" if (dateConstraint.dateFrom || dateConstraint.dateTo){ queryxml += "\t" + (dateConstraint.dateFrom ? "\n\t\t\t\t" + dateConstraint.dateFrom.Year + "-" + dateConstraint.dateFrom.Month + "-" + dateConstraint.dateFrom.Day + "T00:00:00.000-05:00'\n\t\t\t" : "") + (dateConstraint.dateTo ? "\n\t\t\t\t" + dateConstraint.dateTo.Year + "-" + dateConstraint.dateTo.Month + "-" + dateConstraint.dateTo.Day + "T00:00:00.000-05:00'\n\t\t\t\t" : "") + "\n\t\t" } queryxml += "\n"; } } queryxml +="\n\t\n" } queryxml += "\n\n"; return queryxml }, }; i2b2.MedCo.QT.ctrlr.appendPanel = function(){ // Each element (row) visualized on a panel is bound either to a concept (which can // be either sensitive or non-sensitive) or to a list of variant ids (always sensitive). // create a new panel, add it to the list of panels and bind to it model, view and controller var panel = {}; panel.view = {}; panel.ctrlr = {}; panel.model = {}; i2b2.MedCo.QT.panels.push(panel); // ------ initialize the model of the panel ------ panel.model = { // indicates whether the panel is excluded or not "exclude": false, // contains a list of {"sensitive": bool, "values"=[ list of concepts/variant ids bound to i-th row of the panel ]} "content": [], // date constraint model and controller (to show the popup) "date": {}, // id of the whole html panel "panelId": 'MedCo-QueryPanel' + currId, // id of the html panel containing dropped concepts "conceptPanelId": "MedCo-QueryPanelConcepts" + currId }; currId++; // bind the panel to a date constraint model i2b2.MedCo.QT.dateConstraint.ctrlr.init(panel) // ------ initialize the view of the panel ------ document.getElementById("MedCo-QueryPanels").appendChild(htmlToElement( "
" + "
" + "
" + "" + "Clear" + "" + "
"+ "
Group " + i2b2.MedCo.QT.panels.length + "
" + "
" + "
" + "
" + "Dates"+ "
" + "
" + // "
" + "
" + "Exclude" + "
" + "
" + "
" + "
" + "
")); // // create and add the div for the dropped concepts i2b2.sdx.Master.AttachType(panel.model.conceptPanelId , 'CONCPT', {dropTarget: true}); i2b2.sdx.Master.setHandlerCustom(panel.model.conceptPanelId, 'CONCPT', 'DropHandler', function (sdxData) {panel.ctrlr.doDrop(sdxData);}); /* Instantiate a ContextMenu for this panel*/ YAHOO.util.Event.onContentReady(panel.model.conceptPanelId, function () { var conceptContextMenu = new YAHOO.widget.ContextMenu( panel.model.conceptPanelId+"ContextMenu", { trigger: document.getElementById(panel.model.conceptPanelId).childNodes, itemdata: ["Delete"], lazyload: true } ); // whenever the content of the panel changes, update the trigger property $(panel.model.conceptPanelId).on("DOMSubtreeModified",function(){ conceptContextMenu.cfg.setProperty("trigger", document.getElementById(panel.model.conceptPanelId).childNodes); }); function onContextMenuClick(p_sType, p_aArgs) { //p_aArgs[1]: MenuItem instance that was the target of the "click" event. var oItem = p_aArgs[1], // The MenuItem that was clicked oTarget = this.contextEventTarget, oLI; if (oItem) { // todo: also verify the class (sdxDefaultCONCPT)? oLI = oTarget.nodeName.toUpperCase() == "DIV" ? oTarget : YAHOO.util.Dom.getAncestorByClassName(oTarget, "sdxDefaultCONCPT"); switch (oItem.index) { case 0: // Delete // remove the concept from the view and from the model var panel_view = document.getElementById(panel.model.conceptPanelId) var index = Array.from(panel_view.children).indexOf(oLI) panel.model.content.splice(index, 1) panel_view.removeChild(oLI) break; // add here other cases if there are more menu items } } } // "render" event handler for the ewe context menu function onContextMenuRender(p_sType, p_aArgs) { // Add a "click" event handler to the ewe context menu this.subscribe("click", onContextMenuClick); } conceptContextMenu.subscribe("render", onContextMenuRender); // a bit rude bug fix... (hide menu when clicking on the panel) document.getElementById(panel.model.conceptPanelId).onclick = function () {conceptContextMenu.hide.call(conceptContextMenu)} }); panel.view.appendConcept = function (concept, output){ var conceptDiv = "
" + ((panel.model.exclude) ? "NOT" : "") + "" + concept + ((output)? (" (...)") : "") + "
"; // var panel.model.conceptPanelId = panel.view.getElementsByClassName('MedCo-QueryPanelConcepts')[0].id; var conceptPanel = document.getElementById(panel.model.conceptPanelId) conceptPanel.innerHTML += conceptDiv; return conceptPanel.lastChild }; panel.view.clear = function(){ panel.model.exclude = false; var excludeButton = document.getElementById(panel.model.panelId).getElementsByClassName("exclude")[0] excludeButton.style.textDecoration = panel.model.exclude? "underline":"none"; var concepts = document.getElementById(panel.model.conceptPanelId); while (concepts.firstChild) { concepts.removeChild(concepts.firstChild); } }; // ------ initialize the controller ------ panel.ctrlr.doDrop = function (sdxData) { var concept = sdxData[0]; // only interested in first record // CHECK: some useful functions: // alert(i2b2.h.getXNodeVal(sdxData.origData.xmlOrig, "level")) // alert(Object.getOwnPropertyNames(sdxData.origData)); // optimization to prevent requerying the hive for new results if the input dataset has not changed // i2b2.ExampTabs.model.dirtyResultsData = true; // check whether to show a popup or directly append the concept switch(concept.sdxInfo.sdxDisplayName) { case "Gene Name": i2b2.MedCo.popups.ByGene.ctrlr.show(i2b2.MedCo.QT.panels.indexOf(panel)); return; case "Protein Position": i2b2.MedCo.popups.ByProteinPosition.ctrlr.show(i2b2.MedCo.QT.panels.indexOf(panel)); return; case "Variant Name": i2b2.MedCo.popups.ByVariantName.ctrlr.show(i2b2.MedCo.QT.panels.indexOf(panel)); return; } var keyval = concept.sdxInfo.sdxKeyValue; var sensitive = !keyval.includes("nonsensitive") && !keyval.includes("non-sensitive"); var conceptModel = sensitive ? concept.origData.basecode.split(":")[1] : (concept.origData.key).replace(/ NOT  var not = document.createElement("span") not.className = "itemExclude" not.title = "This item is being excluded" not.innerHTML = "NOT" conceptsDiv[i].insertBefore(not, conceptsDiv[i].firstChild); } } else { // remove the "NOT" in front of the concepts in the div for (var i = 0; i < conceptsDiv.length; i++) { conceptsDiv[i].removeChild(conceptsDiv[i].getElementsByClassName("itemExclude")[0]) } } }; panel.ctrlr.delete = function(){ var index = i2b2.MedCo.QT.panels.indexOf(panel) if (index == -1){ alert("Impossible to delete the panel, panel inexistent.") return } // delete the panel from the model i2b2.MedCo.QT.panels.splice(index, 1) // delete the panel from the view var panels_container = document.getElementById("MedCo-QueryPanels") var panels = panels_container.childElements() panels_container.removeChild(panels[index]); // update the group number in the header of the panels panels = panels_container.childElements() for (var i = 0; i < panels.length; i++) { // i2b2.MedCo.QT.panels[i].group = (i + 1); var header = panels[i].getElementsByClassName("groupId")[0] header.innerHTML = 'Group ' + (i+1); } if (i2b2.MedCo.QT.panels.length != panels.length){ alert("Something went wrong: model and view are inconsistent.") } } panel.ctrlr.isEmpty = function(){ // check there is at least a row which contains something for (var i=0; i < panel.model.content.length; i++) { if (panel.model.content[i].values.length > 0) { return false; } } return true; } }; i2b2.MedCo.QT.ctrlr.deletePanel = function(HTMLelem){ // HTMLelem: (clicked) html element // first get the index of the panel var index = getPanelIndex(HTMLelem); if (index < 0){ alert("Panel not found"); return } // then call the delete function on it i2b2.MedCo.QT.panels[index].ctrlr.delete() }; i2b2.MedCo.QT.ctrlr.excludePanel = function(HTMLelem){ // HTMLelem: (clicked) html element // first get the index of the panel var index = getPanelIndex(HTMLelem) if (index < 0){ alert("Panel not found") return } // then call the exclude function on it i2b2.MedCo.QT.panels[index].ctrlr.exclude(HTMLelem) }; i2b2.MedCo.QT.ctrlr.showDateConstraint = function(HTMLelem){ var index = getPanelIndex(HTMLelem) if (index < 0){ alert("Panel not found") return } // alert("panel index:"+index) i2b2.MedCo.QT.panels[index].model.date.ctrlr.show() } \ No newline at end of file