function drawTimedEventGraph(timedEventGraph, containerSelector) {
const height = 600;
const nodeRadius = 30;
const svg = d3
.select(containerSelector)
.append("svg")
.attr("width", width)
.attr("height", height);
let nodes = [...timedEventGraph.places, ...timedEventGraph.transitions];
let links = timedEventGraph.arcs;
function storePositions() {
nodes.forEach((node) => {
node.x = node.x || null;
node.y = node.y || null;
});
}
let tickCount = 0;
const maxTicks = 200;
const simulation = d3
.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-900))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink(links).distance(200).strength(1))
.force("collide", d3.forceCollide(nodeRadius * 2))
.on("tick", ticked);
let link = svg
.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr("marker-end", "url(#arrowhead)");
let place = svg
.selectAll(".place")
.data(timedEventGraph.places)
.enter()
.append("g")
.each(function (d) {
d.svgElement = this; // Set the SVG DOM element as an attribute of the Transition object
})
.attr("class", "place")
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
place
.append("circle")
.attr("class", "outer-circle")
.attr("r", nodeRadius * 1.3)
.style("display", (d) => (d.holdingTime ? "block" : "none"));
place
.append("circle")
.attr("class", "inner-circle")
.attr("r", nodeRadius)
.style("display", (d) => "block");
let transitionheight = nodeRadius * 4;
// Create transition elements
let transition = svg
.selectAll(".transition")
.data(timedEventGraph.transitions)
.enter()
.append("rect")
.each(function (d) {
d.svgElement = this; // Set the SVG DOM element as an attribute of the Transition object
})
.attr("class", (d) => (d.holdingTime ? "transition" : "transition-filled"))
.attr("width", nodeRadius)
.attr("height", transitionheight)
.attr("rx", 3)
.attr("ry", 3)
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
const marker = svg
.append("defs")
.append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", nodeRadius * 1.3 + 5) // adjust the position of the arrowhead along the line
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("class", "arrowhead");
let label = svg
.selectAll(".label")
.data(nodes)
.enter()
.append("text")
.attr("class", "label")
.text((d) => d.id)
.attr("text-anchor", "middle")
.attr("dy", ".3em");
timedEventGraph.setUpdateCallback(updateVisualization);
function updateVisualization() {
// Update nodes and links data
nodes = [...timedEventGraph.places, ...timedEventGraph.transitions];
links = timedEventGraph.arcs;
// Update the simulation's nodes and links
simulation.nodes(nodes);
//simulation.force("link").links(links);
// Restart the simulation
tickCount = 0;
simulation.alphaTarget(0.3).restart();
// Update the visualization elements
link = link
.data(links)
.join("line")
.attr("class", "link")
.attr("marker-end", "url(#arrowhead)");
place = place
.data(timedEventGraph.places)
.join("g")
.attr("class", "place")
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
place
.selectAll(".outer-circle")
.data((d) => [d])
.join("circle")
.attr("class", "outer-circle")
.attr("r", nodeRadius * 1.3)
.style("display", (d) => (d.holdingTime ? "block" : "none"));
place
.selectAll(".inner-circle")
.data((d) => [d])
.join("circle")
.attr("class", "inner-circle")
.attr("r", nodeRadius)
.style("display", (d) => "block");
// Draw tokens as dots inside the inner circle
// console.log("does this get executed");
place
.selectAll(".token-dot")
.data((d) => (d.tokens < 6 ? Array(d.tokens).fill(d) : []))
.join("circle")
.attr("class", "token-dot")
.attr("r", 3)
.attr(
"cx",
(d, i) => nodeRadius * 0.5 * Math.cos((i * 2 * Math.PI) / d.tokens)
)
.attr(
"cy",
(d, i) => nodeRadius * 0.5 * Math.sin((i * 2 * Math.PI) / d.tokens)
)
.style("display", (d) => "block");
// Display the numeric value for 6 or more tokens in the center
place
.selectAll(".token-count")
.data((d) => [d])
.join("text")
.attr("class", "token-count")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text((d) => (d.tokens >= 6 ? d.tokens : ""))
.style("display", (d) => (d.tokens >= 6 ? "block" : "none"));
// Update transition elements
transition = transition
.data(timedEventGraph.transitions)
.join("rect")
.attr("class", (d) =>
d.holdingTime ? "transition" : "transition-filled"
)
.attr("width", nodeRadius)
.attr("height", transitionheight)
.attr("rx", 3)
.attr("ry", 3)
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
label = label
.data(nodes)
.join("text")
.attr("class", "label")
.text((d) => d.id)
.attr("text-anchor", "middle")
.attr("dy", ".0");
}
function ticked() {
if (tickCount >= maxTicks) {
simulation.stop();
} else {
tickCount++;
}
/* function getCoords(node, offset, coord) {
if (node.constructor.name == "Transition") {
//console.log("transition?");
return node[coord] + offset;
} else {
return node[coord];
}
}*/
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);
place.attr("transform", (d) => `translate(${d.x}, ${d.y})`);
transition
.attr("x", (d) => d.x - nodeRadius / 2)
.attr("y", (d) => d.y - transitionheight / 2);
//node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
label.attr("x", (d) => d.x).attr("y", (d) => d.y - transitionheight);
}
function dragstarted(event, d) {
if (!event.active) {
tickCount = 0;
simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
tickCount = 0;
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
tickCount = 0;
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}