forked from enviPath/enviPy
Fix App Domain Bug when a Rule can be applied more than once (#49)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#49
This commit is contained in:
@ -739,7 +739,10 @@ function handleAssessmentResponse(depict_url, data) {
|
||||
var objLink = null;
|
||||
if (transObj['is_predicted']) {
|
||||
panelName = `Predicted Transformation by ${transObj['rule']['name']}`;
|
||||
objLink = `<a class='list-group-item' href="${transObj['edge']['url']}">${transObj['edge']['name']}</a>`
|
||||
for (e in transObj['edges']) {
|
||||
objLink = `<a class='list-group-item' href="${transObj['edges'][e]['url']}">${transObj['edges'][e]['name']}</a>`
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
panelName = `Potential Transformation by applying ${transObj['rule']['name']}`;
|
||||
objLink = `<a class='list-group-item' href="${transObj['rule']['url']}">${transObj['rule']['name']}</a>`
|
||||
|
||||
194
static/js/pw.js
194
static/js/pw.js
@ -1,6 +1,5 @@
|
||||
console.log("loaded pw.js")
|
||||
|
||||
|
||||
function predictFromNode(url) {
|
||||
$.post("", {node: url})
|
||||
.done(function (data) {
|
||||
@ -28,62 +27,165 @@ function draw(pathway, elem) {
|
||||
const horizontalSpacing = 75; // horizontal space between nodes
|
||||
const depthMap = new Map();
|
||||
|
||||
nodes.forEach(node => {
|
||||
// Sort nodes by depth first to minimize crossings
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
|
||||
|
||||
sortedNodes.forEach(node => {
|
||||
if (!depthMap.has(node.depth)) {
|
||||
depthMap.set(node.depth, 0);
|
||||
}
|
||||
|
||||
const nodesInLevel = nodes.filter(n => n.depth === node.depth).length;
|
||||
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
|
||||
node.fy = node.depth * levelSpacing + 50;
|
||||
|
||||
// For pseudo nodes, try to position them to minimize crossings
|
||||
if (node.pseudo) {
|
||||
const parentLinks = links.filter(l => l.target.id === node.id);
|
||||
const childLinks = links.filter(l => l.source.id === node.id);
|
||||
|
||||
if (parentLinks.length > 0 && childLinks.length > 0) {
|
||||
const parentX = parentLinks[0].source.x || (width / 2);
|
||||
const childrenX = childLinks.map(l => l.target.x || (width / 2));
|
||||
const avgChildX = childrenX.reduce((sum, x) => sum + x, 0) / childrenX.length;
|
||||
|
||||
// Position pseudo node between parent and average child position
|
||||
node.fx = (parentX + avgChildX) / 2;
|
||||
} else {
|
||||
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
|
||||
}
|
||||
} else {
|
||||
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
|
||||
}
|
||||
|
||||
node.fy = node.depth * levelSpacing + 50;
|
||||
depthMap.set(node.depth, depthMap.get(node.depth) + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Funktion für das Update der Positionen
|
||||
// Function to update pseudo node positions based on connected nodes
|
||||
function updatePseudoNodePositions() {
|
||||
nodes.forEach(node => {
|
||||
if (node.pseudo && !node.isDragging) { // Don't auto-update if being dragged
|
||||
const parentLinks = links.filter(l => l.target.id === node.id);
|
||||
const childLinks = links.filter(l => l.source.id === node.id);
|
||||
|
||||
if (parentLinks.length > 0 && childLinks.length > 0) {
|
||||
const parent = parentLinks[0].source;
|
||||
const children = childLinks.map(l => l.target);
|
||||
|
||||
// Calculate optimal position to minimize crossing
|
||||
const parentX = parent.x;
|
||||
const parentY = parent.y;
|
||||
const childrenX = children.map(c => c.x);
|
||||
const childrenY = children.map(c => c.y);
|
||||
const avgChildX = d3.mean(childrenX);
|
||||
const avgChildY = d3.mean(childrenY);
|
||||
|
||||
// Position pseudo node between parent and average child position
|
||||
node.fx = (parentX + avgChildX) / 2;
|
||||
node.fy = (parentY + avgChildY) / 2; // Allow vertical movement too
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Enhanced ticked function
|
||||
function ticked() {
|
||||
// Update pseudo node positions first
|
||||
updatePseudoNodePositions();
|
||||
|
||||
link.attr("x1", d => d.source.x)
|
||||
.attr("y1", d => d.source.y)
|
||||
.attr("x2", d => d.target.x)
|
||||
.attr("y2", d => d.target.y);
|
||||
|
||||
node.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||
|
||||
nodes.forEach(n => {
|
||||
if (n.pseudo) {
|
||||
// Alle Kinder dieses Pseudonodes finden
|
||||
const childLinks = links.filter(l => l.source.id === n.id);
|
||||
const childNodes = childLinks.map(l => l.target);
|
||||
if (childNodes.length > 0) {
|
||||
// Durchschnitt der Kinderpositionen berechnen
|
||||
const avgX = d3.mean(childNodes, d => d.x);
|
||||
const avgY = d3.mean(childNodes, d => d.y);
|
||||
n.fx = avgX;
|
||||
// keep level as is
|
||||
n.fy = n.y;
|
||||
}
|
||||
}
|
||||
});
|
||||
//simulation.alpha(0.3).restart();
|
||||
}
|
||||
|
||||
function dragstarted(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x; // Setzt die Fixierung auf die aktuelle Position
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
|
||||
// Mark if this node is being dragged
|
||||
d.isDragging = true;
|
||||
|
||||
// If dragging a non-pseudo node, mark connected pseudo nodes for update
|
||||
if (!d.pseudo) {
|
||||
markConnectedPseudoNodes(d);
|
||||
}
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
d.fx = event.x; // Position direkt an Maus anpassen
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
|
||||
// Update connected pseudo nodes in real-time
|
||||
if (!d.pseudo) {
|
||||
updateConnectedPseudoNodes(d);
|
||||
}
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
// Knoten bleibt an der neuen Position und wird nicht zurückgezogen
|
||||
|
||||
// Mark that dragging has ended
|
||||
d.isDragging = false;
|
||||
|
||||
// Final update of connected pseudo nodes
|
||||
if (!d.pseudo) {
|
||||
updateConnectedPseudoNodes(d);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to mark connected pseudo nodes
|
||||
function markConnectedPseudoNodes(draggedNode) {
|
||||
// Find pseudo nodes connected to this node
|
||||
const connectedPseudos = new Set();
|
||||
|
||||
// Check as parent of pseudo nodes
|
||||
links.filter(l => l.source.id === draggedNode.id && l.target.pseudo)
|
||||
.forEach(l => connectedPseudos.add(l.target));
|
||||
|
||||
// Check as child of pseudo nodes
|
||||
links.filter(l => l.target.id === draggedNode.id && l.source.pseudo)
|
||||
.forEach(l => connectedPseudos.add(l.source));
|
||||
|
||||
return connectedPseudos;
|
||||
}
|
||||
|
||||
// Helper function to update connected pseudo nodes
|
||||
function updateConnectedPseudoNodes(draggedNode) {
|
||||
const connectedPseudos = markConnectedPseudoNodes(draggedNode);
|
||||
|
||||
connectedPseudos.forEach(pseudoNode => {
|
||||
if (!pseudoNode.isDragging) { // Don't update if pseudo node is being dragged
|
||||
const parentLinks = links.filter(l => l.target.id === pseudoNode.id);
|
||||
const childLinks = links.filter(l => l.source.id === pseudoNode.id);
|
||||
|
||||
if (parentLinks.length > 0 && childLinks.length > 0) {
|
||||
const parent = parentLinks[0].source;
|
||||
const children = childLinks.map(l => l.target);
|
||||
|
||||
const parentX = parent.fx || parent.x;
|
||||
const parentY = parent.fy || parent.y;
|
||||
const childrenX = children.map(c => c.fx || c.x);
|
||||
const childrenY = children.map(c => c.fy || c.y);
|
||||
const avgChildX = d3.mean(childrenX);
|
||||
const avgChildY = d3.mean(childrenY);
|
||||
|
||||
// Update pseudo node position - allow both X and Y movement
|
||||
pseudoNode.fx = (parentX + avgChildX) / 2;
|
||||
pseudoNode.fy = (parentY + avgChildY) / 2;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Restart simulation with lower alpha to smooth the transition
|
||||
simulation.alpha(0.1).restart();
|
||||
}
|
||||
|
||||
|
||||
// t -> ref to "this" from d3
|
||||
function nodeClick(event, node, t) {
|
||||
console.log(node);
|
||||
@ -105,7 +207,7 @@ function draw(pathway, elem) {
|
||||
pop = $(e).attr("aria-describedby")
|
||||
h = $('#' + pop).height();
|
||||
$('#' + pop).attr("style", `position: fixed; top: ${clientY - (h / 2.0)}px; left: ${clientX + 10}px; margin: 0px; max-width: 1000px; display: block;`)
|
||||
setTimeout(function () {
|
||||
setTimeout(function () {
|
||||
var close = setInterval(function () {
|
||||
if (!$(".popover:hover").length // mouse outside popover
|
||||
&& !$(e).is(':hover')) { // mouse outside element
|
||||
@ -140,13 +242,12 @@ function draw(pathway, elem) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function node_popup(n) {
|
||||
popupContent = "<a href='" + n.url +"'>" + n.name + "</a><br>";
|
||||
popupContent = "<a href='" + n.url + "'>" + n.name + "</a><br>";
|
||||
popupContent += "Depth " + n.depth + "<br>"
|
||||
|
||||
if (appDomainViewEnabled) {
|
||||
if(n.app_domain != null) {
|
||||
if (n.app_domain != null) {
|
||||
popupContent += "This compound is " + (n.app_domain['inside_app_domain'] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "<br>"
|
||||
if (n.app_domain['uncovered_functional_groups']) {
|
||||
popupContent += "Compound contains functional groups not covered by the training set <br>"
|
||||
@ -154,7 +255,7 @@ function draw(pathway, elem) {
|
||||
}
|
||||
}
|
||||
|
||||
popupContent += "<img src='" + n.image + "' width='"+ 20 * nodeRadius +"'><br>"
|
||||
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>"
|
||||
if (n.scenarios.length > 0) {
|
||||
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
||||
for (var s of n.scenarios) {
|
||||
@ -163,7 +264,7 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
|
||||
if(pathway.isIncremental && isLeaf) {
|
||||
if (pathway.isIncremental && isLeaf) {
|
||||
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
|
||||
}
|
||||
|
||||
@ -171,25 +272,24 @@ function draw(pathway, elem) {
|
||||
}
|
||||
|
||||
function edge_popup(e) {
|
||||
popupContent = "<a href='" + e.url +"'>" + e.name + "</a><br>";
|
||||
popupContent = "<a href='" + e.url + "'>" + e.name + "</a><br>";
|
||||
|
||||
if(e.app_domain){
|
||||
if (e.app_domain) {
|
||||
adcontent = "<p>";
|
||||
if(e.app_domain["times_triggered"]) {
|
||||
if (e.app_domain["times_triggered"]) {
|
||||
adcontent += "This rule triggered " + e.app_domain["times_triggered"] + " times in the training set<br>";
|
||||
}
|
||||
adcontent += "Reliability " + e.app_domain["reliability"].toFixed(2) + " (" + (e.app_domain["reliability"] > e.app_domain["reliability_threshold"] ? ">" : "<") + " Reliability Threshold of " + e.app_domain["reliability_threshold"] + ")<br>";
|
||||
adcontent += "Local Compatibility " + e.app_domain["local_compatibility"].toFixed(2) + " (" + (e.app_domain["local_compatibility"] > e.app_domain["local_compatibility_threshold"] ? ">" : "<") + " Local Compatibility Threshold of " + e.app_domain["local_compatibility_threshold"] + ")<br>";
|
||||
adcontent += "Reliability " + e.app_domain["reliability"].toFixed(2) + " (" + (e.app_domain["reliability"] > e.app_domain["reliability_threshold"] ? ">" : "<") + " Reliability Threshold of " + e.app_domain["reliability_threshold"] + ")<br>";
|
||||
adcontent += "Local Compatibility " + e.app_domain["local_compatibility"].toFixed(2) + " (" + (e.app_domain["local_compatibility"] > e.app_domain["local_compatibility_threshold"] ? ">" : "<") + " Local Compatibility Threshold of " + e.app_domain["local_compatibility_threshold"] + ")<br>";
|
||||
adcontent += "</p>";
|
||||
popupContent += adcontent;
|
||||
}
|
||||
popupContent += adcontent;
|
||||
|
||||
popupContent += "<img src='" + e.image + "' width='"+ 20 * nodeRadius +"'><br>"
|
||||
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>"
|
||||
if (e.reaction_probability) {
|
||||
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
|
||||
}
|
||||
|
||||
|
||||
if (e.scenarios.length > 0) {
|
||||
popupContent += '<b>Half-lives and related scenarios:</b><br>'
|
||||
for (var s of e.scenarios) {
|
||||
@ -202,9 +302,9 @@ function draw(pathway, elem) {
|
||||
|
||||
var clientX;
|
||||
var clientY;
|
||||
document.addEventListener('mousemove', function(event) {
|
||||
document.addEventListener('mousemove', function (event) {
|
||||
clientX = event.clientX;
|
||||
clientY =event.clientY;
|
||||
clientY = event.clientY;
|
||||
});
|
||||
|
||||
const zoomable = d3.select("#zoomable");
|
||||
@ -258,8 +358,8 @@ function draw(pathway, elem) {
|
||||
.attr("marker-end", d => d.target.pseudo ? '' : 'url(#arrow)')
|
||||
|
||||
// add element to links array
|
||||
link.each(function(d) {
|
||||
d.el = this; // attach the DOM element to the data object
|
||||
link.each(function (d) {
|
||||
d.el = this; // attach the DOM element to the data object
|
||||
});
|
||||
|
||||
pop_add(link, "Reaction", edge_popup);
|
||||
@ -279,7 +379,7 @@ function draw(pathway, elem) {
|
||||
|
||||
// Kreise für die Knoten hinzufügen
|
||||
node.append("circle")
|
||||
// make radius "invisible"
|
||||
// make radius "invisible" for pseudo nodes
|
||||
.attr("r", d => d.pseudo ? 0.01 : nodeRadius)
|
||||
.style("fill", "#e8e8e8");
|
||||
|
||||
@ -292,9 +392,9 @@ function draw(pathway, elem) {
|
||||
.attr("height", nodeRadius * 2);
|
||||
|
||||
// add element to nodes array
|
||||
node.each(function(d) {
|
||||
d.el = this; // attach the DOM element to the data object
|
||||
node.each(function (d) {
|
||||
d.el = this; // attach the DOM element to the data object
|
||||
});
|
||||
|
||||
pop_add(node, "Compound", node_popup);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user