Files
enviPy-bayer/static/js/epp/pathway.js
2025-06-23 20:13:54 +02:00

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);
}
});
}