forked from enviPath/enviPy
1783 lines
54 KiB
JavaScript
1783 lines
54 KiB
JavaScript
|
|
var pwURI = "";
|
|
|
|
// START PATHWAY GRAPH CONFIGURATION ----------------------------------------------
|
|
|
|
// colors
|
|
var lightGrey = "#E8E8E8";
|
|
var grey = "#B2B2B2";
|
|
|
|
var adOut = "#ff9999";
|
|
var adIn = "#007000";
|
|
|
|
// compounds
|
|
var compoundSize = 75; // = diameter of nodes of small compounds
|
|
var maxCompoundSize = function(){ return 1.5 * compoundSize }; // limit increased size of larger compounds
|
|
var maxCompoundPopupSize = function(){ return 5 * compoundSize };
|
|
var compoundImageMargin = 0.15; // compounds images are rectangles, reduce size to fit into nodes
|
|
|
|
var compoundFill = lightGrey;
|
|
var compoundFillSelected = grey;
|
|
|
|
var showCompoundOutlines = false;
|
|
var compoundStroke = "black";
|
|
var compoundStrokeRoot = "red";
|
|
var compoundStrokeOpacity = function(d){
|
|
// set opacity according to depth:
|
|
// * depth 0 -> opacity 1
|
|
// * depth == max-depth -> opacity 0
|
|
return (maxNodeDepth - d.depth) / maxNodeDepth;
|
|
};
|
|
var compoundStrokeWidth = function(){ return 0.04 * compoundSize };
|
|
|
|
var compoundTextSize = function(){ return 6 + compoundSize * 0.04 };
|
|
var compoundTextYOffset = function(){ return compoundSize * 0.5 };
|
|
|
|
// links
|
|
var linkStroke = grey;
|
|
var linkStrokeUp = "red";
|
|
var linkStrokeDown = "green";
|
|
var linkStrokeOpacity = 1.0;
|
|
var linkStrokeOpacitySelected = 1.0;
|
|
|
|
var linkStrokeWidth = function(){ return 0.02 * compoundSize };
|
|
var linkTextSize = function(){ return compoundTextSize() };
|
|
|
|
// layout settings ----------------------------
|
|
// default = pathway !
|
|
var treeLayoutEnabled = true;
|
|
var mergedView = false;
|
|
var ADView = false;
|
|
|
|
// ROOT in CENTER layout
|
|
var centerLayoutLinkDistance = function(){ return 2.5 * compoundSize };
|
|
var centerLayoutCharge = -1500; // compounds push each other apart
|
|
var centerLayoutRootCharge = centerLayoutCharge * 2;
|
|
var centerLayoutGravity = 0.05; // pulls compounds back to graph center
|
|
|
|
// TREE layout
|
|
var treeLayoutLinkDistance = function(){ return 2.5 * compoundSize };
|
|
var treeLayoutCharge = -1000; // compounds push each other apart
|
|
var treeLayoutPseudoCharge = treeLayoutCharge * 0.1; // relaxes forces on pseudo nodes
|
|
var treeLayoutLevelGravity = 0.5; // pulls compounds back to their depth level
|
|
var treeLayoutXCenterGravity = 0.03; // pulls compounds to x center
|
|
var treeLayoutLevels = function(){ return treeLayoutLinkDistance() }; // distance between depth levels
|
|
var treeLayoutXInitMargin = function(){ return treeLayoutLinkDistance() }; // initial x-distance between nodes of same depth
|
|
|
|
// popup
|
|
var popupEnabled = true;
|
|
var popupWaitBeforeShow = 1000;
|
|
var popushowAtLeast = 1000;
|
|
|
|
// other
|
|
var showCompoundNames = false;
|
|
var showReactionNames = false;
|
|
var showReactionProb = false;
|
|
var selectionEnabled = true;
|
|
|
|
// this is to not draw the arrow at the end of the line (which is a the compound center)
|
|
// but at the beginning of the circle
|
|
// MG: dont understand whats going on, depends on stroke-Width!, estimate manually for
|
|
// each setting
|
|
var arrowOffset = function(target_size){ return target_size * 0.33 };
|
|
|
|
// END PATHWAY GRAPH CONFIGURATION ----------------------------------------------
|
|
|
|
var maxNodeDepth = 0;
|
|
|
|
var educts =[];
|
|
var products =[];
|
|
|
|
//indicates click modes
|
|
var mode = 'undefined';
|
|
|
|
var width;
|
|
var height;
|
|
|
|
var border = "thin solid lightgrey";
|
|
|
|
var setFix = false;
|
|
|
|
var markedCompUri;
|
|
var markedEdgeUri;
|
|
|
|
var links;
|
|
var nodes;
|
|
|
|
//backup variable to keep original graph;
|
|
var __graph;
|
|
|
|
var nodeSelected = false;
|
|
var edgeSelected = false;
|
|
|
|
var clickedNode = 'undefined';
|
|
var clickedNode_d = 'undefined';
|
|
var clickedEdge = 'undefined';
|
|
var clickedEdge_d = 'undefined';
|
|
|
|
var compoundNodeUri = 'undefined';
|
|
//Array for links of all paths
|
|
var paths = [];
|
|
//Array for one path found by dfs
|
|
var path = [];
|
|
//debug variable
|
|
var pathNumeric = [];
|
|
//variable for dfs which indicates visited nodes
|
|
var greenLinks = [];
|
|
|
|
var visited;
|
|
|
|
var svg;
|
|
var force;
|
|
|
|
var modelUri;
|
|
var modelName;
|
|
|
|
|
|
function restartPathwayViz(onGraphLoad){
|
|
|
|
if (!width) {
|
|
width = document.getElementById("viz").offsetWidth;
|
|
height = width*0.75;
|
|
}
|
|
|
|
if (force)
|
|
force.stop();
|
|
d3.select("svg").remove();
|
|
|
|
if (treeLayoutEnabled) {
|
|
force = d3.layout.force()
|
|
.charge(function(d) {
|
|
if (d.pseudo)
|
|
return treeLayoutPseudoCharge;
|
|
else
|
|
return treeLayoutCharge;
|
|
})
|
|
.gravity(0)
|
|
.linkDistance(function(d) {
|
|
dist = Math.abs(d.target.treeLevel - d.source.treeLevel) * treeLayoutLinkDistance();
|
|
if (dist == 0) {
|
|
// nodes with depth in brackets: A (0) -> B (1) + C (2)
|
|
// then there is a pseudo node that is on the same level as B
|
|
dist = 0.5 * treeLayoutLinkDistance();
|
|
}
|
|
return dist;
|
|
})
|
|
.linkStrength(function(d) {
|
|
return strength = 1 / d.siblings;
|
|
})
|
|
.size([width, height]);
|
|
}
|
|
else {
|
|
force = d3.layout.force()
|
|
.charge(function(d) {
|
|
return d.depth==0 ? centerLayoutRootCharge : centerLayoutCharge;
|
|
})
|
|
.gravity(centerLayoutGravity)
|
|
.linkDistance(centerLayoutLinkDistance())
|
|
.size([width, height]);
|
|
}
|
|
|
|
svg = d3.select("#viz")
|
|
.append("svg")
|
|
.attr("width", "100%")
|
|
.attr("height", height)
|
|
.style("border", border)
|
|
.attr("inline", "block")
|
|
.attr("margin", "auto")
|
|
.attr("pointer-events", "all")
|
|
.append('svg:g')
|
|
.call(d3.behavior.zoom().on("zoom", redraw))
|
|
.attr("preserveAspectRatio", "xMidYMid meet")
|
|
.append('svg:g');
|
|
|
|
pwUrl = pwURI;
|
|
|
|
if(mergedView) {
|
|
pwUrl += "?merged=true";
|
|
}
|
|
|
|
d3.json(pwUrl, function(error, graph) {
|
|
if (graph) {
|
|
if (graph.pathwaySetting && graph.pathwaySetting.truncationstrategy) {
|
|
modelUri = graph.pathwaySetting.truncationstrategy.modelUri;
|
|
modelName = graph.pathwaySetting.truncationstrategy.modelName;
|
|
}
|
|
|
|
if(!graph.isPredicted) {
|
|
$("#toggleADView").parent().remove();
|
|
$("#toggleADView").remove();
|
|
}
|
|
|
|
if (onGraphLoad)
|
|
onGraphLoad(graph);
|
|
__graph = graph;
|
|
if (graph.nodes.length==0)
|
|
alert("pathway is empty");
|
|
else
|
|
start(graph);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
function goFullscreen(id) {
|
|
|
|
var element = document.getElementById(id);
|
|
|
|
if (element.mozRequestFullScreen) {
|
|
|
|
element.mozRequestFullScreen();
|
|
} else if (element.webkitRequestFullScreen) {
|
|
|
|
element.webkitRequestFullScreen();
|
|
}
|
|
}
|
|
|
|
// now pathway fun
|
|
|
|
function reformatLink(string) {
|
|
return string.replace(/\[([^|]+)\|([^\]]+)]/g, "<a target='parent' href=\"$1\">$2</a>");
|
|
}
|
|
|
|
/*function delComp() {
|
|
if (markedCompUri == 'undefined') {
|
|
// console.log("markedComp was undefined");
|
|
return;
|
|
}
|
|
var conf = confirm("Do you really want to delete this Node?");
|
|
if (conf) {
|
|
jQuery.ajax({
|
|
type : "delete",
|
|
url : markedCompUri,
|
|
success : function(response) {
|
|
hide(clickedNode_d);
|
|
click(clickedNode_d);
|
|
},
|
|
error : function(jqXHR, textStatus, errorThrown) {
|
|
alert("Error while deleting Node");
|
|
}
|
|
|
|
});
|
|
} else {
|
|
return;
|
|
}
|
|
}*/
|
|
|
|
/*function delEdge() {
|
|
// console.log(markedEdgeUri);
|
|
if (markedEdgeUri == 'undefined') {
|
|
return;
|
|
}
|
|
var conf = confirm("Do you really want to delete this Edge?");
|
|
if (conf) {
|
|
jQuery.ajax({
|
|
type : "delete",
|
|
url : markedEdgeUri,
|
|
success : function(response) {
|
|
linkArray = new Array();
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.id == clickedEdge_d.id) {
|
|
linkArray.push(links[0][i]);
|
|
}
|
|
}
|
|
hideLinks(linkArray);
|
|
edgeClick(clickedEdge_d);
|
|
},
|
|
error : function(jqXHR, textStatus, errorThrown) {
|
|
alert("Error while deleting Edge");
|
|
}
|
|
|
|
});
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
}*/
|
|
|
|
function start(graph) {
|
|
|
|
setChildrenAndParents(graph)
|
|
setPseudoLinks(graph)
|
|
|
|
maxNodeDepth = 0
|
|
graph.nodes.forEach(function(node, i) {
|
|
if (node.depth > maxNodeDepth)
|
|
maxNodeDepth = node.depth;
|
|
});
|
|
|
|
if (treeLayoutEnabled) {
|
|
setTreeLevel(graph)
|
|
var treeLayout = buildTreeLayout(graph)
|
|
}
|
|
|
|
force.nodes(graph.nodes).links(graph.links).start();
|
|
|
|
computeNodeSizes(graph.nodes);
|
|
|
|
$('#name').val(graph.pathwayName);
|
|
$('#description').val(graph.description);
|
|
|
|
// Background rectangle to enable zoom function
|
|
// even cursor is not on a node or path
|
|
svg.append('svg:rect').attr('width', 9 * width).attr('height', 9 * height)
|
|
.attr('fill', 'white').attr("x", -4 * width).attr("y", -4 * height)
|
|
.attr("opacity", 0.0);
|
|
|
|
function defineArrow(name, color, opacity, target) {
|
|
svg.append("svg:defs").selectAll("marker").data([ name ]).enter()
|
|
.append("svg:marker").attr("id", String).attr("viewBox",
|
|
"0 -5 10 10")
|
|
.attr("refX", 10 + (target ? arrowOffset(target.size) : 0 ))
|
|
.attr("refY", 0).attr(
|
|
"markerWidth", 10).attr("markerHeight", 10).attr("orient",
|
|
"auto").attr("stroke", color).attr("fill", "none").attr(
|
|
"stroke-opacity", opacity).append("svg:path").attr("d",
|
|
"M0,-5L10,0L0,5");
|
|
}
|
|
// hack: define arrow statically for each target and each color (dont know how to change dynamically)
|
|
for (var i = 0; i < graph.links.length; i++) {
|
|
if (graph.links[i].target.size) {
|
|
defineArrow("arrow_"+i,linkStroke,linkStrokeOpacity,graph.links[i].target);
|
|
defineArrow("arrow-green_"+i,linkStrokeDown,linkStrokeOpacitySelected,graph.links[i].target);
|
|
defineArrow("arrow-red_"+i,linkStrokeUp,linkStrokeOpacitySelected,graph.links[i].target);
|
|
}
|
|
}
|
|
defineArrow("arrow",linkStroke,linkStrokeOpacity);
|
|
defineArrow("arrow-green",linkStrokeDown,linkStrokeOpacitySelected);
|
|
defineArrow("arrow-red",linkStrokeUp,linkStrokeOpacitySelected);
|
|
|
|
// coordinates are set in tick
|
|
links = svg.selectAll("line").data(graph.links).enter().append("g").append("svg:line")
|
|
.style("stroke", function(d) {
|
|
if(ADView) {
|
|
if(d.ad) {
|
|
if(d.ad[0]['passesAD']) {
|
|
return adIn;
|
|
} else {
|
|
return adOut;
|
|
}
|
|
} else {
|
|
return linkStroke
|
|
}
|
|
} else {
|
|
return linkStroke
|
|
}
|
|
})
|
|
.style("stroke-opacity", function(d) {
|
|
if(ADView) {
|
|
if(d.ad) {
|
|
return 1;
|
|
} else {
|
|
return linkStrokeOpacity
|
|
}
|
|
} else {
|
|
return linkStrokeOpacity
|
|
}
|
|
})
|
|
.style("stroke-width", function() {
|
|
return linkStrokeWidth();
|
|
})
|
|
.attr("class", "link arrow")
|
|
.on("click", edgeClick)
|
|
.attr("marker-end", function(d,i) {
|
|
if (!d.target.pseudo) {
|
|
// if (d.multistep === 'true') {
|
|
// return "";
|
|
// }
|
|
return "url(#arrow_"+i+")";
|
|
} else {
|
|
if (d.multistep === 'true') {
|
|
return "url(#arrow)";
|
|
}
|
|
return "";
|
|
}
|
|
})
|
|
.attr("idx", function(d,i) {return i; })
|
|
.attr("target-pseudo", function(d) {
|
|
return d.target.pseudo;
|
|
});
|
|
|
|
// only for mouse over and clicking (other lines are hard to hit)
|
|
// coordinates are set in tick
|
|
links_invisible = svg.selectAll("g").append("svg:line")
|
|
.style("stroke", "black").style("stroke-opacity", "0").style(
|
|
"stroke-width", function() {
|
|
return linkStrokeWidth() * 10;
|
|
}).attr("class", "link arrow").on("click", edgeClick);
|
|
|
|
// coordinates are set in tick
|
|
link_text = svg.selectAll("g")
|
|
.append("text")
|
|
.attr("text-anchor", "middle")
|
|
.style("fill", "#000")
|
|
.style("font-size", function() { return linkTextSize() + "pt"; })
|
|
.style("font-family", "sans-serif");
|
|
updateReactionText();
|
|
|
|
// the popup for lines is oriented on the max-right position of the line
|
|
// adjust to center of the line / to the pseudo node
|
|
function popup_offset(d) {
|
|
x = 15;
|
|
y = 0;
|
|
if (d.target.pseudo) {
|
|
y += Math.abs(d.source.y - d.target.y) * 0.5;
|
|
if (d.target.x < d.source.x)
|
|
x -= Math.abs(d.source.x - d.target.x);
|
|
}
|
|
else if (d.source.pseudo) {
|
|
y -= Math.abs(d.source.y - d.target.y) * 0.5;
|
|
if (d.target.x > d.source.x)
|
|
x -= Math.abs(d.source.x - d.target.x);
|
|
}
|
|
else {
|
|
x -= Math.abs(d.source.x - d.target.x) * 0.5;
|
|
}
|
|
return {x: x, y: y};
|
|
}
|
|
if (popupEnabled) {
|
|
function reaction_content(d) {
|
|
content = "<p>Reaction: <a href='"+d.idreaction+"'>"+d.name+"</a></p>";
|
|
var adcontent = "";
|
|
if(d.ad){
|
|
for(i in d.ad) {
|
|
data = d.ad[i];
|
|
adcontent = "<p>";
|
|
if(data["timesTriggered"]) {
|
|
adcontent += "This rule triggered " + data["timesTriggered"] + " in the training set<br>";
|
|
}
|
|
adcontent += "Reliability " + round(data["reliability"]) + " (" + (data["reliability"] > data["reliabilityThreshold"] ? ">" : "<") + " Reliability Threshold of " + data["reliabilityThreshold"] + ")<br>";
|
|
adcontent += "Local Compatibility " + round(data["localCompatibility"]) + " (" + (data["localCompatibility"] > data["localCompatibilityThreshold"] ? ">" : "<") + " Reliability Threshold of " + data["localCompatibilityThreshold"] + ")<br>";
|
|
adcontent += "</p>";
|
|
}
|
|
}
|
|
content += adcontent;
|
|
|
|
if (d.scenarios && d.scenarios.length>0) {
|
|
|
|
content += "<p>Scenario/s:"
|
|
for (var sc in d.scenarios) {
|
|
scen = d.scenarios[sc]
|
|
content += "<br><a href='"+scen.id+"'>"+scen.name+"</a>";
|
|
if (scen.modelpredictionprob) {
|
|
content += "<p>Relative Reasoning: <a href='"+modelUri+"'>"+modelName+"</a>";
|
|
content += "<br>Probability: "+scen.modelpredictionprob+"</p>";
|
|
}
|
|
if (scen.modelbayespredictionprob) {
|
|
content += "<p>Relative Reasoning: <a href='"+modelUri+"'>"+modelName+"</a>";
|
|
content += "<br>Bayes Probability: "+scen.modelbayespredictionprob+"</p>";
|
|
}
|
|
}
|
|
content += "</p>";
|
|
}
|
|
if (d.ecNumbers && d.ecNumbers.length>0) {
|
|
content += "<p>Enzyme/s:";
|
|
for (var ec in d.ecNumbers) {
|
|
content += "<br><a href=\"http://www.brenda-enzymes.org/enzyme.php?ecno="
|
|
+ d.ecNumbers[ec].ecNumber + "\" target=\"_blank\">"
|
|
+ d.ecNumbers[ec].ecName + " (" + d.ecNumbers[ec].ecNumber + ")</a>";
|
|
}
|
|
content += "</p>";
|
|
}
|
|
return content;
|
|
}
|
|
pop_add(links_invisible,"Reaction",function(d){ return reaction_content(d);},popup_offset);
|
|
pop_add(link_text,"Reaction",function(d){ return reaction_content(d);});
|
|
}
|
|
|
|
var node_drag = d3.behavior.drag().on("dragstart", dragstart).on("drag",
|
|
dragmove).on("dragend", dragend);
|
|
|
|
function dragstart(d, i) {
|
|
pop_hideall();
|
|
d3.event.sourceEvent.stopPropagation();
|
|
force.stop();
|
|
}
|
|
|
|
function dragmove(d, i) {
|
|
d3.event.sourceEvent.stopPropagation();
|
|
d.px += d3.event.dx;
|
|
d.py += d3.event.dy;
|
|
d.x += d3.event.dx;
|
|
d.y += d3.event.dy;
|
|
tick();
|
|
}
|
|
|
|
function dragend(d, i) {
|
|
pop_show();
|
|
d3.event.sourceEvent.stopPropagation();
|
|
d.fixed = true;
|
|
tick();
|
|
force.resume();
|
|
}
|
|
|
|
nodes = svg.selectAll(".node").data(graph.nodes).enter().append("g").attr(
|
|
"class", "node").call(node_drag);
|
|
|
|
rootIndices = []
|
|
graph.nodes.forEach(function(node,i) {
|
|
if (node.depth==0) {
|
|
rootIndices.push(i);
|
|
}
|
|
});
|
|
|
|
// init node positions
|
|
if (!treeLayoutEnabled) {
|
|
maxTreeSize = -1
|
|
node = null
|
|
graph.nodes.forEach(function(d,i) {
|
|
if (d.depth == 0 && d.treeSize>maxTreeSize) {
|
|
maxTreeSize = d.treeSize;
|
|
node = d;
|
|
}
|
|
});
|
|
x = width/2;
|
|
y = height/2;
|
|
node.x = x;
|
|
node.y = y;
|
|
node.px = x;
|
|
node.py = y;
|
|
node.fixed = true;
|
|
}
|
|
else {
|
|
// positions for tree layout
|
|
// y: set node at treeLevel
|
|
// x: equally distriute according to number of nodes per level (for non-pseudo nodes)
|
|
for (var level in treeLayout) {
|
|
xStart = (width * 0.5) - (treeLayoutXInitMargin() * (treeLayout[level].length-1) * 0.5)
|
|
treeLayout[level].forEach(function (node,j) {
|
|
if (!node.pseudo) {
|
|
x = xStart + j * treeLayoutXInitMargin();
|
|
node.x = x;
|
|
node.px = x;
|
|
}
|
|
y = treeLayoutLevels()*node.treeLevel + compoundSize;
|
|
node.y = y;
|
|
node.py = y;
|
|
|
|
});
|
|
}
|
|
// update x for pseudo nodes: set in between parents and children
|
|
for (var level in treeLayout) {
|
|
treeLayout[level].forEach(function (node,j) {
|
|
if (node.pseudo) {
|
|
sumX = 0;
|
|
countX = 0;
|
|
(node.parents.concat(node.children)).forEach(function (n,j) {
|
|
if (!n.pseudo) {
|
|
sumX += n.x;
|
|
countX += 1;
|
|
}
|
|
});
|
|
x = sumX/countX;
|
|
node.x = x;
|
|
node.px = x;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
circles = nodes.append("circle").attr("class", "circle").attr("cx", 0).attr(
|
|
"cy", 0)
|
|
.attr("depth", function(d) {
|
|
return d.depth;
|
|
}).attr("r", function(d) {
|
|
if (!d.pseudo) {
|
|
return d.size * 0.5;
|
|
} else {
|
|
return 0;
|
|
}
|
|
})
|
|
.style("stroke", function(d) {
|
|
if(ADView) {
|
|
if(d.ad){
|
|
if(d.ad['inAD']) {
|
|
return adIn;
|
|
} else {
|
|
return adOut;
|
|
}
|
|
}
|
|
return "black";
|
|
} else if (d.depth == 0) {
|
|
return compoundStrokeRoot;
|
|
} else {
|
|
return compoundStroke;
|
|
}
|
|
}).style("stroke-opacity",function(d) {
|
|
if(ADView) {
|
|
return 1;
|
|
} else if (showCompoundOutlines) {
|
|
return compoundStrokeOpacity(d);
|
|
} else {
|
|
return 0.0;
|
|
}
|
|
})
|
|
.style("stroke-width", function(d) {
|
|
if (!d.pseudo) {
|
|
return compoundStrokeWidth() + "px";
|
|
} else {
|
|
return 0;
|
|
}
|
|
})
|
|
.style("fill", function(d) {
|
|
if(d.engineeredIntermediate) {
|
|
return "#dcfcf9"
|
|
}else {
|
|
return compoundFill
|
|
}
|
|
|
|
})
|
|
.style("fill-opacity", function(d) {
|
|
if (!d.pseudo) {
|
|
return "1";
|
|
} else {
|
|
return "0.0";
|
|
}
|
|
});
|
|
|
|
nodes.append("inAD").attr("inAD", function(d){
|
|
if (!d.pseudo) {
|
|
if(d.ad) {
|
|
return d.ad['inAD'];
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
});
|
|
|
|
nodes.append("image").attr("xlink:href", function(d) {
|
|
if (!d.pseudo) {
|
|
if(ADView) {
|
|
return d.image += "&highlight=true";
|
|
}
|
|
return d.image;
|
|
} else {
|
|
return;
|
|
}
|
|
}).attr("x", function(d) {
|
|
if (!d.pseudo) {
|
|
return -0.5*(1.0-compoundImageMargin)*d.size
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}).attr("y", function(d) {
|
|
if (!d.pseudo) {
|
|
return -0.5*(1.0-compoundImageMargin)*d.size
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}).on("click",
|
|
function(d) {
|
|
if (!d.pseudo) {
|
|
return click(d);
|
|
} else {
|
|
return;
|
|
}
|
|
}).attr("width", function(d) {
|
|
if (!d.pseudo) {
|
|
return d.size * (1.0-compoundImageMargin);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}).attr("height", function(d) {
|
|
if (!d.pseudo) {
|
|
return d.size * (1.0-compoundImageMargin);
|
|
} else {
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
function removeObjDel(id) {
|
|
var button = document.getElementById(id);
|
|
button.parentNode.removeChild(button);
|
|
}
|
|
|
|
node_text = nodes.append("text").attr("text-anchor", "middle").style("fill", "#000")
|
|
.style("font-size", function() {
|
|
return compoundTextSize() + "pt";
|
|
}).style("font-family", "sans-serif").style("stroke-width", "0")
|
|
.attr("transform", function() {
|
|
return "translate(0, -" + compoundTextYOffset() + ")";
|
|
}).text(function(d) {
|
|
if (!d.pseudo && showCompoundNames) {
|
|
return d.name;
|
|
} else {
|
|
return "";
|
|
}
|
|
});
|
|
|
|
if (popupEnabled)
|
|
pop_add(nodes, "Compound", function(d) {
|
|
|
|
var prop = d.proposed;
|
|
var proposedInfo = '';
|
|
|
|
if(d.ad) {
|
|
|
|
proposedInfo += "This compound is " + (d.ad['inAD'] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "<br>";
|
|
if(d.ad['uncoveredFG']) {
|
|
proposedInfo += "Compound contains functional groups not covered by the training set <br>";
|
|
}
|
|
|
|
}
|
|
|
|
if(graph.isIncremental && isLeaf(d)) {
|
|
console.log(d);
|
|
proposedInfo += '<a id="aNodeProp'+ d.index + '" class="btn btn-primary" onclick="predictFromNode(\'' + d.index + '\')" href="#">Predict from here</a><br>'
|
|
}
|
|
|
|
if(d.properties.length > 0) {
|
|
proposedInfo += "<br>";
|
|
for(x in d.properties) {
|
|
property = d.properties[x];
|
|
proposedInfo += property["name"] + ": " + property["value"] + '<br>';
|
|
}
|
|
proposedInfo += "<br>";
|
|
}
|
|
|
|
sortByKey(prop, 'scenarioName');
|
|
proposedInfo += "<a href='"+d.id+"'>"+d.name+"</a><br>"
|
|
+"Depth: "+d.depth+"<br><br>"
|
|
+"<img src="+d.image+" width='"+d.popupSize+"'>" + "<br>"
|
|
+"<br><br><p><b>Half-lives and related scenarios:</b></p>";
|
|
for(var i in prop){
|
|
// Get scenario name and uri
|
|
var scenName = prop[i].scenarioName;
|
|
var scenUri = prop[i].scenarioId;
|
|
proposedInfo += "<a href='" + scenUri + "'>" + scenName+"</a><br>";
|
|
|
|
// Iterate over all other properties
|
|
for(var key in prop[i]){
|
|
if (key === 'scenarioName' || key === 'scenarioId') {
|
|
continue;
|
|
}
|
|
proposedInfo += key+": "+prop[i][key]+"<br>";
|
|
}
|
|
proposedInfo += '<br>';
|
|
}
|
|
return proposedInfo;
|
|
});
|
|
|
|
// on each iteration: update links and translate compounds
|
|
function tick() {
|
|
|
|
links_invisible.attr("x1", function(d) {
|
|
return d.source.x;
|
|
}).attr("y1", function(d) {
|
|
return d.source.y;
|
|
}).attr("x2", function(d) {
|
|
return d.target.x;
|
|
}).attr("y2", function(d) {
|
|
return d.target.y;
|
|
});
|
|
|
|
links.attr("x1", function(d) {
|
|
return d.source.x;
|
|
}).attr("y1", function(d) {
|
|
return d.source.y;
|
|
}).attr("x2", function(d) {
|
|
return d.target.x;
|
|
}).attr("y2", function(d) {
|
|
return d.target.y;
|
|
});
|
|
|
|
link_text.attr("dx", function(d) {
|
|
if (d.target.pseudo) {
|
|
return d.target.x;
|
|
}
|
|
else if (d.source.pseudo) {
|
|
return d.source.x;
|
|
}
|
|
else {
|
|
return (d.source.x + d.target.x)/2;
|
|
}
|
|
}).attr("dy", function(d) {
|
|
if (d.target.pseudo) {
|
|
return d.target.y;
|
|
}
|
|
else if (d.source.pseudo) {
|
|
return d.source.y;
|
|
}
|
|
else {
|
|
return (d.source.y + d.target.y)/2;
|
|
}
|
|
});
|
|
|
|
nodes.attr("transform", function(d) {
|
|
return "translate(" + d.x + "," + d.y + ")";
|
|
});
|
|
}
|
|
|
|
// on each iteration: add forces and manual stopping of forces
|
|
force.on("tick", function(e) {
|
|
|
|
if (treeLayoutEnabled) {
|
|
// implement treeLayoutGravity
|
|
var ky = treeLayoutLevelGravity * e.alpha;
|
|
var kx = treeLayoutXCenterGravity * e.alpha;
|
|
graph.nodes.forEach(function(d) {
|
|
// pull back to y-level
|
|
yLevel = treeLayoutLevels()*d.treeLevel + compoundSize;
|
|
d.y -= (d.y - yLevel) * ky;
|
|
|
|
// pull back to center
|
|
xCenter = width/2;
|
|
d.x -= (d.x - xCenter) * kx;
|
|
});
|
|
}
|
|
|
|
// TODO find a better value =0.03...
|
|
if (force.alpha() <= 0.005004) {
|
|
for (var i = 0; i < nodes[0].length; i++) {
|
|
if (!nodes[0][i].__data__.pseudo) {
|
|
nodes[0][i].__data__.fixed = true;
|
|
}
|
|
}
|
|
setFix = true;
|
|
force.stop();
|
|
|
|
} else {
|
|
tick();
|
|
}
|
|
});
|
|
}
|
|
|
|
function isLeaf(node) {
|
|
return node.children.length == 0;
|
|
}
|
|
|
|
// adds a parents[] and a children[] array for each node
|
|
// set treeSize
|
|
function setChildrenAndParents(graph)
|
|
{
|
|
graph.nodes.forEach(function(node, i) {
|
|
node.parents = []
|
|
node.children = []
|
|
});
|
|
graph.links.forEach(function(link,j) {
|
|
source = graph.nodes[link.source];
|
|
target = graph.nodes[link.target];
|
|
source.children.push(target);
|
|
target.parents.push(source);
|
|
});
|
|
|
|
function count(node) {
|
|
node.touch = true;
|
|
c = 1;
|
|
node.children.forEach(function(child, i) {
|
|
if (!child.touch) {
|
|
c += count(child);
|
|
}
|
|
});
|
|
node.count = false;
|
|
return c;
|
|
}
|
|
graph.nodes.forEach(function(node, i) {
|
|
graph.nodes.forEach(function(n, i) {
|
|
n.touch = false;
|
|
});
|
|
node.treeSize = count(node);
|
|
});
|
|
|
|
graph.links.forEach(function(link,j) {
|
|
p = graph.nodes[link.source].children.length;
|
|
c = graph.nodes[link.target].parents.length;
|
|
link.siblings = Math.max(p, c);
|
|
});
|
|
}
|
|
|
|
|
|
// computes tree level for each node
|
|
// similar to depth, but not the shortest path to root
|
|
// i.e. if a node has parents of depth 1 and 2, its level is set to 3
|
|
// sets level of root compounds to level -1 of their children
|
|
// pseudo nodes will have intermedate level of .33 and .66
|
|
function setTreeLevel(graph) {
|
|
|
|
// init levels
|
|
graph.nodes.forEach(function(node, i) {
|
|
node.treeLevel = 0;
|
|
});
|
|
|
|
// recursively set levels
|
|
graph.nodes.forEach(function(node, i) {
|
|
graph.nodes.forEach(function(n, i) {
|
|
n.touch = false;
|
|
});
|
|
if (node.depth==0)
|
|
recursion(node);
|
|
});
|
|
function recursion(node) {
|
|
node.touch = true;
|
|
node.children.forEach(function(child, i) {
|
|
if (!child.touch) {
|
|
d = node.treeLevel;
|
|
if (node.pseudo || child.pseudo)
|
|
d += 0.33;
|
|
else
|
|
d += 1;
|
|
if (!child.pseudo)
|
|
d = Math.round(d);
|
|
if (d > child.treeLevel) {
|
|
child.treeLevel = d;
|
|
recursion(child);
|
|
}
|
|
}
|
|
});
|
|
node.touch = false;
|
|
}
|
|
|
|
// increase levels of root compounds that have no level=1 child, but children with higher levels
|
|
graph.nodes.forEach(function(node, i) {
|
|
if (node.treeLevel==0 && node.children.length>0) {
|
|
minLevel = 10000;
|
|
node.children.forEach(function(child,j) {
|
|
d = child.treeLevel;
|
|
if (node.pseudo || child.pseudo)
|
|
d -= 0.33;
|
|
else
|
|
d -= 1;
|
|
if (!node.pseudo)
|
|
d = Math.round(d);
|
|
if (d < minLevel)
|
|
minLevel = d;
|
|
});
|
|
if (minLevel > 0) {
|
|
node.treeLevel = minLevel;
|
|
}
|
|
}
|
|
});
|
|
|
|
// pseudo nodes with non-pseudo parent always have 0.33
|
|
// set to 0.5 if they do not have pseudo children
|
|
graph.nodes.forEach(function(node, i) {
|
|
if (node.pseudo && (node.parents.size>1 || !node.parents[0].pseudo)) {
|
|
if (node.children.size>1 || !node.children[0].pseudo) {
|
|
node.treeLevel = (node.children[0].treeLevel + node.parents[0].treeLevel) * 0.5;
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// builds a tree layout by iterating through the tree
|
|
// * the treeLevel is determined beforehand and remains unchanged
|
|
// * puts all nodes into a map with key treeLevel
|
|
// * tree layout is the order of the node arrays for each level
|
|
// * dfs insertion
|
|
// * idea is to insert children first which have children that are already present
|
|
function buildTreeLayout(graph) {
|
|
|
|
treeLayout = {}
|
|
|
|
// start off with the root nodes
|
|
roots = []
|
|
graph.nodes.forEach(function(node, i) {
|
|
if (node.depth==0)
|
|
roots.push(node);
|
|
});
|
|
insertNodes(roots);
|
|
|
|
// picks the node with the max-ratio of already inserted children
|
|
function insertNodes(nodes) {
|
|
if (nodes.length==1) {
|
|
insertNode(nodes[0]);
|
|
}
|
|
else if (nodes.length>1) {
|
|
maxIdx = -1;
|
|
nodes.forEach(function(node,j) {
|
|
graph.nodes.forEach(function(n, i) {
|
|
n.touch = false;
|
|
});
|
|
insertedChildren = findInserted(node, true);
|
|
insertedParents = findInserted(node, false);
|
|
total = insertedChildren.total + insertedParents.total;
|
|
found = insertedChildren.found + insertedParents.found;
|
|
if (total==0)
|
|
node.foundRatio = 0;
|
|
else
|
|
node.foundRatio = found/total;
|
|
if (maxIdx==-1 || node.foundRatio>nodes[maxIdx].foundRatio)
|
|
maxIdx = j;
|
|
});
|
|
// insert max-ratio-node, and remove it from node array
|
|
maxNode = nodes[maxIdx]
|
|
nodes = nodes.slice(); // copy array
|
|
nodes.splice(maxIdx, 1); // remove node
|
|
insertNode(maxNode);
|
|
// recalculate the found-ratio scores for the remaining nodes
|
|
insertNodes(nodes);
|
|
}
|
|
}
|
|
// inserts a single node (if not already included) and then inserts its children
|
|
function insertNode(node) {
|
|
if (!treeLayout[node.treeLevel]) {
|
|
treeLayout[node.treeLevel] = [];
|
|
}
|
|
if ($.inArray(node, treeLayout[node.treeLevel]) == -1) {
|
|
treeLayout[node.treeLevel].push(node);
|
|
insertNodes(node.children);
|
|
}
|
|
}
|
|
// to determine the ratio of already inserted children/parents
|
|
function findInserted(node, downwards) {
|
|
var found = 0;
|
|
var total = 0;
|
|
nodes = downwards ? node.children : node.parents;
|
|
nodes.forEach(function(n,j) {
|
|
if (!n.touch) {
|
|
n.touch = true;
|
|
if (treeLayout[n.treeLevel] && $.inArray(n, treeLayout[n.treeLevel])!=-1)
|
|
found += 1;
|
|
total += 1;
|
|
newCC = findInserted(n, downwards);
|
|
found += newCC.found;
|
|
total += newCC.total;
|
|
}
|
|
});
|
|
return {found: found, total: total};
|
|
}
|
|
return treeLayout;
|
|
}
|
|
|
|
|
|
// sets all links to pseudo that are connected to a pseudo node
|
|
function setPseudoLinks(graph) {
|
|
graph.links.forEach(function(link,j) {
|
|
if (graph.nodes[link.source].pseudo || graph.nodes[link.target].pseudo) {
|
|
link.pseudo = true;
|
|
}
|
|
else {
|
|
link.pseudo = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// compute size of compound-node and store it in <node>.size value
|
|
// size of smallest compound will be assigned to 'compoundSize'
|
|
// size of larger compounds is limited by 'maxCompoundSize'
|
|
function computeNodeSizes(nodes) {
|
|
|
|
// step1: determine size of smallest compound
|
|
min_size = 1000000;
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (!nodes[i].pseudo) {
|
|
s = nodes[i].imageSize;
|
|
if (s < min_size) {
|
|
min_size = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
// step2: compute scaled size and store as .size
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
if (!nodes[i].pseudo) {
|
|
size = nodes[i].imageSize;
|
|
r = size/min_size;
|
|
size = r * compoundSize;
|
|
nodes[i].size = Math.min(size,maxCompoundSize());
|
|
nodes[i].popupSize = Math.min(size,maxCompoundPopupSize());
|
|
}
|
|
}
|
|
}
|
|
|
|
function toggleCompoundOutlines() {
|
|
showCompoundOutlines = !showCompoundOutlines;
|
|
circles.style("stroke-opacity",function(d) {
|
|
if (showCompoundOutlines) {
|
|
return compoundStrokeOpacity(d);
|
|
} else {
|
|
return 0.0;
|
|
}
|
|
});
|
|
}
|
|
|
|
function toggleCompoundNames() {
|
|
showCompoundNames = !showCompoundNames;
|
|
node_text.text(function(d) {
|
|
if (!d.pseudo && showCompoundNames) {
|
|
return d.name;
|
|
} else {
|
|
return "";
|
|
}
|
|
});
|
|
}
|
|
|
|
function toggleReactionNames() {
|
|
showReactionNames = !showReactionNames;
|
|
updateReactionText();
|
|
}
|
|
|
|
function toggleReactionProb() {
|
|
showReactionProb = !showReactionProb;
|
|
updateReactionText();
|
|
}
|
|
|
|
function updateReactionText() {
|
|
link_text.text(function(d){
|
|
|
|
s = "";
|
|
if(showReactionNames){
|
|
s = d.name;
|
|
}
|
|
if(d.rule && showReactionProb){
|
|
s += " Probability: "+d.modelpredictionprob;
|
|
}
|
|
return s.trim();
|
|
});
|
|
}
|
|
|
|
function pop_add(objects, title, contentFunction, offsetFunction) {
|
|
objects.attr("id","pop")
|
|
.attr("data-container","body")
|
|
.attr("data-toggle","popover")
|
|
.attr("data-placement","right")
|
|
.attr("title",title);
|
|
|
|
objects.each(function(d,i) {
|
|
options = { trigger: "manual", html: true, animation:false };
|
|
this_ = this;
|
|
var p = $(this).popover(options).on("mouseenter", function () {
|
|
pop_show_e(this);
|
|
});
|
|
p.on("show.bs.popover", function(e){
|
|
// this is to dynamically ajdust the content and bounds of the popup
|
|
p.attr('data-content',contentFunction(d));
|
|
p.data("bs.popover").setContent();
|
|
if (offsetFunction) {
|
|
offset = offsetFunction(d);
|
|
p.data("bs.popover").tip().css({"margin-left": offset["x"]+"px", "margin-top": offset["y"]+"px"});
|
|
}
|
|
p.data("bs.popover").tip().css({"max-width": "1000px"});
|
|
});
|
|
});
|
|
}
|
|
|
|
function pop_hideall() {
|
|
svg.selectAll("#pop").each(function(d,i){$(this).popover("hide")});
|
|
}
|
|
|
|
function pop_show_e(element) {
|
|
|
|
var e = element;
|
|
setTimeout(function () {
|
|
if ($(e).is(':hover')) { // if element is still hovered
|
|
$(e).popover("show");
|
|
|
|
setTimeout(function () {
|
|
var close = setInterval(function(){
|
|
if (!$(".popover:hover").length // mouse outside popover
|
|
&& !$(e).is(':hover')) { // mouse outside element
|
|
$(e).popover('hide');
|
|
clearInterval(close);
|
|
}
|
|
}, 100);
|
|
},popushowAtLeast);
|
|
}
|
|
},popupWaitBeforeShow);
|
|
}
|
|
|
|
function pop_show() {
|
|
svg.selectAll("#pop").each(function(d,i){
|
|
pop_show_e(this);
|
|
});
|
|
}
|
|
|
|
|
|
function redraw() {
|
|
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale("
|
|
+ d3.event.scale + ")");
|
|
}
|
|
|
|
function hide(node) {
|
|
var nodeArr = new Array();
|
|
var linkArr = new Array();
|
|
var nodeIndices = new Array();
|
|
|
|
// The if case handles the if the user wants to hide nodes with
|
|
// a specific atomcount
|
|
if (!node) {
|
|
var limit = document.getElementById("counter").value;
|
|
|
|
if (limit.length == 0 || limit == 0) {
|
|
// nothing to hide...
|
|
return;
|
|
}
|
|
|
|
// Searching all relevant nodes...
|
|
for (var i = 0; i < nodes[0].length; i++) {
|
|
if (nodes[0][i].__data__.atomCount <= limit) {
|
|
nodeArr.push(nodes[0][i]);
|
|
nodeIndices.push(nodes[0][i].__data__.index);
|
|
}
|
|
}
|
|
}
|
|
// This case handles the deletion of one single node
|
|
else {
|
|
for (var i = 0; i < nodes[0].length; i++) {
|
|
if (nodes[0][i].__data__.id == node.id) {
|
|
nodeArr.push(nodes[0][i]);
|
|
nodeIndices.push(nodes[0][i].__data__.index);
|
|
}
|
|
}
|
|
}
|
|
|
|
var tempLinks = new Array();
|
|
// searching relevant links
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
var target = links[0][i].__data__.target.index;
|
|
var source = links[0][i].__data__.source.index;
|
|
|
|
if (nodeIndices.indexOf(source) != -1
|
|
|| nodeIndices.indexOf(target) != -1) {
|
|
// linkArr.push(links[0][i]);
|
|
tempLinks.push(links[0][i]);
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
for (var j = 0; j < tempLinks.length; j++) {
|
|
if (tempLinks[j].__data__.id == links[0][i].__data__.id) {
|
|
linkArr.push(links[0][i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
hideNodes(nodeArr);
|
|
hideLinks(linkArr);
|
|
|
|
force.resume();
|
|
|
|
}
|
|
|
|
// TODO method hideNodes and hideLinks are equal merge it
|
|
function hideNodes(nodeArray) {
|
|
if (detectedBrowser == "Chromium" || detectedBrowser == "Chrome") {
|
|
for (var i = 0; i < nodeArray.length; i++) {
|
|
nodeArray[i].remove();
|
|
}
|
|
}
|
|
|
|
else {
|
|
for (var k = 0; k < nodeArray.length; k++) {
|
|
var parentNode = nodeArray[k].parentNode;
|
|
if (parentNode == null) {
|
|
continue;
|
|
}
|
|
var childrens = parentNode.childNodes;
|
|
for (var j = 0; j < childrens.length; j++) {
|
|
if (childrens[j] == nodeArray[k]) {
|
|
parentNode.removeChild(childrens[j]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function hideLinks(linkArray) {
|
|
|
|
for (var k = 0; k < linkArray.length; k++) {
|
|
var parentNode = linkArray[k].parentNode;
|
|
if (parentNode == null) {
|
|
continue;
|
|
}
|
|
var childrens = parentNode.childNodes;
|
|
for (var j = 0; j < childrens.length; j++) {
|
|
if (childrens[j] == linkArray[k]) {
|
|
parentNode.removeChild(childrens[j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function reset() {
|
|
// remove all;
|
|
svg.selectAll("g.node").remove();
|
|
svg.selectAll("rect").remove();
|
|
svg.selectAll("image").remove();
|
|
svg.selectAll("line").remove();
|
|
svg.selectAll("marker").remove();
|
|
svg.selectAll("text").remove();
|
|
svg.selectAll("g").remove();
|
|
svg.selectAll("defs").remove();
|
|
start(__graph);
|
|
}
|
|
|
|
function click(d) {
|
|
|
|
if (!selectionEnabled)
|
|
return;
|
|
|
|
try {
|
|
if (d3.event.defaultPrevented) {
|
|
return;
|
|
}
|
|
} catch (Exception) {
|
|
console.log("mhm");
|
|
}
|
|
|
|
for (var i = 0; i < nodes[0].length; i++) {
|
|
if (nodes[0][i].__data__.id == d.id) {
|
|
tempNode = nodes[0][i].childNodes[0];
|
|
}
|
|
}
|
|
|
|
if (mode === "educt" || mode === "product") {
|
|
var comp = new Array();
|
|
comp.push(tempNode);
|
|
if (mode === "educt") {
|
|
var list = document.getElementById("eductList");
|
|
appendCompounds(educts, list, comp);
|
|
} else {
|
|
var list = document.getElementById("productList");
|
|
appendCompounds(products, list, comp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (edgeSelected) {
|
|
edgeClick(clickedEdge_d);
|
|
}
|
|
|
|
// variable of child from where dfs should start
|
|
var from = tempNode.parentNode.__data__.index;
|
|
|
|
if (clickedNode == 'undefined') {
|
|
setSelectedDropdownValue('#nodeDropdown', d.id);
|
|
setSelectedDropdownValue('#nodeToHl', d.id);
|
|
nodeSelected = true;
|
|
compoundNodeUri = d.idcomp;
|
|
markedCompUri = tempNode.parentNode.__data__.id;
|
|
clickedNode = tempNode;
|
|
clickedNode_d = d;
|
|
mark(clickedNode, nodeSelected);
|
|
|
|
resetArrays();
|
|
rootIndices.forEach(function(rootIndex) {
|
|
dfs(from, rootIndex, from, path, pathNumeric);
|
|
});
|
|
for (var i = 0; i < paths.length; i++) {
|
|
paintPath(paths[i],nodeSelected, clickedNode_d);
|
|
}
|
|
findOutgoing(from);
|
|
markOutgoingLinks(greenLinks, nodeSelected);
|
|
|
|
} else if (clickedNode == tempNode) {
|
|
setSelectedDropdownValue('#nodeDropdown', "undefined");
|
|
setSelectedDropdownValue('#nodeToHl', "undefined");
|
|
nodeSelected = false;
|
|
old_d = clickedNode_d;
|
|
markedCompUri = "undefined";
|
|
mark(clickedNode, nodeSelected);
|
|
// update sidebar with empty string
|
|
clickedNode = 'undefined';
|
|
clickedNode_d = 'undefined';
|
|
compoundNodeUri = 'undefined';
|
|
// in this case the path is already painted and
|
|
// the node is clicked again -> unpaint
|
|
for (var i = 0; i < paths.length; i++) {
|
|
paintPath(paths[i],nodeSelected, old_d);
|
|
}
|
|
|
|
markOutgoingLinks(greenLinks,nodeSelected);
|
|
// initial state
|
|
resetArrays();
|
|
|
|
} else {
|
|
setSelectedDropdownValue('#nodeDropdown', d.id);
|
|
setSelectedDropdownValue('#nodeToHl', d.id);
|
|
nodeSelected = true;
|
|
markedCompUri = tempNode.parentNode.__data__.id;
|
|
|
|
mark(clickedNode, false);
|
|
mark(tempNode, true);
|
|
clickedNode = tempNode;
|
|
clickedNode_d = d;
|
|
compoundNodeUri = d.idcomp;
|
|
for (var i = 0; i < paths.length; i++) {
|
|
paintPath(paths[i],false, clickedNode_d);
|
|
}
|
|
markOutgoingLinks(greenLinks,false);
|
|
|
|
resetArrays();
|
|
rootIndices.forEach(function(rootIndex) {
|
|
dfs(from, rootIndex, from, path, pathNumeric);
|
|
});
|
|
for (var i = 0; i < paths.length; i++) {
|
|
paintPath(paths[i],nodeSelected, clickedNode_d);
|
|
}
|
|
findOutgoing(from);
|
|
markOutgoingLinks(greenLinks,nodeSelected);
|
|
|
|
}
|
|
}
|
|
|
|
function appendCompounds(datastore, list, comp) {
|
|
for (var i = 0; i < comp.length; i++) {
|
|
var li = document.createElement("li");
|
|
li.innerHTML = comp[i].__data__.name;
|
|
list.appendChild(li);
|
|
datastore.push([ comp[i].__data__.name, comp[i].__data__.id ]);
|
|
}
|
|
}
|
|
|
|
function clearList(list) {
|
|
list.innerHTML = "";
|
|
educts = [];
|
|
products = [];
|
|
}
|
|
|
|
function edgeClick(d) {
|
|
|
|
if (!selectionEnabled)
|
|
return;
|
|
|
|
var connectors = new Array();
|
|
var old_connectors = new Array();
|
|
if (nodeSelected) {
|
|
// objectSelected = false;
|
|
click(clickedNode_d);
|
|
}
|
|
|
|
var tempEdge = 'undefined';
|
|
|
|
// Determine the selected Edge
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.id == d.id) {
|
|
tempEdge = links[0][i];
|
|
connectors.push(tempEdge);
|
|
old_connectors.push(tempEdge);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tempEdge == 'undefined') {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.id == tempEdge.__data__.id) {
|
|
connectors.push(links[0][i]);
|
|
}
|
|
if (clickedEdge != 'undefined') {
|
|
if (links[0][i].__data__.id == clickedEdge.__data__.id) {
|
|
old_connectors.push(links[0][i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
var eductsTemp = new Array();
|
|
var productsTemp = new Array();
|
|
for (var i = 0; i < connectors.length; i++) {
|
|
for (var j = 0; j < nodes[0].length; j++) {
|
|
if (connectors[i].__data__.source_byname == nodes[0][j].__data__.name) {
|
|
eductsTemp.push(nodes[0][j]);
|
|
} else if (connectors[i].__data__.target_byname == nodes[0][j].__data__.name) {
|
|
productsTemp.push(nodes[0][j]);
|
|
}
|
|
}
|
|
}
|
|
// console.log(educts);
|
|
// console.log(products);
|
|
|
|
if (clickedEdge == 'undefined') {
|
|
setSelectedDropdownValue('#edgeDropdown', d.id);
|
|
edgeSelected = true;
|
|
clickedEdge = tempEdge;
|
|
clickedEdge_d = d;
|
|
markedEdgeUri = clickedEdge.__data__.id;
|
|
paintPath(clickedEdge,edgeSelected, clickedEdge_d);
|
|
for (var i = 0; i < connectors.length; i++) {
|
|
paintPath(connectors[i],edgeSelected, clickedEdge_d);
|
|
}
|
|
|
|
// var list = document.getElementById("eductList");
|
|
// appendCompounds(educts,list,eductsTemp);
|
|
// var list = document.getElementById("productList");
|
|
// appendCompounds(products,list,productsTemp);
|
|
|
|
} else if (clickedEdge == tempEdge) {
|
|
setSelectedDropdownValue('#edgeDropdown', "undefined");
|
|
old_d = clickedEdge_d;
|
|
edgeSelected = false;
|
|
clickedEdge = 'undefined';
|
|
markedEdgeUri = 'undefined';
|
|
clickedEdge_d = 'undefined';
|
|
|
|
for (var i = 0; i < connectors.length; i++) {
|
|
paintPath(connectors[i],edgeSelected, old_d);
|
|
}
|
|
paintPath(tempEdge,edgeSelected, old_d);
|
|
//
|
|
// var list = document.getElementById("eductList");
|
|
// clearList(list);
|
|
// var list = document.getElementById("productList");
|
|
// clearList(list);
|
|
|
|
} else {
|
|
setSelectedDropdownValue('#edgeDropdown', d.id);
|
|
// console.log(old_connectors);
|
|
edgeSelected = true;
|
|
// paintPath(clickedEdge);
|
|
clickedEdge = tempEdge;
|
|
clickedEdge_d = d;
|
|
for (var i = 0; i < old_connectors.length; i++) {
|
|
paintPath(old_connectors[i],edgeSelected, clickedEdge_d);
|
|
}
|
|
for (var i = 0; i < connectors.length; i++) {
|
|
paintPath(connectors[i],edgeSelected, clickedEdge_d);
|
|
}
|
|
// paintPath(clickedEdge);
|
|
markedEdgeUri = clickedEdge.__data__.id;
|
|
|
|
// var list = document.getElementById("eductList");
|
|
// clearList(list);
|
|
// var list = document.getElementById("productList");
|
|
// clearList(list);
|
|
|
|
// var list = document.getElementById("eductList");
|
|
// appendCompounds(educts,list,eductsTemp);
|
|
// var list = document.getElementById("productList");
|
|
// appendCompounds(products,list,productsTemp);
|
|
}
|
|
}
|
|
|
|
function mark(nodeToMark, select) {
|
|
if (select){
|
|
nodeToMark.style.fill = compoundFillSelected;
|
|
}
|
|
else {
|
|
nodeToMark.style.fill = compoundFill;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Depth first search
|
|
*/
|
|
function dfs(from, to, orig, path, pathNumeric) {
|
|
if (from == to) {
|
|
// add path to all found paths
|
|
for (var i = 0; i < path.length; i++) {
|
|
if (paths.indexOf(path[i]) == -1) {
|
|
paths.push(path[i]);
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.target.index == nodes[0][from].__data__.index
|
|
&& visited[links[0][i].__data__.source.index] == -1) {
|
|
if (links[0][i].__data__.target.index != links[0][i].__data__.source.index) {
|
|
path.push(links[0][i]);
|
|
// @DEBUG pathnumeric stores indices of links
|
|
// pathNumeric.push(/*links[0][i].__data__.target.index*/
|
|
// i);
|
|
var curr = links[0][i].__data__.source.index;
|
|
visited[from] = links[0][i].__data__.source.index;
|
|
dfs(curr, to, orig, path, pathNumeric);
|
|
// Backtracking to find all possible paths
|
|
path.pop();
|
|
// pathNumeric.pop();
|
|
visited[from] = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function paintPath(path, select, d) {
|
|
var curr = path.style;
|
|
|
|
passes = false;
|
|
|
|
if(ADView) {
|
|
if ('ad' in d){
|
|
if(d['id'].includes('node')) {
|
|
passes = d.ad['inAD'];
|
|
} else {
|
|
passes = d.ad[0]['passesAD']
|
|
}
|
|
}
|
|
}
|
|
|
|
if (select) {
|
|
curr.stroke = linkStrokeUp;
|
|
curr.strokeOpacity = linkStrokeOpacitySelected;
|
|
if (path.getAttribute("marker-end"))
|
|
if (path.getAttribute("target-pseudo"))
|
|
path.setAttribute("marker-end","url(#arrow-red)");
|
|
else
|
|
path.setAttribute("marker-end","url(#arrow-red_"+path.getAttribute("idx")+")");
|
|
}
|
|
else {
|
|
curr.stroke = ADView ? (passes ? adIn : adOut) : linkStroke
|
|
curr.strokeOpacity = ADView ? (passes ? 1 : 0) : linkStrokeOpacity;
|
|
if (path.getAttribute("marker-end"))
|
|
if (path.getAttribute("target-pseudo"))
|
|
path.setAttribute("marker-end","url(#arrow)");
|
|
else
|
|
path.setAttribute("marker-end","url(#arrow_"+path.getAttribute("idx")+")");
|
|
}
|
|
}
|
|
|
|
function markOutgoingLinks(links, select) {
|
|
for (var i = 0; i < links.length; i++) {
|
|
var curr = links[i].style;
|
|
if (select) {
|
|
curr.stroke = linkStrokeDown;
|
|
curr.strokeOpacity = linkStrokeOpacitySelected;
|
|
if (links[i].getAttribute("marker-end"))
|
|
if (links[i].getAttribute("target-pseudo"))
|
|
links[i].setAttribute("marker-end","url(#arrow-green)");
|
|
else
|
|
links[i].setAttribute("marker-end","url(#arrow-green_"+links[i].getAttribute("idx")+")");
|
|
}
|
|
else {
|
|
curr.stroke = linkStroke;
|
|
curr.strokeOpacity = linkStrokeOpacity;
|
|
if (links[i].getAttribute("marker-end"))
|
|
if (links[i].getAttribute("target-pseudo"))
|
|
links[i].setAttribute("marker-end","url(#arrow)");
|
|
else
|
|
links[i].setAttribute("marker-end","url(#arrow_"+links[i].getAttribute("idx")+")");
|
|
}
|
|
}
|
|
}
|
|
|
|
function findOutgoing(from) {
|
|
var tempLinks = new Array();
|
|
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.source.index == from) {
|
|
// greenLinks.push(links[0][i]);
|
|
tempLinks.push(links[0][i]);
|
|
}
|
|
}
|
|
|
|
for (var j = 0; j < tempLinks.length; j++) {
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (tempLinks[j].__data__.id == links[0][i].__data__.id) {
|
|
greenLinks.push(links[0][i]);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function findConnectedEdges(from) {
|
|
// var res = new Array();
|
|
for (var i = 0; i < links[0].length; i++) {
|
|
if (links[0][i].__data__.source.index == from) {
|
|
for (var j = 0; j < links[0].length; j++) {
|
|
if (links[0][i].__data__.id == links[0][j].__data__.id) {
|
|
greenLinks.push(links[0][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function resetArrays() {
|
|
visited = new Array(nodes[0].length);
|
|
for (var i = 0; i < visited.length; i++) {
|
|
visited[i] = -1;
|
|
}
|
|
path = [];
|
|
paths = [];
|
|
pathNumeric = [];
|
|
greenLinks = [];
|
|
|
|
}
|
|
|
|
function uploadReaction() {
|
|
|
|
var eductsidlist = [];
|
|
for ( var x in educts) {
|
|
eductsidlist.push(educts[x][1]);
|
|
}
|
|
|
|
var productsidlist = [];
|
|
for ( var x in products) {
|
|
productsidlist.push(products[x][1]);
|
|
}
|
|
|
|
if (educts.length == 0 || products.length == 0) {
|
|
console.log("Caught empty list");
|
|
return;
|
|
}
|
|
|
|
var url = window.location.href + "/edge";
|
|
|
|
var multi = $("#multistep").is(":checked");
|
|
|
|
var data = {
|
|
products : productsidlist.join(),
|
|
educts : eductsidlist.join(),
|
|
edgeReason : "",
|
|
multistep : multi
|
|
};
|
|
|
|
jQuery.ajax({
|
|
type : "post",
|
|
data : data,
|
|
url : url,
|
|
success : function(response) {
|
|
console.log("success");
|
|
location.reload();
|
|
},
|
|
error : function(jqXHR, textStatus, errorThrown) {
|
|
alert("Error while adding Edge");
|
|
console.log(errorThrown);
|
|
console.log(jqXHR);
|
|
console.log(textStatus);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
function dl(url) {
|
|
var hiddenIFrameID = 'hiddenDownloader', iframe = document
|
|
.getElementById(hiddenIFrameID);
|
|
if (iframe === null) {
|
|
iframe = document.createElement('iframe');
|
|
iframe.id = hiddenIFrameID;
|
|
iframe.style.display = 'none';
|
|
document.body.appendChild(iframe);
|
|
}
|
|
iframe.src = url;
|
|
}
|
|
function dlCSV() {
|
|
var thresh = $('#thresh').val();
|
|
console.log(thresh);
|
|
if(!thresh){
|
|
thresh = 0.001;
|
|
}
|
|
var uri = '?toCSV=true&threshold='+thresh;
|
|
dl(uri);
|
|
}
|
|
|
|
function dlPW(){
|
|
var uri = "?hidden=download";
|
|
var thresh = $('#threshold').val();
|
|
if(!thresh){
|
|
thresh = 0.001;
|
|
}
|
|
uri += "&threshold="+thresh;
|
|
var format = $('#format').val();
|
|
uri += "&format="+format;
|
|
if(format === 'custom'){
|
|
var delimiter = $('#delimiter').val();
|
|
if(delimiter){
|
|
uri += "&delimiter="+delimiter;
|
|
}
|
|
}
|
|
dl(uri);
|
|
}
|
|
|