Public
Edited
Apr 17, 2023
1 fork
Insert cell
Insert cell
Insert cell
{
// Draw the timed event graph using D3.js
let div = html`<div><div>`;
drawTimedEventGraph(timedEventGraph, div);
return div;
}
Insert cell
console.log(timedEventGraph.fromId("T0"))
Insert cell
{
//debugger;
// timedEventGraph.addPlace(new timedEventGraph._inner.Place("p3", 1));
var t0 = timedEventGraph.fromId("T0");
timedEventGraph.fireTransition(t0);
}
Insert cell
timedEventGraph = {
class Place {
constructor(id, holdingTime = 0) {
this.id = id;
this.holdingTime = holdingTime;
this.tokens = 0;
}
}

class Transition {
constructor(id, holdingTime = 0) {
this.id = id;
this.holdingTime = holdingTime;
}
}

class Arc {
constructor(source, target) {
this.source = source;
this.target = target;
}
}

class TimedEventGraph {
constructor() {
this.places = [];
this.transitions = [];
this.arcs = [];
this.updateCallback = null;
this.mode = 0;
}

setUpdateCallback(callback) {
this.updateCallback = callback;
}
fireTransition(transition) {
// Find all input and output places for the given transition
const inputPlaces = this.arcs
.filter((arc) => arc.target === transition)
.map((arc) => arc.source);
const outputPlaces = this.arcs
.filter((arc) => arc.source === transition)
.map((arc) => arc.target);

// Check if the transition is enabled (i.e., if all input places have at least one token)
const isEnabled = inputPlaces.every((place) => place.tokens > 0);

if (isEnabled) {
// Subtract a token from each input place and add a token to each output place
inputPlaces.forEach((place) => place.tokens--);
outputPlaces.forEach((place) => place.tokens++);

// Update the visualization to reflect the new token counts
this.update();
} else {
console.log(`Transition ${transition.id} is not enabled.`);
}
}
fromId(id) {
// Search for a matching Place object
const place = this.places.find((p) => p.id === id);
if (place) {
return place;
}

// Search for a matching Transition object
const transition = this.transitions.find((t) => t.id === id);
if (transition) {
return transition;
}

// Search for a matching Arc object
const arc = this.arcs.find((t) => t.id === id);
if (arc) {
return arc;
}

// Return null if no matching object is found
return null;
}
addPlace(place) {
this.places.push(place);
this.update();
}

addTransition(transition) {
this.transitions.push(transition);
this.update();
}

addArc(arc,mode) {
this.arcs.push(arc);
this.update();
}

update() {
if (this.updateCallback) {
this.updateCallback();
}
}
}

// Create the timed event graph
const timedEventGraph = new TimedEventGraph();
timedEventGraph._inner = { Arc, Place, Transition };

const place1 = new Place("P1", 5);
const place2 = new Place("P2", 0);
const transition1 = new Transition("T1", 0);
const input = new Transition("T0", 0);
timedEventGraph.addPlace(place1);
timedEventGraph.addPlace(place2);
timedEventGraph.addTransition(transition1);
timedEventGraph.addTransition(input);
timedEventGraph.addArc(new Arc(place1, transition1));
timedEventGraph.addArc(new Arc(transition1, place2));
timedEventGraph.addArc(new Arc(input, place1));

return timedEventGraph;
}
Insert cell
function drawTimedEventGraph(timedEventGraph, containerSelector) {
//const width = 800;
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;
}
}
Insert cell
class Toolbar {
constructor(canvas) {
this.currentTool = null;
this._canvas=canvas;

// Create the toolbar container and buttons
this.toolbarContainer = document.createElement("div");
this.toolbarContainer.classList.add("toolbar-container");

this.addButton("Place", () => this.selectTool("place"));
this.addButton("Transition", () => this.selectTool("transition"));
this.addButton("Connect", () => this.selectTool("connect"));

this.addButton("Show/Hide", () => this.toggleVisibility());

// Add the toolbar container to the body
document.body.appendChild(this.toolbarContainer);

// Set up dragging functionality
this.initDragging();
}

addButton(label, onClick) {
const button = document.createElement("button");
button.innerText = label;
button.addEventListener("click", onClick);
this.toolbarContainer.appendChild(button);
}

selectTool(tool) {
this.currentTool = tool;
// Emit a custom event to notify the Canvas about the tool change
}

toggleVisibility() {
this.toolbarContainer.classList.toggle("hidden");
}

initDragging() {
let isDragging = false;
let offsetX = 0;
let offsetY = 0;

const onMouseDown = (event) => {
isDragging = true;
offsetX = event.clientX - this.toolbarContainer.offsetLeft;
offsetY = event.clientY - this.toolbarContainer.offsetTop;
};

const onMouseMove = (event) => {
if (!isDragging) return;
this.toolbarContainer.style.left = `${event.clientX - offsetX}px`;
this.toolbarContainer.style.top = `${event.clientY - offsetY}px`;
};

const onMouseUp = () => {
isDragging = false;
};

this.toolbarContainer.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
}
}
Insert cell
class Canvas {
constructor(timedEventGraph, toolbar, containerSelector) {
this.timedEventGraph = timedEventGraph;
this.toolbar = toolbar;
this.container = containerSelector;

this.initSvg();
this.initEventListeners();
}

initSvg() {
const width = 800;
const height = 600;

this.svg = d3
.select(this.container)
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "canvas");
}

initEventListeners() {
this.svg.on("click", (event) => this.onClick(event));
// Add other event listeners as needed (e.g., drag, hover, and keydown)
}

onClick(event) {
const [x, y] = d3.pointer(event);

switch (this.toolbar.currentTool) {
case "place":
// Create a new place and add it to the timedEventGraph
break;
case "transition":
// Create a new transition and add it to the timedEventGraph
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
default:
// Handle other cases or do nothing
break;
}
}

onDrag(event) {
// Handle drag events based on the current tool
// (e.g., create a new arc between two elements or move an existing element)
}

onHover(event) {
// Handle hover events to display UI elements like delete, rename, etc.
}

onKeyDown(event) {
// Handle keyboard events, such as deleting selected elements
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more