forked from enviPath/enviPy
All html files now prettier formatted and fixes for incompatible blocks applied Co-authored-by: Tim Lorsbach <tim@lorsba.ch> Reviewed-on: enviPath/enviPy#193 Co-authored-by: Tobias O <tobias.olenyi@envipath.com> Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
238 lines
7.6 KiB
HTML
238 lines
7.6 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Force-Directed Tree</title>
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<style>
|
|
svg {
|
|
width: 100%;
|
|
height: 600px;
|
|
}
|
|
.link {
|
|
stroke: #999;
|
|
stroke-opacity: 0.6;
|
|
marker-end: url(#arrow);
|
|
}
|
|
.link_no_arrow {
|
|
stroke: #999;
|
|
stroke-opacity: 0.6;
|
|
}
|
|
.node image {
|
|
cursor: pointer;
|
|
}
|
|
.node circle {
|
|
fill: lightblue;
|
|
stroke: steelblue;
|
|
stroke-width: 1.5px;
|
|
}
|
|
.highlighted {
|
|
stroke: red;
|
|
stroke-width: 3px;
|
|
}
|
|
.tooltip {
|
|
position: absolute;
|
|
background: lightgray;
|
|
padding: 5px;
|
|
border-radius: 5px;
|
|
visibility: hidden;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<p></p>
|
|
{{ pathway.name|safe }}
|
|
<div id="viz">
|
|
<svg width="2000" height="2000">
|
|
<!-- Sehr großes SVG für Zoom -->
|
|
<defs>
|
|
<marker
|
|
id="arrow"
|
|
viewBox="0 0 10 10"
|
|
refX="43"
|
|
refY="5"
|
|
markerWidth="6"
|
|
markerHeight="6"
|
|
orient="auto-start-reverse"
|
|
>
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
|
|
</marker>
|
|
</defs>
|
|
<g id="zoomable"></g>
|
|
</svg>
|
|
</div>
|
|
<div id="tooltip" class="tooltip"></div>
|
|
|
|
{# prettier-ignore-start #}
|
|
<script>
|
|
function assignPositions(nodes) {
|
|
const levelSpacing = 75; // Vertikaler Abstand zwischen den Tiefen
|
|
const horizontalSpacing = 75; // Abstand zwischen Knoten auf gleicher Tiefe
|
|
const depthMap = new Map();
|
|
|
|
nodes.forEach(node => {
|
|
if (!depthMap.has(node.depth)) {
|
|
depthMap.set(node.depth, 0);
|
|
}
|
|
|
|
// Gleichmäßige horizontale Verteilung je nach Anzahl Knoten pro Tiefe
|
|
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);
|
|
});
|
|
}
|
|
|
|
function nodeClick(event, node, t) {
|
|
console.log(node);
|
|
d3.select(t).select("circle").classed("highlighted", !d3.select(t).select("circle").classed("highlighted"));
|
|
}
|
|
|
|
const nodeRadius = 20;
|
|
const linkDistance = 100;
|
|
const chargeStrength = -200;
|
|
const depthSpacing = 150;
|
|
const width = 800, height = 600;
|
|
|
|
const tooltip = d3.select("#tooltip");
|
|
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);
|
|
|
|
data = {{ pathway.d3_json | safe }};
|
|
nodes = data['nodes'];
|
|
links = data['links'];
|
|
|
|
assignPositions(nodes);
|
|
|
|
// Physik-Simulation für den Graphen
|
|
<!-- const simulation = d3.forceSimulation(nodes)-->
|
|
<!-- .force("link", d3.forceLink(links).id(d => d.id).distance(linkDistance))-->
|
|
<!-- .force("y", d3.forceY(d => d.depth * depthSpacing + 50))-->
|
|
<!-- .force("x", d3.forceX(width / 2))-->
|
|
<!-- .force("charge", d3.forceManyBody().strength(chargeStrength))-->
|
|
<!-- .force("center", d3.forceCenter(width / 2, height / 4))-->
|
|
<!-- .on("tick", ticked);-->
|
|
<!-- -->
|
|
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")
|
|
.on("mouseover", (event, d) => {
|
|
tooltip.style("visibility", "visible")
|
|
.text(`Link: ${d.source.id} → ${d.target.id}`)
|
|
.style("top", `${event.pageY + 5}px`)
|
|
.style("left", `${event.pageX + 5}px`);
|
|
})
|
|
.on("mouseout", () => tooltip.style("visibility", "hidden"));
|
|
|
|
// 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) {
|
|
nodeClick(event, d, this);
|
|
<!-- console.log(event); console.log(d);console.log(this);-->
|
|
<!-- d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));-->
|
|
})
|
|
.on("mouseover", (event, d) => {
|
|
if (d.pseudo) {
|
|
return
|
|
}
|
|
tooltip.style("visibility", "visible")
|
|
.text(`Node: ${d.id} Depth: ${d.depth}`)
|
|
.style("top", `${event.pageY + 5}px`)
|
|
.style("left", `${event.pageX + 5}px`);
|
|
})
|
|
.on("mouseout", () => tooltip.style("visibility", "hidden"));
|
|
|
|
// 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);
|
|
|
|
|
|
// 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})`);
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
</script>
|
|
{# prettier-ignore-end #}
|
|
<svg width="400" height="300">
|
|
<path
|
|
d="M200,50 C200,100 150,150 150,200"
|
|
stroke="black"
|
|
fill="none"
|
|
stroke-width="2"
|
|
/>
|
|
<path
|
|
d="M200,50 C200,100 250,150 250,200"
|
|
stroke="black"
|
|
fill="none"
|
|
stroke-width="2"
|
|
/>
|
|
|
|
<!-- Knoten -->
|
|
<circle cx="200" cy="50" r="5" fill="blue" />
|
|
<circle cx="150" cy="200" r="5" fill="red" />
|
|
<circle cx="250" cy="200" r="5" fill="red" />
|
|
</svg>
|
|
</body>
|
|
{% load static %}
|
|
<script src="{% static 'js/pw.js' %}"></script>
|
|
</html>
|