console.log("loaded pw.js") function predictFromNode(url) { $.post("", {node: url}) .done(function (data) { console.log("Success:", data); window.location.href = data.success; }) .fail(function (xhr, status, error) { console.error("Error:", xhr.status, xhr.responseText); // show user-friendly message or log error }); } // data = {{ pathway.d3_json | safe }}; // elem = 'vizdiv' function draw(pathway, elem) { const nodeRadius = 20; const linkDistance = 100; const chargeStrength = -200; const depthSpacing = 150; const width = document.getElementById(elem).offsetWidth, height = width * 0.75; function assignPositions(nodes) { const levelSpacing = 75; // vertical space between levels const horizontalSpacing = 75; // horizontal space between nodes const depthMap = new Map(); nodes.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; depthMap.set(node.depth, depthMap.get(node.depth) + 1); }); } // Funktion für das Update der Positionen function ticked() { 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.fy = d.y; } function dragged(event, d) { d.fx = event.x; // Position direkt an Maus anpassen d.fy = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); // Knoten bleibt an der neuen Position und wird nicht zurückgezogen } // t -> ref to "this" from d3 function nodeClick(event, node, t) { console.log(node); d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted")); } // Wait one second before showing popup var popupWaitBeforeShow = 1000; // Keep Popup at least for one second var popushowAtLeast = 1000; function pop_show_e(element) { var e = element; setTimeout(function () { if ($(e).is(':hover')) { // if element is still hovered $(e).popover("show"); // workaround to set fixed positions 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 () { 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_add(objects, title, contentFunction) { 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(); p.data("bs.popover").tip().css({"max-width": "1000px"}); }); }); } function node_popup(n) { popupContent = "" + n.name + "
"; popupContent += "Depth " + n.depth + "
" if (appDomainViewEnabled) { 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." + "
" if (n.app_domain['uncovered_functional_groups']) { popupContent += "Compound contains functional groups not covered by the training set
" } } } popupContent += "
" if (n.scenarios.length > 0) { popupContent += 'Half-lives and related scenarios:
' for (var s of n.scenarios) { popupContent += "" + s.name + "
"; } } var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0; if(pathway.isIncremental && isLeaf) { popupContent += '
Predict from here
'; } return popupContent; } function edge_popup(e) { popupContent = "" + e.name + "
"; if(e.app_domain){ adcontent = "

"; if(e.app_domain["times_triggered"]) { adcontent += "This rule triggered " + e.app_domain["times_triggered"] + " times in the training set
"; } 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"] + ")
"; 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"] + ")
"; adcontent += "

"; } popupContent += adcontent; popupContent += "
" if (e.reaction_probability) { popupContent += 'Probability:
' + e.reaction_probability.toFixed(3) + '
'; } if (e.scenarios.length > 0) { popupContent += 'Half-lives and related scenarios:
' for (var s of e.scenarios) { popupContent += "" + s.name + "
"; } } return popupContent; } var clientX; var clientY; document.addEventListener('mousemove', function(event) { clientX = event.clientX; clientY =event.clientY; }); const zoomable = d3.select("#zoomable"); // Zoom Funktion aktivieren const zoom = d3.zoom() .scaleExtent([0.5, 5]) .on("zoom", (event) => { zoomable.attr("transform", event.transform); }); d3.select("svg").call(zoom); nodes = pathway['nodes']; links = pathway['links']; // Use "max" depth for a neater viz nodes.forEach(n => { const parents = links .filter(l => l.target === n.id) .map(l => l.source); orig_depth = n.depth // console.log(n.id, parents) for (idx in parents) { p = nodes[parents[idx]] // console.log(p.depth) if (p.depth >= n.depth) { // keep the .5 steps for pseudo nodes n.depth = n.pseudo ? p.depth + 1 : Math.floor(p.depth + 1); // console.log("Adjusting", orig_depth, Math.floor(p.depth + 1)); } } }); assignPositions(nodes); const simulation = d3.forceSimulation(nodes) .force("link", d3.forceLink(links).id(d => d.id).distance(linkDistance)) .force("charge", d3.forceManyBody().strength(chargeStrength)) .force("center", d3.forceCenter(width / 2, height / 4)) .on("tick", ticked); // Kanten zeichnen const link = zoomable.append("g") .selectAll("line") .data(links) .enter().append("line") // Check if target is pseudo and draw marker only if not pseudo .attr("class", d => d.target.pseudo ? "link_no_arrow" : "link") .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 }); pop_add(link, "Reaction", edge_popup); // Knoten zeichnen const node = zoomable.append("g") .selectAll("g") .data(nodes) .enter().append("g") .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on("click", function (event, d) { d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted")); }) // Kreise für die Knoten hinzufügen node.append("circle") // make radius "invisible" .attr("r", d => d.pseudo ? 0.01 : nodeRadius) .style("fill", "#e8e8e8"); // Add image only for non pseudo nodes node.filter(d => !d.pseudo).append("image") .attr("xlink:href", d => d.image) .attr("x", -nodeRadius) .attr("y", -nodeRadius) .attr("width", nodeRadius * 2) .attr("height", nodeRadius * 2); // add element to nodes array node.each(function(d) { d.el = this; // attach the DOM element to the data object }); pop_add(node, "Compound", node_popup); }