knetmaps
Version:
Interactive network visualization tool for intuitive exploratory analysis of heterogeneous knowledge graphs
1,043 lines (943 loc) • 105 kB
JavaScript
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