Public
Edited
Jun 12, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function TimedEventGraphComponent() {
const tegRef = useRef(timedEventGraph);
const [currentMode, setCurrentMode] = useState(tegRef.current.mode);
const [currentTool, setCurrentTool] = useState("cursor"); //default tool

const handleModeChange = (newMode) => {
tegRef.current.mode = newMode;
setCurrentMode(newMode);
tegRef.current.update();
};

const addNewMode = (newMode) => {
tegRef.current.addMode(newMode);
tegRef.current.update();
};

const deleteMode = (modeName) => {
if (currentMode === modeName) {
setCurrentMode(Object.keys(tegRef.current.arcs)[0]);
tegRef.current.mode = Object.keys(tegRef.current.arcs)[0];
}
tegRef.current.removeMode(modeName);
tegRef.current.update();
};
const handleModeAdd = (modeName) => {
tegRef.current.addMode(modeName);
tegRef.current.update();
};

const handleModeRemove = (modeName) => {
tegRef.current.removeMode(modeName);
tegRef.current.update();
};

return jsx`
<div>
<${Toolbar}
onModeAdd={handleModeAdd}
onModeRemove={handleModeRemove}
onModeChange=${handleModeChange}
tegRef=${tegRef}
addNewMode=${addNewMode}
deleteMode=${deleteMode}
currentMode=${currentMode}
setCurrentTool=${setCurrentTool}
currentTool={currentTool}
/>
<${Canvas} tegRef=${tegRef} currentMode=${currentMode} currentTool=${currentTool}/>
</div>
`;
}
Insert cell
function Toolbar({
onModeChange,
tegRef,
addNewMode,
deleteMode,
currentMode,
setCurrentTool,
currentTool
}) {
const toolbarRef = useRef();
//const [currentTool, setCurrentTool] = useState(null);

useEffect(() => {
initDragging();
}, []);

const selectTool = (tool) => {
setCurrentTool(tool);
//console.log("I have selected current Tool... " + tool);
// Emit a custom event to notify the Canvas about the tool change
};

const toggleVisibility = () => {
toolbarRef.current.classList.toggle("hidden");
};

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

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

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

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

toolbarRef.current.addEventListener("mousedown", onMouseDown);
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
};
const [showNewModeInput, setShowNewModeInput] = useState(false);
const [newModeName, setNewModeName] = useState("");
var modes = Object.keys(tegRef.current.arcs);
// let currentMode = tegRef.current.mode;
const handleChange = (event) => {
if (event.target.value === "new") {
setShowNewModeInput(true);
} else {
onModeChange(event.target.value);
}
};

const handleNewModeChange = (event) => {
setNewModeName(event.target.value);
};

const handleNewModeSubmit = (event) => {
event.preventDefault();
if (newModeName) {
addNewMode(newModeName);
onModeChange(newModeName);
setShowNewModeInput(false);
setNewModeName("");
}
};

const handleDeleteMode = () => {
deleteMode(currentMode);
};
const tools = [
{ id: "cursor", label: "🖱️" },
{ id: "place", label: "⚪" },
{ id: "transition", label: "▭" },
{ id: "connect", label: "🔗" },
{ id: "edit", label: "✏️" },
{ id: "delete", label: "🗑️" }
];

