xxxxxxxxxx
408
class Node {
constructor(value) {
this.value = value;
this.x = 0;
this.y = 0;
this.w = 20;
this.h = 20;
this.parent = null;
this.children = [];
}
draw() {
noFill();
stroke(255);
ellipse(this.x, this.y, this.w, this.w);
}
getWidth() {
return this.w;
}
}
function getChildrenAverageX(children) {
let avgX = 0;
for (let child of children) {
avgX += child.x;
}
avgX = avgX / children.length;
return avgX;
}
function getLeaves(root) {
if (!root) return [];
// If the node is a leaf, return it as a single-element array
if (root.children.length === 0) {
return [[root.value]];
}
// If the node has children, recurse and collect leaves
let result = [];
for (let child of root.children) {
let childLeaves = getLeaves(child);
// If the child is not a leaf, add its result directly
if (child.children.length > 0) {
result = result.concat(childLeaves);
} else {
// If the child is a leaf, add it to the last subarray or create a new one
if (result.length > 0 && result[result.length - 1].length > 0) {
result[result.length - 1].push(child);
} else {
result.push([child]);
}
}
}
return result;
}
// get the nodes from bottom to top
// written by Claude 3.5 Sonnet!
const leafToRootBFS = (root) => {
if (!root) return [];
let queue = [root];
let levels = [];
while (queue.length) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node);
queue.push(node.children);
}
levels.unshift(currentLevel);
}
return levels;
};
class Tree {
constructor(root) {
this.root = root;
this.leaves = [];
}
// build a tree from a tabbed string
// written by ChatGPT4-o. Claude failed!
// static generateFromString(inputString) {
// if (!inputString) return new Tree();
// const lines = inputString.split("\n").filter((line) => line.trim() !== "");
// const root = new Node(lines[0].trim());
// const nodeStack = [root];
// for (let i = 1; i < lines.length; i++) {
// const line = lines[i];
// const trimmedLine = line.trim();
// const level = line.lastIndexOf("\t") + 1;
// const newNode = new Node(trimmedLine);
// // Find the parent node from the stack
// while (nodeStack.length > level) {
// nodeStack.pop();
// }
// const parentNode = nodeStack[nodeStack.length - 1];
// parentNode.children.push(newNode);
// newNode.parent = parentNode;
// nodeStack.push(newNode);
// }
// return new Tree(root);
// }
static generateFromString(input) {
const lines = input.split("\n");
const tree = new Tree();
let stack = [];
lines.forEach((line) => {
const level = line.search(/\S/);
const value = line.trim();
const node = new Node(value);
if (level === 0) {
tree.root = node;
stack = [node];
} else {
while (stack.length > level) {
stack.pop();
}
const parent = stack[stack.length - 1];
parent.children.push(node);
node.parent = parent; // Set the parent
stack.push(node);
}
});
return tree;
}
// add a node to the tree
add(parent, value) {
const node = new Node(value);
parent.children.push(node);
return node;
}
// finds the leaf nodes and stores them in this.leaves
computeLeafNodes() {
this.leaves = getLeaves(this.root);
}
// Depth-first traversal (pre-order)
traverseDF(callback) {
function traverse(node) {
callback(node);
node.children.forEach((child) => traverse(child));
}
traverse(this.root);
}
// Breadth-first traversal
traverseBF(callback) {
const queue = [this.root];
while (queue.length > 0) {
const node = queue.shift();
callback(node);
node.children.forEach((child) => queue.push(child));
}
}
// compute the X,Y position of each node.
computePositions() {
// get the nodes from bottom to the top
// because the x-position of parents is
// the avg x-position of their children.
this.computeLeafNodes();
const nodesByLevel = leafToRootBFS(this.root);
const leafNodes = this.leaves;
// print(leafNodes.flat().length)
const neighMargin = NEIGHBOR_MARGIN; // margin between neighbors (not siblings)
const sibMargin = SIBLING_MARGIN; // margin between siblings
let totalLeafCount = leafNodes.reduce(
(acc, curr) => acc + curr.length,
0
);
let totalCommaCount = totalLeafCount - leafNodes.length;
let totalNodeWidths = leafNodes.reduce(
(acc, curr) =>
acc + curr.reduce((subAcc, subCurr) => subAcc + subCurr.w, 0),
0
);
let totalWidth =
totalNodeWidths +
totalCommaCount * sibMargin +
(leafNodes.length - 1) * neighMargin;
// calculate their PosX
let currentX = -totalWidth / 2;
// let currentX = 0;
for (let i = 0; i < leafNodes.length; i++) {
const nodes = leafNodes[i];
for (let j = 0; j < nodes.length; j++) {
const node = nodes[j];
node.x = currentX;
// print(node.value, node.x);
currentX += node.w;
currentX += sibMargin;
}
currentX += neighMargin;
}
// parents
// let queue = [...leafNodes.flat()];
// // print(leafNodes.length)
// let visited = new Set(leafNodes); // To keep track of visited nodes
// while (queue.length > 0) {
// let currentNode = queue.shift();
// print(currentNode.value, currentNode.parent.value)
// if (currentNode.parent) {
// let parent = currentNode.parent;
// // Update the parent's x position
// parent.x = getChildrenAverageX(parent.children);
// // print(parent.value, parent.x)
// // If the parent hasn't been visited yet, add it to the queue and mark it as visited
// if (!visited.has(parent)) {
// // print("not visited")
// queue.push(parent);
// visited.add(parent);
// }
// }
// }
// parents
for (let level in nodesByLevel) {
for (let node of nodesByLevel[level]) {
// print(node.value, node.parent.value)
if(node.children.length>0) {
node.x = getChildrenAverageX(node.children);
}
}
}
// set ys
for (let level in nodesByLevel) {
for (let node of nodesByLevel[level]) {
node.y = level*-30;
}
}
}
// traverse the tree and draw the nodes
draw(shape) {
// this.computePositions();
// Draw the lines
this.traverseDF((node) => {
fill(0);
stroke(255);
if (node.parent) {
line(node.x, node.y, node.parent.x, node.parent.y);
}
});
// Draw the nodes
this.traverseDF((node) => {
fill(0);
stroke(255);
if (shape == "circle") {
ellipse(node.x, node.y, node.w, node.h);
}
if (shape == "rectangle") {
rectMode(CENTER);
rect(node.x, node.y, node.w, node.h, node.w / 10);
}
if (shape == "no-shape") {
}
textAlign(CENTER, CENTER);
fill(255);
noStroke();
text(node.value, node.x, node.y);
});
}
}
const NEIGHBOR_MARGIN = 20;
const SIBLING_MARGIN = 5;
function setup() {
createCanvas(500, 500);
}
let zoom = 1;
let offsetX = 0;
let offsetY = 0;
let startPanX, startPanY;
let isPanning = false;
function draw() {
background(0);
translate(width / 2, height / 2);
// Apply zoom and pan transformations
translate(offsetX, offsetY);
scale(zoom);
// helper axis
const infinity = 10000;
fill("yellow");
circle(0, 0, 5);
noFill();
stroke("rgba(255,0,0,0.6)");
line(-infinity / 2, 0, infinity / 2, 0);
stroke("rgba(0,255,0,0.6)");
line(0, -infinity / 2, 0, infinity / 2);
// grid
// Draw gray grid lines
const gridSpacing = 50;
strokeWeight(1 / zoom);
stroke("rgba(128,128,128,0.55)"); // Light gray color with some transparency
for (let i = -infinity / 2; i < infinity / 2; i += gridSpacing) {
// Vertical lines
line(i, -infinity / 2, i, infinity / 2);
// Horizontal lines
line(-infinity / 2, i, infinity / 2, i);
}
// const inputString = `A
// \tB
// \t\tD
// \t\tE
// \t\tF
// \tC
// \t\tG
// \t\tH`;
const inputString = `A
\tB
\t\tT
\t\tG
\t\t\tX
\t\t\tY
\t\t\t\tV
\t\t\t\t\tM
\t\t\t\t\tC?
\t\t\t\t\tL`;
// const inputString = `A
// \tB
// \tC`;
const tree = Tree.generateFromString(inputString);
tree.root.children[0].children[1].children[1].children[0].children[1].w = 20;
// print("done")
tree.computeLeafNodes();
tree.computePositions();
// print(tree.root)
// print(tree.leaves)
// print(tree);
tree.draw("circle"); // rectangle|circle|no-shape
// Restore transformation matrix
resetMatrix();
// noLoop();
}
// Start panning
function mousePressed() {
startPanX = mouseX;
startPanY = mouseY;
isPanning = true;
}
// Perform panning
function mouseDragged() {
if (isPanning) {
offsetX += mouseX - startPanX;
offsetY += mouseY - startPanY;
startPanX = mouseX;
startPanY = mouseY;
}
}
// Stop panning
function mouseReleased() {
isPanning = false;
}
// Zooming with the mouse wheel
function mouseWheel(event) {
let zoomSensitivity = 0.001;
zoom += event.delta * zoomSensitivity;
zoom = constrain(zoom, 0.1, 10); // Constrain zoom level
}