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, "$2"); } /*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 = "

Reaction: "+d.name+"

"; var adcontent = ""; if(d.ad){ for(i in d.ad) { data = d.ad[i]; adcontent = "

"; if(data["timesTriggered"]) { adcontent += "This rule triggered " + data["timesTriggered"] + " in the training set
"; } adcontent += "Reliability " + round(data["reliability"]) + " (" + (data["reliability"] > data["reliabilityThreshold"] ? ">" : "<") + " Reliability Threshold of " + data["reliabilityThreshold"] + ")
"; adcontent += "Local Compatibility " + round(data["localCompatibility"]) + " (" + (data["localCompatibility"] > data["localCompatibilityThreshold"] ? ">" : "<") + " Reliability Threshold of " + data["localCompatibilityThreshold"] + ")
"; adcontent += "

"; } } content += adcontent; if (d.scenarios && d.scenarios.length>0) { content += "

Scenario/s:" for (var sc in d.scenarios) { scen = d.scenarios[sc] content += "
"+scen.name+""; if (scen.modelpredictionprob) { content += "

Relative Reasoning: "+modelName+""; content += "
Probability: "+scen.modelpredictionprob+"

"; } if (scen.modelbayespredictionprob) { content += "

Relative Reasoning: "+modelName+""; content += "
Bayes Probability: "+scen.modelbayespredictionprob+"

"; } } content += "

"; } if (d.ecNumbers && d.ecNumbers.length>0) { content += "

Enzyme/s:"; for (var ec in d.ecNumbers) { content += "
" + d.ecNumbers[ec].ecName + " (" + d.ecNumbers[ec].ecNumber + ")"; } content += "

"; } 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." + "
"; if(d.ad['uncoveredFG']) { proposedInfo += "Compound contains functional groups not covered by the training set
"; } } if(graph.isIncremental && isLeaf(d)) { console.log(d); proposedInfo += 'Predict from here
' } if(d.properties.length > 0) { proposedInfo += "
"; for(x in d.properties) { property = d.properties[x]; proposedInfo += property["name"] + ": " + property["value"] + '
'; } proposedInfo += "
"; } sortByKey(prop, 'scenarioName'); proposedInfo += ""+d.name+"
" +"Depth: "+d.depth+"

" +"" + "
" +"

Half-lives and related scenarios:

"; for(var i in prop){ // Get scenario name and uri var scenName = prop[i].scenarioName; var scenUri = prop[i].scenarioId; proposedInfo += "" + scenName+"
"; // Iterate over all other properties for(var key in prop[i]){ if (key === 'scenarioName' || key === 'scenarioId') { continue; } proposedInfo += key+": "+prop[i][key]+"
"; } proposedInfo += '
'; } 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 .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); }