return jsx`
<button onClick=${toggleVisibility}>Show/Hide</button>
<div ref=${toolbarRef} class="toolbar">

<div class="tool-selector">
${tools.map((tool) => {
return jsx`
<input
type="radio"
id=${tool.id}
name="tool"
value=${tool.id}
onChange=${() => selectTool(tool.id)}
/>
<label htmlFor=${tool.id}>${tool.label}</label>
`;
})}
</div>
${
!showNewModeInput
? jsx`<select value=${currentMode} onChange=${handleChange}>
${modes.map(
(mode, index) => jsx`<option value=${mode}>${mode}</option>`
)}
<option value="new">New Mode</option>
</select>`
: jsx`<form onSubmit=${handleNewModeSubmit}>
<input
type="text"
autoFocus
value=${newModeName}
onChange=${handleNewModeChange}
placeholder="Enter new mode name"
/>
</form>`
}
<button onClick=${handleDeleteMode}>Delete Mode</button>
</div>
`;
}
Insert cell
function Canvas({ tegRef, currentTool }) {
const canvasRef = useRef();
const [showInput, setShowInput] = useState(false);
const [newName, setNewName] = useState("");
const [inputPosition, setInputPosition] = useState({ x: 0, y: 0 });
const [selectedElement, setSelectedElement] = useState(null);
const [blurTimeout, setBlurTimeout] = useState(null);
const [singleClickTimeout, setSingleClickTimeout] = useState(null);

const clearBlurTimeout = () => {
if (blurTimeout) {
clearTimeout(blurTimeout);
setBlurTimeout(null);
}
};
const handleNameChange = (event) => {
setNewName(event.target.value);
};
const handleElementUpdate = (event) => {
event.preventDefault();
if (selectedElement) {
selectedElement.elementData.id = selectedElement.id;
console.log(
"selectedElement.elementData.holdingTime = selectedElement.holdingTime",
selectedElement.elementData.holdingTime,
selectedElement.holdingTime
);
selectedElement.elementData.holdingTime[tegRef.current.mode] =
selectedElement.holdingTime;
tegRef.current.update();
//if (updatedElement) {
setSelectedElement(null);
//}
}
};

const handleElementNameChange = (event) => {
setSelectedElement({ ...selectedElement, id: event.target.value });
};

const handleElementHoldingTimeChange = (event) => {
setSelectedElement({
...selectedElement,
holdingTime: parseFloat(event.target.value)
});
};

const handlePlaceNameSubmit = (event) => {
event.preventDefault();
if (newName) {
// Create a new place and add it to the timedEventGraph
switch (currentTool) {
case "place":
tegRef.current.addPlace(
new Place(newName, 0, inputPosition.x, inputPosition.y)
);
break;
case "transition":
tegRef.current.addTransition(
new Transition(newName, 0, inputPosition.x, inputPosition.y)
);
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
default:
// Handle other cases or do nothing
break;
}

tegRef.current.update();
setNewName("");
setShowInput(false);
}
};
const handleCancelInput = () => {
setNewName("");
setShowInput(false);
};

const handleKeyDown = (event) => {
if (event.key === "Escape") {
handleCancelInput();
}
};
const handleElementUpdateBlur = () => {
setBlurTimeout(setTimeout(() => setSelectedElement(null), 100));
};

const handleElementUpdateKeyDown = (event) => {
if (event.key === "Escape") {
setSelectedElement(null);
}
};

useEffect(() => {
if (canvasRef.current) {
function tools(ref) {
//let startNode;
const svgPoint = ref.svg.node().createSVGPoint();

let arrow;
if (currentTool === "connect") {
ref.simulation.stop();
ref.animate = false;
}

function dragstarted(event, d) {
if (currentTool === "connect") {
ref.simulation.stop();
//startNode = d;
arrow = ref.svg
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowend)");
} else {
if (!event.active) {
ref.tickCount = 0;
ref.simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
}
}

function dragged(event, d) {
//console.log(arrow, currentTool === "connect", "dragged");
if (currentTool === "connect" && arrow) {
var x = event.x;
var y = event.y;
// Remove highlight class from all elements
d3.selectAll("g").classed("highlight", false);
let endNode = d3
.selectAll("g") // selecting both circles (places) and rectangles (transitions)
.filter((node) => node !== d)
.filter(function (node) {
const bbox = this.getBBox();
var res =
x >= bbox.x + node.x &&
x <= bbox.x + node.x + bbox.width &&
y >= bbox.y + node.y &&
y <= bbox.y + node.y + bbox.height;
return res;
});
endNode.classed("highlight", true);
console.log("hightlight added");
arrow
.attr("x1", d.x)
.attr("y1", d.y)
.attr("x2", event.x)
.attr("y2", event.y);
} else {
ref.tickCount = 0;
d.fx = event.x;
d.fy = event.y;
}
}

function dragended(event, d) {
if (currentTool === "connect" && arrow) {
var x = event.x;
var y = event.y;
// Remove highlight class from all elements
d3.selectAll("g").classed("highlight", false);
let endNode2 = d3
.selectAll("g") // selecting both circles (places) and rectangles (transitions)
.filter((node) => node !== d)
.each(function (node) {
// console.log("for all nides", node, this);
})
.filter(function (node) {
const bbox = this.getBBox();
var res =
x >= bbox.x + node.x &&
x <= bbox.x + node.x + bbox.width &&
y >= bbox.y + node.y &&
y <= bbox.y + node.y + bbox.height;
// console.log(res, node, bbox, "WHAt???", event, x, y);
return res;
});
arrow.remove();
if (endNode2.node()) {
var endNode = endNode2.datum();
if (d !== endNode) {
//what && d.type !== endNode.type
tegRef.current.addArc(new Arc(d, endNode));
tegRef.current.update();
}
} else {
console.log("NO ENDNODE found!!!", endNode2);
}

//console.log("CONNECT", d, endNode);

arrow = null;
} else {
ref.tickCount = 0;
if (!event.active) ref.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
ref.svg.on("click", (event) => onClick(event, tegRef.current));
ref.svg.on("dblclick", (event) => onDoubleClick(event));
return { dragstarted, dragged, dragended };
}
//Thanks to d3 we have to recreate the whole graph because it's so hard to unset drag operators :/
drawTimedEventGraph(tegRef.current, canvasRef.current, tools);
}
}, [tegRef.current, currentTool]);
const onDoubleClick = (event) => {
// Clear the single click timeout to prevent it from executing
if (singleClickTimeout) {
clearTimeout(singleClickTimeout);
setSingleClickTimeout(null);
}
// Handle double-click event logic here
console.log("Double-click detected", currentTool);
switch (currentTool) {
case "cursor":
//console.log(event.target.__data__, tegRef, "AYY");
break;
case "transition":
break;
case "place":
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
default:
// Handle other cases or do nothing
break;
}
};

const onClick = (event, tegRef) => {
// If there's already a single click timeout set, clear it
if (singleClickTimeout) {
clearTimeout(singleClickTimeout);
setSingleClickTimeout(null);
}

const [x, y] = d3.pointer(event);
let elementData;
switch (currentTool) {
case "delete":
elementData = event.target.__data__;
tegRef.removeElement(elementData);
tegRef.update();

case "edit":
// Set a new single click timeout
// setSingleClickTimeout(
// setTimeout(() => {
elementData = event.target.__data__;
if (elementData) {
setInputPosition({ x, y });
if (elementData instanceof Place || elementData instanceof Transition)
setSelectedElement({
elementData,
x,
y,
id: elementData.id,
holdingTime: elementData.holdingTime
});
} else {
console.log("nothing to edit");
}
// }, 300)
// );
break;

case "transition":
case "place":
setInputPosition({ x, y });
setShowInput(true);
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
case "cursor":
tegRef.fireTransition(event.target.__data__);
highlightTransition(event.target);

break;
default:
// Handle other cases or do nothing
break;
}
};
function highlightTransition(transitionElement) {
d3.select(transitionElement).classed("highlight-transition", true);
setTimeout(() => {
d3.select(transitionElement).classed("highlight-transition", false);
}, 100); // 1000ms or 1 second highlight duration
}
console.log("selectedelement", selectedElement);
return jsx`
<div ref=${canvasRef}>
${
showInput &&
jsx`
<form
onSubmit=${handlePlaceNameSubmit}
style=${{
position: "absolute",
left: `${inputPosition.x}px`,
top: `${inputPosition.y}px`
}}
>
<input
type="text"
autoFocus
value=${newName}
onChange=${handleNameChange}
onBlur=${handleCancelInput}
onKeyDown=${handleKeyDown}
placeholder="Enter ${currentTool} name"
/>
</form>`
}
${
selectedElement &&
jsx`
<form
onSubmit=${handleElementUpdate}
onKeyDown=${handleElementUpdateKeyDown}
style=${{
position: "absolute",
left: `${inputPosition.x}px`,
top: `${inputPosition.y}px`
}}
>
<input
type="text"
value=${selectedElement.id}
onChange=${handleElementNameChange}
onBlur=${handleElementUpdateBlur}
onFocus=${clearBlurTimeout}
onKeyDown=${handleElementUpdateKeyDown}
placeholder="Enter name"
autoFocus
/>
<input
type="number"
step="0.01"
value=${selectedElement.holdingTime[tegRef.current.mode]}
onChange=${handleElementHoldingTimeChange}
onBlur=${handleElementUpdateBlur}
onFocus=${clearBlurTimeout}
onKeyDown=${handleElementUpdateKeyDown}
placeholder="Enter holding time"
/>
<button type="submit">Update</button>
</form>`
}
</div>
`;
}
Insert cell
fullscreen = htl.html`<button onclick=${({ currentTarget }) => {
const currentCell = currentTarget.parentElement;
const nextCell = currentCell.nextElementSibling;
nextCell.requestFullscreen
? nextCell.requestFullscreen()
: nextCell.webkitRequestFullscreen
? nextCell.webkitRequestFullscreen()
: () => {
throw new Error("Fullscreen API not supported");
};
}}>Fullscreen</button>`
Insert cell
render((_) => jsx`<${TimedEventGraphComponent} />`)
Insert cell
/*{
// Draw the timed event graph using D3.js
let div = html`<div><div>`;
drawTimedEventGraph(timedEventGraph, div);
return div;
}*/
Insert cell
timedEventGraph
Insert cell
//timedEventGraph.fireTransition(timedEventGraph.transitions[1])
Insert cell
{
//debugger;
// timedEventGraph.addPlace(new timedEventGraph._inner.Place("p3", 1));
var t0 = timedEventGraph.fromId("T1");
timedEventGraph.fireTransition(t0);
}
Insert cell
class Place {
/**
* Creates a Place instance
* @param {string} id - ID of the Place
* @param {Object<string, number>} holdingTime - Holding time per mode
* @param {number} [x] - X coordinate
* @param {number} [y] - Y coordinate
*/
constructor(id, holdingTime = {}, tokens = 0, x = undefined, y = undefined) {
this.id = id;
this.holdingTime = holdingTime;
this.tokens = tokens;
this.x = x;
this.y = y;
}
}
Insert cell
class Transition {
/**
* Creates a Transition instance
* @param {string} id - ID of the Transition
* @param {Object<string, number>} holdingTime - Holding time per mode
* @param {number} [x] - X coordinate
* @param {number} [y] - Y coordinate
*/
constructor(id, holdingTime = {}, x = undefined, y = undefined) {
this.id = id;
this.holdingTime = holdingTime;
this.x = x;
this.y = y;
}
}
Insert cell
class Arc {
constructor(source, target) {
this.source = source;
this.target = target;
}
}
Insert cell
timedEventGraph
Insert cell
parseGDMatrix(`[0,12]=(g1.d0)
[0,23]=(g1.d6)`)
Insert cell
timedEventGraph = {
const timedEventGraph = new TimedEventGraph();
timedEventGraph._inner = { Arc, Place, Transition };
createTEG(
parseGDMatrix(`[0,12]=(g1.d0)
[0,23]=(g1.d6)
[1,0]=(g0.d1)
[2,1]=(g0.d10)
[5,2]=(g0.d3)
[11,23]=(g1.d0)`),
timedEventGraph
);
console.log("its time", timedEventGraph.places[0].tokens);

/* const place1 = new Place("P1", {a:5});
const place2 = new Place("P2", {a:0});
const transition1 = new Transition("T1", {a:0});
const input = new Transition("T0", {a: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
/**
* Parses the file content and returns a data object.
* @param {string} fileContent - The content of the file to parse.
* @returns {Object.<number, Object.<number, {g: number, d: number}>>} - The parsed data object with nested numeric keys.
*/
function parseGDMatrix(fileContent) {
const rows = fileContent.trim().split("\n");
const data = {};

for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const [coord, value] = row.split("=");
const [r, c] = coord
.replace(/[\[\]]/g, "")
.split(",")
.map((x) => parseInt(x));

// Split the value string at 'd' and 'g', and convert the parts to number.
const g = parseInt(value.match(/g(\d+)/)[1]);
const d = parseInt(value.match(/d(\d+)/)[1]);

if (!(r in data)) {
data[r] = {};
}

data[r][c] = { g: g, d: d };
}

return data;
}
Insert cell
/**
* Creates a TimedEventGraph from a parsed matrix.
* @param {Object.<number, Object.<number, {g: number, d: number}>>} matrix - The parsed data object with nested numeric keys.
* @param {TimedEventGraph} teg - The TimedEventGraph to populate.
*/
function createTEG(matrix, teg) {
// Keep track of created transitions and places
const transitions = {};
const places = {};

// Iterate over all elements in the matrix
for (const [t1, targets] of Object.entries(matrix)) {
for (const [t2, gd] of Object.entries(targets)) {
// Create transitions if they don't exist
if (!transitions[t1]) {
transitions[t1] = new Transition(`T${t1}`, { a: 0 });
teg.addTransition(transitions[t1]);
}
if (!transitions[t2]) {
transitions[t2] = new Transition(`T${t2}`, { a: 0 });
teg.addTransition(transitions[t2]);
}

// Create the intermediate place
const placeID = `P${t1}-${t2}`;
if (!places[placeID]) {
console.log("gd", gd);
places[placeID] = new Place(placeID, { a: gd.d }, gd.g);
console.log(places[placeID]);
teg.addPlace(places[placeID]);
}

// Add arcs from T2 to Place and from Place to T1
teg.addArc(new Arc(transitions[t2], places[placeID]));
teg.addArc(new Arc(places[placeID], transitions[t1]));
}
}
}
Insert cell
Insert cell
tools=function(ref){
function dragstarted(event, d) {
if (!event.active) {
ref.tickCount = 0;
ref.simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
}

function dragged(event, d) {
ref.tickCount = 0;
d.fx = event.x;
d.fy = event.y;
}

function dragended(event, d) {
ref.tickCount = 0;
if (!event.active) ref.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
return {dragstarted,dragged,dragended}
}
Insert cell
function drawTimedEventGraph(timedEventGraph, containerSelector, tools) {
//const width = 800;
const height = 600;
const nodeRadius = 20;
const container = d3.select(containerSelector);

// Check if an SVG container already exists
let svg2 = container.select("svg");

// Create or select the SVG
if (svg2.empty()) {
svg2 = container.append("svg").attr("width", width).attr("height", height);
}

// Create or select the 'g' element
let svg = svg2.select("g");
if (svg.empty()) {
svg = svg2.append("g");
} else {
// If an SVG container exists, clear its contents
svg.selectAll("*").remove();
}
svg2.call(
d3
.zoom()
.scaleExtent([0.1, 4])
.filter(function (event) {
// Allow panning (dragging) and zooming only when the Ctrl key is pressed
return (
["wheel", "mousedown", "mousemove", "mouseup"].indexOf(event.type) >=
0 && event.ctrlKey
);
})
.on("zoom", zoomed)
);
// svg2.call(fullscreen);
function zoomed(event) {
// Apply the transform to the 'g' element
svg.attr("transform", event.transform);
}
// console.log("svg what is this??", svg);

let nodes = [...timedEventGraph.places, ...timedEventGraph.transitions];
let links = timedEventGraph.arcs[timedEventGraph.mode];
let tickCount2 = 0;
const maxTicks = 200;

let simulation = d3
.forceSimulation(nodes)
.force("charge", d3.forceManyBody().strength(-200))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("link", d3.forceLink(links).distance(100).strength(0.3))
.force("collide", d3.forceCollide(nodeRadius * 3));
var changeable = {
simulation,
tickCount: tickCount2,
svg: svg2,
animate: true
};
simulation.restart();
let toolf = tools(changeable);
var link = applyLink(svg.selectAll(".link"));
let place = applyPlace(svg.selectAll(".place"));
function applyLink(link) {
// console.log("links", links);
return link
.data(links)
.join("line")
.attr("class", "link")
.attr("marker-end", "url(#arrowhead)");
}

function applyPlace(place) {
return place
.data(timedEventGraph.places)
.join("g")
.attr("class", "place")
.call(
d3
.drag()
.on("start", toolf.dragstarted)
.on("drag", toolf.dragged)
.on("end", toolf.dragended)
);
}

function applyTransition(transition) {
return transition
.data(timedEventGraph.transitions)
.join("g")
.attr("class", "transition-container")
.call(
d3
.drag()
.on("start", toolf.dragstarted)
.on("drag", toolf.dragged)
.on("end", toolf.dragended)
);
}

place
.append("circle")
.attr("class", "outer-circle")
.attr("r", nodeRadius * 1.3)
.style("display", (d) =>
d.holdingTime[timedEventGraph.mode] ? "block" : "none"
);
place
.append("circle")
.attr("class", "inner-circle")
.attr("r", nodeRadius)
.style("display", (d) => "block");
place
.append("text")
.attr("class", "label")
.text((d) => d.id)
.attr("text-anchor", "middle")
.attr("dy", 1.8 * nodeRadius + 10); // Adjust this value to position the label below the place element

let transitionheight = nodeRadius * 4;
// Create transition elements
let transition = applyTransition(svg.selectAll(".transition"));
transition
.append("text")
.attr("class", "label")
.text((d) => d.id)
.attr("text-anchor", "middle")
.attr("dy", +0.6 * transitionheight + 10);

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 = applyLabel(svg.selectAll(".label"));

// console.log("before setUpdateCallback", timedEventGraph);
timedEventGraph.setUpdateCallback(updateVisualization);
updateVisualization();
function updateVisualization() {
// Update nodes and links data
nodes = [...timedEventGraph.places, ...timedEventGraph.transitions];
links = timedEventGraph.arcs[timedEventGraph.mode];

// Update the simulation's nodes and links
changeable.simulation.nodes(nodes);
changeable.simulation.force("link").links(links);
changeable.simulation.restart();
if (changeable.animate) {
// Restart the simulation
changeable.tickCount = 0;
} else {
changeable.tickCount = maxTicks - 2; //it needs to animate at least a bit
}

// Update the visualization elements
link = applyLink(link);

place = applyPlace(place);
place
.selectAll(".outer-circle")
.data((d) => [d])
.join("circle")
.attr("class", "outer-circle")
.attr("r", nodeRadius * 1.3)
.style("display", (d) =>
d.holdingTime[timedEventGraph.mode] ? "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 = applyTransition(transition);
transition
.selectAll("rect")
.data((d) => [d])
.join("rect")
.attr("class", (d) =>
d.holdingTime[timedEventGraph.mode] ? "transition" : "transition-filled"
)
.attr("width", nodeRadius)
.attr("height", transitionheight)
.attr("rx", 3)
.attr("ry", 3)
.attr("x", -nodeRadius / 2)
.attr("y", -transitionheight / 2);
place
.selectAll(".label")
.data((d) => [d])
.text((d) => d.id);

transition
.selectAll(".label")
.data((d) => [d])
.text((d) => d.id);

// label = applyLabel(label);
}

function ticked() {
if (changeable.tickCount >= maxTicks) {
changeable.simulation.stop();
} else {
changeable.tickCount++;
}
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("transform", (d) => `translate(${d.x}, ${d.y})`);

// label.attr("x", (d) => d.x).attr("y", (d) => d.y - transitionheight);
}
changeable.simulation.on("tick", ticked);
}
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