UNPKG

knetmaps

Version:

Interactive network visualization tool for intuitive exploratory analysis of heterogeneous knowledge graphs

1,043 lines (943 loc) 105 kB
var KNETMAPS = KNETMAPS || {}; KNETMAPS.ConceptsLegend = function () { var stats = KNETMAPS.Stats(); var my = function () {}; my.conName = function (conText) { // Map the concept names to corresponding legend context names map = {"Biological_Process": "BioProc", "Molecular_Function": "MolFunc", "Cellular_Component": "CellComp", "Trait Ontology": "TO", "PlantOntologyTerm": "PO", "Enzyme Classification": "EC", "Quantitative Trait Locus": "QTL", "Protein Domain": "Domain"}; result = map[conText]; // If result is not null, return result, else return conText return result != null ? result : conText; } // Dynamically populate interactive concept legend. my.populateConceptLegend = function () { var cy = $('#cy').cytoscape('get'); var conNodes = cy.nodes(); var conceptTypes = []; // get a unique Array with all concept Types in current network. conNodes.forEach(function (ele) { if (conceptTypes.indexOf(ele.data('conceptType')) === -1) { conceptTypes.push(ele.data('conceptType')); } }); // conceptTypes.sort(); // sort alpabetically, fails as "Trait" is displayed as "GWAS" var conceptsHashmap = {}; conceptTypes.forEach(function (conType, index) { // Get the total number of concepts for that concept var totConCount = conNodes.filterFn(function (ele) { return ele.data('conceptType') === conType; }).size(); // Get a collection of all the visible nodes and their length var currently_visibleNodes = cy.collection(); conNodes.filter('node[conceptType="' + conType + '"]').forEach(function (conc) { if (conc.hasClass('ShowEle')) { currently_visibleNodes = currently_visibleNodes.add(conc); } }); var visible_len = $(currently_visibleNodes).length; conceptsHashmap[conType] = (visible_len + "/" + totConCount); }); // update knetLegend. var knetLegend = '<div class="knetInteractiveLegend"><div class="knetLegend_row">' + '<div class="knetLegend_cell"><b>Interactive Legend:</b></div>'; // Show concept Type icons (with total count displayed alongside). for (var con in conceptsHashmap) { // Iterate through the Concept Classes // Check to see if any additional nodes currently exist for the icon, if not make icon opaque var conText = KNETMAPS.ConceptsLegend().conName(con); var conID = KNETMAPS.ConceptsLegend().conNameFormat(con); var collections = KNETMAPS.ConceptsLegend().nodeClassesOnGraph(cy, conID), hiddenEdges = collections.hiddenEdges, hiddenNodes = collections.hiddenNodes, currently_visibleNodes = collections.visibleNodes; var edge_count = $(hiddenNodes.edgesWith(currently_visibleNodes)).length; if (edge_count == 0 && hiddenEdges >= 0) { knetLegend = knetLegend + '<div class="knetLegend_cell"><input type="submit" value="" id="' + con.replace(/ /g, '_') + '" title="Show or remove all ' + con.replace(/_/g, ' ') + '(s)" class="knetCon_' + con.replace(/ /g, '_') + " opacity_class" + '" style="vertical-align:middle" ontouchmove="KNETMAPS.ConceptsLegend().hideConnectedByType(this.id);" ondblclick="KNETMAPS.ConceptsLegend().hideConnectedByType(this.id);" onclick="KNETMAPS.ConceptsLegend().showConnectedByType(this.id);">' + '<span class="icon_caption">' + conceptsHashmap[con] + '<br>' + conText + '</span></div>'; } else { knetLegend = knetLegend + '<div class="knetLegend_cell"><input type="submit" value="" id="' + con.replace(/ /g, '_') + '" title="Show or remove all ' + con.replace(/_/g, ' ') + '(s)" class="knetCon_' + con.replace(/ /g, '_') + '" style="vertical-align:middle" ontouchmove="KNETMAPS.ConceptsLegend().hideConnectedByType(this.id);" ondblclick="KNETMAPS.ConceptsLegend().hideConnectedByType(this.id);" onclick="KNETMAPS.ConceptsLegend().showConnectedByType(this.id);">' + '<span class="icon_caption">' + conceptsHashmap[con] + '<br>' + conText + '</span></div>'; } } knetLegend = knetLegend + "</div></div>"; $('#knetLegend').html(knetLegend); // update knetLegend } my.conNameFormat = function (conType) { if (conType === "Trait_Ontology" || conType === "Enzyme_Classification" || conType === "Quantitative_Trait_Locus" || conType === "Protein_Domain") { return conType.replace(/_/g, ' '); } if (conType === "Biological Process" || conType === "Molecular Function" || conType === "Cellular Component") { return conType.replace(/ /g, '_'); } else { return conType; } } my.nodeClassesOnGraph = function (cy, conType) { // Obtain all hidden and currently visible nodes on the graph based upon the class they contain var visibleNodes_ofSameType = cy.collection(), hiddenNodes_ofSameType = visibleNodes_ofSameType, currently_visibleNodes = visibleNodes_ofSameType, currently_hiddenNodes = visibleNodes_ofSameType; var conText = KNETMAPS.ConceptsLegend().conName(conType); // Filter by the type clicked by the user cy.nodes().filter('node[conceptType="' + conType + '"]').forEach(function (conc) { // If the node has the class tag of "show element" then add it to the collection class if (conc.hasClass('ShowEle')) { visibleNodes_ofSameType = visibleNodes_ofSameType.add(conc); } if (conc.hasClass('HideEle')) { hiddenNodes_ofSameType = hiddenNodes_ofSameType.add(conc); } }); cy.nodes().forEach(function (conc) { // Only if the concept is visible if (conc.hasClass('ShowEle')) { // Add to Cytoscape collection. currently_visibleNodes = currently_visibleNodes.add(conc); } if (conc.hasClass('HideEle')) { currently_hiddenNodes = currently_hiddenNodes.add(conc); } }); // Obtain all the count of all of the concepts within the graph of this type var totConcepts = $(visibleNodes_ofSameType).length + $(hiddenNodes_ofSameType).length; var hiddenEdges = $(hiddenNodes_ofSameType.edgesWith(currently_hiddenNodes)).length; return {hiddenNodes: hiddenNodes_ofSameType, visibleNodes: currently_visibleNodes, visibleOfSameType: visibleNodes_ofSameType, total: totConcepts, text: conText, hiddenEdges: hiddenEdges}; } my.conceptCount = function (cy, conType, conText, totConcepts) { // Update the name of the icon with the relevant counts accordingly $(document).ready(function () { var currentNodes_ofSameType = cy.collection(); cy.nodes().filter('node[conceptType="' + conType + '"]').forEach(function (conc) { if (conc.hasClass('ShowEle')) { currentNodes_ofSameType = currentNodes_ofSameType.add(conc); } }); // Update the name with the current nodes / total nodes and the node type name. var nameToUpdate = " " + $(currentNodes_ofSameType).length + '\/' + totConcepts + '<br>' + conText; $("span.icon_caption").each(function () { if ($(this).text().includes(conText)) { $(this).html(nameToUpdate); } }); // If there's no nodes present of the same type then no error message should display anymore. if ($(currentNodes_ofSameType).length === 0) { $('#infoDialog').html(""); } }); } my.showConnectedByType = function (conType) { $('#infoDialog').html(""); // get all cytoscape elements via jQuery var cy = $('#cy').cytoscape('get'); var conID = KNETMAPS.ConceptsLegend().conNameFormat(conType); console.log("ID is: " + conID); var collections = KNETMAPS.ConceptsLegend().nodeClassesOnGraph(cy, conID), hiddenNodes_ofSameType = collections.hiddenNodes, currently_visibleNodes = collections.visibleNodes, conText = collections.text, totConcepts = collections.total, hiddenEdges = collections.hiddenEdges; $('#' + conID.replace(/ /g, '_')).removeClass('opacity_class'); // end here var edge_count = $(hiddenNodes_ofSameType.edgesWith(currently_visibleNodes)).length; if (edge_count > 0) { // Display hidden nodes of same Type which are connected to currently visible Nodes. hiddenNodes_ofSameType.edgesWith(currently_visibleNodes).connectedNodes().addClass('ShowEle').removeClass('HideEle'); // Display edges between such connected Nodes too. hiddenNodes_ofSameType.edgesWith(currently_visibleNodes).addClass('ShowEle').removeClass('HideEle'); KNETMAPS.ConceptsLegend().populateConceptLegend(); } if (hiddenEdges >= 0 && edge_count == 0) { KNETMAPS.ConceptsLegend().populateConceptLegend(); $('#infoDialog').html('<font color="red">' + "Can't show more " + conText + " concept nodes. Please check your graph.</font>").show(); $('#' + conID.replace(/ /g, '_')).addClass('opacity_class'); } stats.updateKnetStats(); // Refresh network Stats. KNETMAPS.ConceptsLegend().conceptCount(cy, conID, conText, totConcepts); // Update the text count } my.hideConnectedByType = function (conType) { $('#infoDialog').html(""); // get all cytoscape elements via jQuery var cy = $('#cy').cytoscape('get'); var conID = KNETMAPS.ConceptsLegend().conNameFormat(conType); // Define the visible nodes of the same type as a Cytoscape collection var collections = KNETMAPS.ConceptsLegend().nodeClassesOnGraph(cy, conID), visibleNodes_ofSameType = collections.visibleOfSameType, currently_visibleNodes = collections.visibleNodes, conText = collections.text, totConcepts = collections.total; $('#' + conID.replace(/_/g, ' ')).removeClass('opacity_class'); var edge_count = $(visibleNodes_ofSameType.edgesWith(currently_visibleNodes)).length; // Obtain the number of edges present // Ensure that Gene concepts cannot be removed. if (conText.includes("Gene") == false) { // Set the collection of visible nodes to hidden and remove its show class, thus setting the CSS display to None. cy.nodes().forEach(function (ele) { if (ele.data('conceptType') === conID) { ele.removeClass('ShowEle').addClass('HideEle'); } }); stats.updateKnetStats(); // Refresh network Stats. } else if (conText.includes("Gene")) { // Show appropriate error regarding user attempt to remove the Gene concept. cy.nodes().forEach(function (ele) { if (ele.hasClass('FlaggedGene')) { $('#infoDialog').html('<font color="red">' + "Can't remove the main Gene Concept node.</font>").show(); } else if (ele.data('conceptType') == conID) { ele.removeClass('ShowEle').addClass('HideEle'); $('#infoDialog').html(""); stats.updateKnetStats(); } }); } else if (edge_count > 0 && conText.includes("Gene") == false) { $('#infoDialog').html(""); } //KNETMAPS.ConceptsLegend().conceptCount(cy, conID, conText, totConcepts); // Update the text count KNETMAPS.ConceptsLegend().populateConceptLegend(); } return my; }; var KNETMAPS = KNETMAPS || {}; KNETMAPS.Container = function() { var stats = KNETMAPS.Stats(); var iteminfo = KNETMAPS.ItemInfo(); var conceptLegend = KNETMAPS.ConceptsLegend(); var layouts = KNETMAPS.Layouts(); var my = function() {}; my.load_reload_Network = function(network_json, network_style, isReloaded) { console.log("load the cytoscapeJS network... isJSON: "+ isReloaded); // Initialise a cytoscape container instance on the HTML DOM using JQuery. //var cy = cytoscape({ var cy = window.cy = cytoscape({ container: document.getElementById('cy')/*$('#cy')*/, style: network_style, // Using the JSON data to create the nodes. elements: network_json, // layout of the Network: isReloaded (set preset layout), else (for JS vars, do nothing). layout: isReloaded ? { name: 'preset' } : '', // this is an alternative that uses a bitmap during interaction. textureOnViewport: false, // true, /* the colour of the area outside the viewport texture when initOptions.textureOnViewport === true can * be set by: e.g., outside-texture-bg-color: white, */ // interpolate on high density displays instead of increasing resolution. pixelRatio: 1, // Zoom settings zoomingEnabled: true, // zooming: both by user and programmatically. // userZoomingEnabled: true, // user-enabled zooming. zoom: 1, // the initial zoom level of the graph before the layout is set. // minZoom: 1e-50, maxZoom: 1e50, /* mouse wheel sensitivity settings to enable a more gradual Zooming process. A value between 0 and 1 * reduces the sensitivity (zooms slower) & a value greater than 1 increases the sensitivity. */ wheelSensitivity: 0.05, panningEnabled: true, // panning: both by user and programmatically. // userPanningEnabled: true, // user-enabled panning. // for Touch-based gestures. // selectionType: (isTouchDevice ? 'additive' : 'single'), touchTapThreshold: 8, desktopTapThreshold: 4, autolock: false, autoungrabify: false, autounselectify: false, // a "motion blur" effect that increases perceived performance for little or no cost. motionBlur: true, ready: function() { if(isReloaded === false) { /* when rendering new knetwork or maximize/minimize, then run selected (default) layout. For reloaded knetworks, skip this. */ KNETMAPS.Menu().rerunLayout(); // reset current layout. } window.cy= this; } }); // Get the cytoscape instance as a Javascript object from JQuery. //var cy= $('#cy').cytoscape('get'); // now we have a global reference to `cy` // cy.boxSelectionEnabled(true); // enable box selection (highlight & select multiple elements for moving via mouse click and drag). cy.boxSelectionEnabled(false); // to disable box selection & hence allow Panning, i.e., dragging the entire graph. // Set requisite background image for each concept (node) instead of using cytoscapeJS shapes. /* cy.nodes().forEach(function( ele ) { var conType= ele.data('conceptType'); var imgName= 'Gene'; // default if(conType === "Biological_Process") { imgName= 'Biological_process'; } else if(conType === "Cellular_Component") { imgName= 'Cellular_component'; } else if(conType === "Gene") { imgName= 'Gene'; } else if(conType === "Protein Domain") { imgName= 'Protein_domain'; } else if(conType === "Pathway") { imgName= 'Pathway'; } else if(conType === "Reaction") { imgName= 'Reaction'; } else if(conType === "Publication") { imgName= 'Publication'; } else if(conType === "Protein") { imgName= 'Protein'; } else if(conType === "Quantitative Trait Locus") { imgName= 'QTL'; } else if(conType === "Enzyme") { imgName= 'Enzyme'; } else if(conType === "Molecular_Function") { imgName= 'Molecular_function'; } else if((conType === "Enzyme_Classification") || (conType === "Enzyme Classification")) { imgName= 'Enzyme_classification'; } else if(conType === "Trait Ontology") { imgName= 'Trait_ontology'; } else if(conType === "Scaffold") { imgName= 'Scaffold'; } else if((conType === "Compound") || (conType === "SNP")) { imgName= 'Compound'; } else if(conType === "Phenotype") { imgName= 'Phenotype'; } var eleImage= 'image/'+ imgName +'.png'; // var eleImage= data_url +'image/'+ imgName +'.png'; // Add these properties to this element's JSON. ele.data('nodeImage', eleImage); // console.log("set data.nodeImage "+ ele.data('nodeImage')); }); // Update the stylesheet for the Network Graph to show background images for Nodes. cy.style().selector('node').css({ // Show actual background images. 'background-image': 'data(nodeImage)', 'background-fit': 'none' // can be 'none' (for original size), 'contain' (to fit inside node) or 'cover' (to cover the node). }).update(); */ /** Add a Qtip message to all the nodes & edges using QTip displaying their Concept Type & value when a node/ edge is clicked. * Note: Specify 'node' or 'edge' to bind an event to a specific type of element. * e.g, cy.elements('node').qtip({ }); or cy.elements('edge').qtip({ }); */ cy.elements().qtip({ content: function() { var qtipMsg= ""; try { if(this.isNode()) { qtipMsg= "<b>Concept:</b> "+ this.data('value') +"<br/><b>Type:</b> "+ this.data('conceptType'); } else if(this.isEdge()) { qtipMsg= "<b>Relation:</b> "+ this.data('label'); var fromID= this.data('source'); // relation source ('fromConcept') qtipMsg= qtipMsg +"<br/><b>From:</b> "+ cy.$('#'+fromID).data('value') +" ("+ cy.$('#'+fromID).data('conceptType').toLowerCase() +")"; var toID= this.data('target'); // relation source ('toConcept') qtipMsg= qtipMsg +"<br/><b>To:</b> "+ cy.$('#'+toID).data('value') +" ("+ cy.$('#'+toID).data('conceptType').toLowerCase() +")"; } } catch(err) { qtipMsg= "Selected element is neither a Concept nor a Relation"; } return qtipMsg; }, style: { classes: 'qtip-bootstrap', tip: { width: 12, height: 6 } } }); /** Event handling: mouse 'tap' event on all the elements of the core (i.e., the cytoscape container). * Note: Specify 'node' or 'edge' to bind an event to a specific type of element. * e.g, cy.on('tap', 'node', function(e){ }); or cy.on('tap', 'edge', function(e){ }); */ cy.on('tap', function(e) { var thisElement= e.cyTarget; var info= ""; try { if(thisElement.isNode()) { info= "<b>Concept:</b> "+ thisElement.data('value') +"<br/><b>Type:</b> "+ thisElement.data('conceptType'); } else if(thisElement.isEdge()) { info= "<b>Relation:</b> "+ this.data('label'); var fromID= this.data('source'); // relation source ('fromConcept') info= info +"<br/><b>From:</b> "+ cy.$('#'+fromID).data('value') +" ("+ cy.$('#'+fromID).data('conceptType').toLowerCase(); var toID= this.data('target'); // relation source ('toConcept') info= info +"<br/><b>To:</b> "+ cy.$('#'+toID).data('value') +" ("+ cy.$('#'+toID).data('conceptType').toLowerCase() +")"; } } catch(err) { info= "Selected element is neither a Concept nor a Relation"; } //console.log(info); iteminfo.showItemInfo(thisElement); }); // cxttap - normalised right click or 2-finger tap event. /** Popup (context) menu: a circular Context Menu for each Node (concept) & Edge (relation) using the 'cxtmenu' jQuery plugin. */ var contextMenu= { menuRadius: 80, // the radius of the circular menu in pixels // Use selector: '*' to set this circular Context Menu on all the elements of the core. /** Note: Specify selector: 'node' or 'edge' to restrict the context menu to a specific type of element. e.g, * selector: 'node', // to have context menu only for nodes. * selector: 'edge', // to have context menu only for edges. */ selector: '*', commands: [ // an array of commands to list in the menu { content: 'Show Info', select: function() { // Show Item Info Pane. iteminfo.openItemInfoPane(); // Display Item Info. iteminfo.showItemInfo(this); } }, { content: 'Show Links', select: function() { if(this.isNode()) { iteminfo.showLinks(this); // Refresh network legend. stats.updateKnetStats(); } } }, { content: 'Hide', select: function() { //this.hide(); // hide the selected 'node' or 'edge' element. this.removeClass('ShowEle'); this.addClass('HideEle'); // Refresh network legend. stats.updateKnetStats(); conceptLegend.populateConceptLegend(); } }, // disabled Hide by Type feature (Jan. 2020) /* { content: 'Hide by Type', select: function() { // Hide all concepts (nodes) of the same type. if(this.isNode()) { var thisConceptType= this.data('conceptType'); // console.log("Hide Concept by Type: "+ thisConceptType); cy.nodes().forEach(function( ele ) { if(ele.data('conceptType') === thisConceptType) { //ele.hide(); ele.removeClass('ShowEle'); ele.addClass('HideEle'); } }); // Relayout the graph. //rerunLayout(); } else if(this.isEdge()) { // Hide all relations (edges) of the same type. var thisRelationType= this.data('label'); // console.log("Hide Relation (by Label type): "+ thisRelationType); cy.edges().forEach(function( ele ) { if(ele.data('label') === thisRelationType) { //ele.hide(); ele.removeClass('ShowEle'); ele.addClass('HideEle'); } }); // Relayout the graph. // rerunLayout(); } // Refresh network Stats. stats.updateKnetStats(); conceptLegend.populateConceptLegend(); } }, */ { // Turn the highlighter on or off, respectively. content: 'Highlighter on/off', select: function () { if (this.isNode() && this.css('text-background-opacity') == '1') { this.css({ 'text-background-opacity': '0' }); } else if (this.isNode() && this.css('text-background-opacity') == '0') { this.css({ 'text-background-opacity': '1' }); } } }, // { // content: 'Hide by Type', // select: function() { // Hide all concepts (nodes) of the same type. // if(this.isNode()) { // var thisConceptType= this.data('conceptType'); // // console.log("Hide Concept by Type: "+ thisConceptType); // cy.nodes().forEach(function( ele ) { // if(ele.data('conceptType') === thisConceptType) { // //ele.hide(); // ele.removeClass('ShowEle'); // ele.addClass('HideEle'); // } // }); // // Relayout the graph. // //rerunLayout(); // } // else if(this.isEdge()) { // Hide all relations (edges) of the same type. // var thisRelationType= this.data('label'); // // console.log("Hide Relation (by Label type): "+ thisRelationType); // cy.edges().forEach(function( ele ) { // if(ele.data('label') === thisRelationType) { // //ele.hide(); // ele.removeClass('ShowEle'); // ele.addClass('HideEle'); // } // }); // // Relayout the graph. // // rerunLayout(); // } // // Refresh network Stats. // stats.updateKnetStats(); // conceptLegend.populateConceptLegend(); // } // }, { content: 'Label on/ off by Type', select: function() { var thisElementType, eleType, elements; if(this.isNode()) { thisElementType= this.data('conceptType'); // get all concept Types. eleType= 'conceptType'; elements= cy.nodes(); // fetch all the nodes. } else if(this.isEdge()) { thisElementType= this.data('label'); // get all relation Labels. eleType= 'label'; elements= cy.edges(); // fetch all the edges. } if(this.hasClass("LabelOff")) { // show the concept/ relation Label. elements.forEach(function( ele ) { if(ele.data(eleType) === thisElementType) { // for same concept or relation types if(ele.hasClass("LabelOff")) { ele.removeClass("LabelOff"); ele.addClass("LabelOn"); } } }); } else if(this.hasClass("LabelOn")) { // hide the concept/ relation Label. elements.forEach(function( ele ) { if(ele.data(eleType) === thisElementType) { // for same concept or relation types if(ele.hasClass("LabelOn")) { ele.removeClass("LabelOn"); ele.addClass("LabelOff"); } } }); } } }, { content: 'Label on/ off', select: function() { if(this.hasClass("LabelOff")) { // show the concept/ relation Label. this.removeClass("LabelOff"); this.addClass("LabelOn"); } else if(this.hasClass("LabelOn")) { // hide the concept/ relation Label. this.removeClass("LabelOn"); this.addClass("LabelOff"); } } } ], fillColor: 'rgba(0, 0, 0, 0.75)', // the background colour of the menu activeFillColor: 'rgba(92, 194, 237, 0.75)', // the colour used to indicate the selected command activePadding: 2, // 20, // additional size in pixels for the active command indicatorSize: 15, // 24, // the size in pixels of the pointer to the active command separatorWidth: 3, // the empty spacing in pixels between successive commands spotlightPadding: 3, // extra spacing in pixels between the element and the spotlight minSpotlightRadius: 5, // 24, // the minimum radius in pixels of the spotlight maxSpotlightRadius: 10, // 38, // the maximum radius in pixels of the spotlight itemColor: 'white', // the colour of text in the command's content itemTextShadowColor: 'black', // the text shadow colour of the command's content zIndex: 9999 // the z-index of the ui div }; cy.cxtmenu(contextMenu); // set Context Menu for all the core elements. // Show the popup Info. dialog box. $('#infoDialog').click(function() { $('#infoDialog').slideToggle(300); }); }; return my; }; var KNETMAPS = KNETMAPS || {}; var graphJSON = graphJSON || ''; var allGraphData = allGraphData || ''; KNETMAPS.Generator = function() { var stats = KNETMAPS.Stats(); var iteminfo = KNETMAPS.ItemInfo(); var container = KNETMAPS.Container(); var legend = KNETMAPS.ConceptsLegend(); var my = function() {}; // initialize and generate the network from default global vars my.generateNetworkGraph=function(eles_jsons, metadata_json, eles_styles) { //console.log("Dataset file path: "+ json_File); graphJSON = eles_jsons; // set graphJSON to reloaded JSON. allGraphData = metadata_json; // Initialize the cytoscapeJS container for Network View. // NB graphJSON and allGraphData should be declared outside this script my.initializeNetworkView(graphJSON, allGraphData, eles_styles); // Highlight nodes with hidden, connected nodes using Shadowing. my.blurNodesWithHiddenNeighborhood(); // re-color gene labels using various shades of blue, based on TAXID my.colorGeneLabelsByTaxID(allGraphData); // Set the default layout. // setDefaultLayout(); // update network stats <div>. stats.updateKnetStats(); // dynamically populate interactive concept legend. legend.populateConceptLegend(); } //initialize and generate the network from provided JSON blob my.generateNetworkGraphRaw=function(json_blob) { //console.log("Dataset file path: "+ json_File); eval(json_blob+'; my.initializeNetworkView(graphJSON, allGraphData); my.blurNodesWithHiddenNeighborhood(); my.colorGeneLabelsByTaxID(allGraphData); stats.updateKnetStats(); legend.populateConceptLegend();'); }; // initialize the network my.initializeNetworkView=function(networkJSON, metadataJSON, existing_styles) { graphJSON = networkJSON; allGraphData = metadataJSON; // modify for networkJSON to read JSON object from file and retain contents from "elements" section for nodes and edges info. // var metadataJSON= allGraphData; // using the dynamically included metadata JSON object directly. var networkStylesheet= ''; var isReloaded= false; if(existing_styles != null || existing_styles != undefined) { // load existing node x,y & style classes/selectors. networkStylesheet= existing_styles; isReloaded= true; } else { // Define the stylesheet to be used for nodes & edges in the cytoscape.js container. networkStylesheet= cytoscape.stylesheet().selector('node').css({ 'content': 'data(displayValue)', /*function(ele) { var label= ''; if(ele.data('value').indexOf('<span') > -1) { // For html content from text, use html tags. var txtLabel= '<html>'+ ele.data('value') +'</html>'; label= jQuery(txtLabel).text(); } else { label= ele.data('value'); } // Trim the label's length. if(label.length> 30) { label= label.substr(0,29)+'...'; } return label; },*/ // 'text-valign': 'center', // to have 'content' displayed in the middle of the node. 'text-background-color': 'data(conceptTextBGcolor)', 'text-background-opacity': 'data(conceptTextBGopacity)',//'0', // default: '0' (disabled). /*function(ele) { // text background opacity var textBackgroundOpacity= '0'; if(ele.data('value').indexOf('<span') > -1) { textBackgroundOpacity= '1'; } return textBackgroundOpacity; },*/ 'text-wrap': 'wrap', // for manual and/or autowrapping the label text. // 'edge-text-rotation' : 'autorotate', // rotate edge labels as the angle of an edge changes: can be 'none' or 'autorotate'. 'border-style': 'data(conceptBorderStyle)',//'solid', // node border, can be 'solid', 'dotted', 'dashed' or 'double'. /*function(ele) { var node_borderStyle= 'solid'; try { // Check if the node was flagged or not if(ele.data('flagged') === "true") { node_borderStyle= 'double'; // can be 'solid', 'dotted', 'dashed' or 'double'. // console.log("node Flagged= "+ ele.data('flagged') +" , node_borderStyle: "+ node_borderStyle); } } catch(err) { console.log(err.stack); } return node_borderStyle; },*/ 'border-width': 'data(conceptBorderWidth)',//'1px', /*function(ele) { var node_borderWidth= '1px'; try { // Check if the node was flagged or not if(ele.data('flagged') === "true") { node_borderWidth= '3px'; // console.log("node Flagged= "+ ele.data('flagged') +" , node_borderWidth: "+ node_borderWidth); } } catch(err) { console.log(err.stack); } return node_borderWidth; },*/ 'border-color': 'data(conceptBorderColor)',//'black', /*function(ele) { var node_borderColor= 'black'; try { // Check if the node was flagged or not if(ele.data('flagged') === "true") { node_borderColor= 'navy'; // console.log("node Flagged= "+ ele.data('flagged') +" , node_borderColor: "+ node_borderColor); } } catch(err) { console.log(err.stack); } return node_borderColor; },*/ 'font-size': '16px', // '8px', // 'min-zoomed-font-size': '8px', // Set node shape, color & display (visibility) depending on settings in the JSON var. 'shape': 'data(conceptShape)', // 'triangle' 'width': 'data(conceptSize)', // '18px', 'height': 'data(conceptSize)', // '18px', 'background-color': 'data(conceptColor)', // 'gray' /** Using 'data(conceptColor)' leads to a "null" mapping error if that attribute is not defined * in cytoscapeJS. Using 'data[conceptColor]' is hence preferred as it limits the scope of * assigning a property value only if it is defined in cytoscapeJS as well. */ 'display': 'data(conceptDisplay)', // display: 'element' (show) or 'none' (hide). 'text-opacity': '0' // to make the label invisible by default. }) .selector('edge').css({ 'content': 'data(label)', // label for edges (arrows). 'font-size': '16px', // 'min-zoomed-font-size': '8px', 'curve-style': 'unbundled-bezier', /* options: bezier (curved) (default), unbundled-bezier (curved with manual control points), haystack (straight edges) */ 'control-point-step-size': '10px', //'1px' // specifies the distance between successive bezier edges. 'control-point-distance': '20px', /* overrides control-point-step-size to curves single edges as well, in addition to parallele edges */ 'control-point-weight': '50'/*'0.7'*/, // '0': curve towards source node, '1': curve towards target node. 'width': 'data(relationSize)', // 'mapData(relationSize, 70, 100, 2, 6)', // '3px', //'line-color': 'data(relationColor)', // e.g., 'grey', 'line-color': 'data(relationColor)', 'line-style': 'solid', // 'solid' (or 'dotted', 'dashed') 'target-arrow-shape': 'triangle', 'target-arrow-color': 'gray', 'display': 'data(relationDisplay)', // display: 'element' (show) or 'none' (hide). 'text-opacity': '0' // to make the label invisible by default. }) .selector('.highlighted').css({ 'background-color': '#61bffc', 'line-color': '#61bffc', 'target-arrow-color': '#61bffc', 'transition-property': 'background-color, line-color, target-arrow-color', 'transition-duration': '0.5s' }) .selector(':selected').css({ // settings for highlighting nodes in case of single click or Shift+click multi-select event. 'border-width': '4px', 'border-color': '#CCCC33' // '#333' }) .selector('.BlurNode').css({ // settings for using shadow effect on nodes when they have hidden, connected nodes. 'shadow-blur': '25', // disable for larger network graphs, use x & y offset(s) instead. 'shadow-color': 'black', // 'data(conceptColor)', 'shadow-opacity': '0.9' }) .selector('.HideEle').css({ // settings to hide node/ edge 'display': 'none' }) .selector('.ShowEle').css({ // settings to show node/ edge 'display': 'element' }) .selector('.LabelOn').css({ // settings to show Label on node/ edge 'text-opacity': '1' }) .selector('.LabelOff').css({ // settings to show Label on node/ edge 'text-opacity': '0' }) .selector('.darkgreyEdge').css({ 'line-color': 'darkGrey' }) .selector('.orangeEdge').css({ 'line-color': 'orange' }) .selector('.dashedEdge').css({ // dashed edge 'line-style': 'dashed' }) .selector('.FlaggedGene').css({ // to show highlighed label on flagged gene 'text-background-color': '#FFFF00', 'text-background-opacity': '1' }); } // On startup $(function() { // on dom ready console.log("initialising..."); // load the cytoscapeJS network to render container.load_reload_Network(networkJSON, networkStylesheet, isReloaded); my.append_visibility_and_label_classes(); // to all network nodes/ edges. }); // on dom ready } my.append_visibility_and_label_classes=function() { var cy= $('#cy').cytoscape('get'); // now we have a global reference to `cy` cy.nodes().forEach(function( conc ) { // for concepts // Add relevant Concept visibility class if(conc.data('conceptDisplay') === 'element') { conc.addClass('ShowEle'); } else { conc.addClass('HideEle'); } // Add relevant label visibility class if(conc.style('text-opacity') === '0') { conc.addClass('LabelOff'); } else { conc.addClass('LabelOn'); } // show flagged gene's labels if(conc.data('flagged') === 'true') { conc.removeClass('LabelOff').addClass('LabelOn'); conc.addClass('FlaggedGene'); } }); cy.edges().forEach(function( rel ) { // for relations // Add relevant Relation visibility class if(rel.data('relationDisplay') === 'element') { rel.addClass('ShowEle'); } else { rel.addClass('HideEle'); } // Add relevant label visibility class if(rel.style('text-opacity') === '0') { rel.addClass('LabelOff'); } else { rel.addClass('LabelOn'); } }); // new: also show labels for all Genes, Biological Process and Trait nodes. cy.nodes().forEach(function( conc ) { // for concepts/nodes if((conc.data('conceptType') === "Gene") || (conc.data('conceptType') === "Biological_Process") || (conc.data('conceptType') === "Trait") || (conc.data('conceptType') === "Trait Ontology")) { // then display labels for these nodes if they are visble already. if(conc.data('conceptDisplay') === 'element') { conc.removeClass('LabelOff').addClass('LabelOn'); } } }); // fix any nodes with black conceptTextBGcolor in json cy.nodes().forEach(function( conc ) { if(conc.data('conceptTextBGcolor') === 'black') { conc.data('conceptTextBGcolor','lightGreen'); // set new color (lightGreen) } }); // modify to dashed edges (line-style) for certain cases, and assign new line-color in some cases too cy.edges().forEach(function( rel ) { if(rel.data('label') === "xref") { rel.addClass('darkgreyEdge'); } if(rel.data('label') === "associated_with") { rel.addClass('darkgreyEdge'); } if(rel.data('label') === "occurs_in") { rel.addClass('orangeEdge'); } // use dashed line style for relation type: cooccurs_with, occurs_in, regulates, has_similar_sequence, enriched_for. var special_edges= [ "cooccurs_with", "occurs_in", "regulates", "has_similar_sequence", "enriched_for" ]; if(special_edges.includes(rel.data('label'))) { rel.addClass('dashedEdge'); } }); } // Show concept neighbourhood. /* function showNeighbourhood() { console.log("Show neighborhood: Display concepts in the neighbourhood of the selected concept (node)..."); var selectedNodes= cy.nodes(':selected'); selectedNodes.neighborhood().nodes().show(); selectedNodes.neighborhood().edges().show(); // Remove shadow effect from the nodes that had hidden nodes in their neighborhood. selectedNodes.forEach(function( ele ) { removeNodeBlur(ele); }); }*/ // Show shadow effect on nodes with connected, hidden elements in their neighborhood. my.blurNodesWithHiddenNeighborhood=function() { var cy= $('#cy').cytoscape('get'); // now we have a global reference to `cy` cy.nodes().forEach(function( ele ) { var thisElement= ele; var eleID, connected_hiddenNodesCount= 0; try { // Retrieve the nodes in this element's neighborhood. // var neighborhood_nodes= thisElement.neighborhood().nodes(); eleID= thisElement.id(); // element ID. // Retrieve the directly connected nodes in this element's neighborhood. var connected_edges= thisElement.connectedEdges(); // Get all the relations (edges) with this concept (node) as the source. // var connected_edges= thisElement.connectedEdges().filter('edge[source = '+eleID+']'); var connected_hidden_nodes= connected_edges.connectedNodes().filter('node[conceptDisplay = "none"]'); // Find the number of hidden, connected nodes. connected_hiddenNodesCount= connected_hidden_nodes.length; if(connected_hiddenNodesCount > 1) { // Show shadow around nodes that have hidden, connected nodes. thisElement.addClass('BlurNode'); } } catch(err) { console.log("Error occurred while adding Shadow to concepts with connected, hidden elements. \n"+"Error Details: "+ err.stack); } }); } my.colorGeneLabelsByTaxID=function(metadata_json) { var cy= $('#cy').cytoscape('get'); // now we have a global reference to `cy` // re-color genes based on different taxid (range of 7 blues) var gene_colors= ['#e6f5fe','#82cbfc','#50b7fb','lightBlue','#0598fa','#74b9e7','#64bcf7']; var all_taxids= []; // get all gene taxID's and make a list[] of them cy.nodes().forEach(function( conc ) { if(conc.data('conceptType') === "Gene") { var this_taxid=""; for(var i=0; i<metadata_json.ondexmetadata.concepts.length;i++) { // if matching concept ID found, traverse attributes to get TAXID if(conc.data('id') === metadata_json.ondexmetadata.concepts[i].id) { for(var j=0; j<metadata_json.ondexmetadata.concepts[i].attributes.length; j++) { if((metadata_json.ondexmetadata.concepts[i].attributes[j].attrname === "TAXID") || (metadata_json.ondexmetadata.concepts[i].attributes[j].attrnameName === "TX")) { this_taxid= metadata_json.ondexmetadata.concepts[i].attributes[j].value; // store unique taxID in list[] if(all_taxids.indexOf(this_taxid) === -1) { all_taxids.push(this_taxid); } } } } } } }); //console.log("all_taxids: "+ all_taxids); var genes_taxid = new Map(); var k=0; // make k,v map of all taxID and blue shades/colors for gene labels for(var j=0; j<all_taxids.length;j++) { genes_taxid.set(all_taxids[j], gene_colors[j]); k= k+1; // reset color counter if no. of taxID's exceedes our label blue shades range (though unlikely) if(k > gene_colors.length) { k=0; } } // now change label color for genes accordingly cy.nodes().forEach(function( conc ) { if(conc.data('conceptType') === "Gene") { var this_taxid=""; // fetch TAXID again for(var i=0; i<metadata_json.ondexmetadata.concepts.length;i++) { if(conc.data('id') === metadata_json.ondexmetadata.concepts[i].id) { for(var j=0; j<metadata_json.ondexmetadata.concepts[i].attributes.length; j++) { if((metadata_json.ondexmetadata.concepts[i].attributes[j].attrname === "TAXID") || (metadata_json.ondexmetadata.concepts[i].attributes[j].attrnameName === "TX")) { this_taxid= metadata_json.ondexmetadata.concepts[i].attributes[j].value; } } } } // get color (which shade of blue) from map var genelabel_color= genes_taxid.get(this_taxid); // set new label blue color/shade accordingly conc.data('conceptTextBGcolor',genelabel_color); conc.css({ 'text-background-opacity': '1' }); } }); } return my; }; /** Item Info.: display information about the selected concept(s)/ relation(s) including attributes, * co-accessions and evidences. */ var KNETMAPS = KNETMAPS || {}; KNETMAPS.ItemInfo = function() { var my = function() {}; my.showItemInfo = function(selectedElement) { var itemInfo= ""; var metadataJSON= allGraphData; // using the dynamically included metadata JSON object directly. var createExpressionEntries= false; try { var cy= $('#cy').cytoscape('get'); // Display the Item Info table in its parent div. document.getElementById("itemInfo_Table").style.display= "inline"; // Display item information in the itemInfo <div> in a <table>. var table= document.getElementById("itemInfo_Table").getElementsByTagName('tbody')[0]; // get the Item Info. table. // Clear the existing table body contents. table.innerHTML= ""; if(selectedElement.isNode()) { conID= selectedElement.id(); // id conValue= selectedElement.data('value'); // value // Unselect other concepts. cy.$(':selected').nodes().unselect(); // Explicity select (highlight) the concept. cy.$('#'+conID).select(); var row= table.insertRow(0); // create a new, empty row. // Insert new cells in this row. var cell1= row.insertCell(0); var cell2= row.insertCell(1); // Store the necessary data in the cells. cell1.innerHTML= "Concept Type:"; cell2.innerHTML= selectedElement.data('conceptType'); // concept Type // Concept 'Annotation'. if(selectedElement.data('annotation') !== "") { row= table.insertRow(1/*3*/); cell1= row.insertCell(0); cell2= row.insertCell(1); cell1.innerHTML= "Annotation:"; cell2.innerHTML= selectedElement.data('annotation'); } // Get all metadata for this concept from the metadataJSON variable. for(var j=0; j < metadataJSON.ondexmetadata.concepts.length; j++) { if(selectedElement.id() === metadataJSON.ondexmetadata.concepts[j].id) { // Concept 'elementOf'. row= table.insertRow(table.rows.length/* - 1*/); // new row. cell1= row.insertCell(0); cell2= row.insertCell(1); cell1.innerHTML= "Source:"; cell2.innerHTML= metadataJSON.ondexmetadata.concepts[j].elementOf; // Get evidence information. var evidences= ""; row= table.insertRow(table.rows.length); // new row. cell1= row.insertCell(0); cell2= row.insertCell(1); cell1.innerHTML= "Evidence:"; for(var k=0; k < metadataJSON.ondexmetadata.concepts[j].evidences.length; k++) { if(metadataJSON.ondexmetadata.concepts[j].evidences[k] !== "") { evidences= evidences + metadataJSON.ondexmetadata.concepts[j].evidences[k] +", "; } } cell2.innerHTML= evidences.substring(0, evidences.length-2); // Concept 'Description'. if(metadataJSON.ondexmetadata.concepts[j].description !== "") { row= table.insertRow(table.rows.length); cell1= row.insertCell(0); cell2= row.insertCell(1); cell1.innerHTML= "Description:"; cell2.innerHTML= metadataJSON.ondexmetadata.concepts[j].description; } // Get all Synonyms (concept names). var all_concept_names= ""; row= table.insertRow(table.rows.length); // new row. cell1= row.insertCell(0); cell2= row.insertCell(1); cell1.innerHTML= "<b>Synonyms:</b>"; for(var k=0; k < metadataJSON.ondexmetadata.concepts[j].conames.length; k++) { var coname_Syn