// drawarea var svg; // colors var border = "thin solid lightgrey"; // nodes // links // tree 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 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 // Convenience method to check wether a given node is a leaf function isLeaf(node) { return node.children.length == 0; } function getMaxNodeDepth(graph) { maxDepth = 0 graph.nodes.forEach(function(node, i) { if (node.depth > maxDepth) maxDepth = node.depth; }); return maxDepth; } // adds a parents[] and a children[] array for each node set treeSize function setChildrenAndParents(graph) { // Init arrays on each node graph.nodes.forEach(function(node, i) { node.parents = [] node.children = [] }); // loop through links, determine source and target and populate // their parent/children arrays graph.links.forEach(function(link, j) { source = graph.nodes[link.source]; target = graph.nodes[link.target]; source.children.push(target); target.parents.push(source); }); // Recursively traverses the tree to count the nodes that can be reached 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; } // Check how many nodes can be reached from each given node // by traversing the children property graph.nodes.forEach(function(node, i) { // reset touch on all nodes graph.nodes.forEach(function(n, i) { n.touch = false; }); // get count node.downstreamNodeCount = count(node); }); // determine siblings for each links 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); }); } // 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; } }); } function redraw() { svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")"); } 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 _drawPathway(elem, graph, options) { width = document.getElementById(elem).offsetWidth; height = width * 0.75; svg = d3.select("#" + elem) .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'); // 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); setChildrenAndParents(graph) setPseudoLinks(graph) var maxDepths = getMaxNodeDepth(graph); // if(options.treeLayout) { // // } else { // // } 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]); force.nodes(graph.nodes).links(graph.links).start(); } function drawPathway(elem, options) { d3.json("", function(error, graph) { if (graph) { _drawPathway(elem, graph, options); } else { throw new Error("Graph not defined: " + error); } }); }