forked from enviPath/enviPy
191 lines
5.9 KiB
JavaScript
191 lines
5.9 KiB
JavaScript
// 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);
|
|
}
|
|
});
|
|
} |