UNPKG

lively.lang

Version:

JavaScript utils providing useful abstractions for working with collections, functions, objects.

374 lines (297 loc) 10.9 kB
import { num, string, arr, fun } from "lively.lang"; class CanvasElement { get left() { return this.x; } set left(val) { this.x = val; } get top() { return this.y; } set top(val) { return this.y = val; } get centerX() { return this.x + Math.floor(this.width/2); } set centerX(x) { this.x = x - Math.floor(this.width/2); } get centerY() { return this.y + Math.floor(this.height/2); } set centerY(y) { this.y = y - Math.floor(this.height/2); } get center() { return [this.centerX, this.centerY]; } set center([x, y]) { this.centerX = x; this.centerY = y; } get right() { return this.x + this.width; } set right(val) { this.x = val - this.width; } get bottom() { return this.y + this.height; } set bottom(val) { this.y = val - this.height; } get topLeft() { return [this.left, this.top]; } set topLeft([x,y]) { this.left = x; this.top = y; } get topRight() { return [this.right, this.top]; } set topRight([x,y]) { this.right = x; this.top = y; } get bottomRight() { return [this.right, this.bottom]; } set bottomRight([x,y]) { this.right = x; this.bottom = y; } get bottomLeft() { return [this.left, this.bottom]; } set bottomLeft([x,y]) { this.left = x; this.bottom = y; } get extent() { return [this.width, this.height]; } set extent([w,h]) { this.width = w; this.height = h; } print() { var cvs = new Canvas(), {x,y} = this; try { this.x = this.y = 0; cvs.objects = [this]; cvs.draw() var printed = cvs.print(); } finally { this.x = x; this.y = y; } return printed; } } class Text extends CanvasElement { constructor(x, y, text) { this.x = x; this.y = y; this.text = text; } get width() { return Math.max(...arr.pluck(this.text.split("\n"), "length")); } get height() { return this.text.split("\n").length; } draw(canvas) { var {x, y, text} = this; text.split("\n").forEach((line, j) => line.split("").forEach((ch, i) => canvas.put(x+i, y+j, ch))); } } class Rectangle extends CanvasElement { constructor(x, y, width, height, borderChar, fillChar) { this.x = x; this.y = y; this.width = width; this.height = height; this.borderChar = borderChar; this.fillChar = fillChar; } draw(canvas) { var {x, y, width, height, borderChar, fillChar} = this; for (var j = y; j <= y + height; j++) { for (var i = x; i <= x + width; i++) { var onBorder = i === x || i === x + width || j === y || j === y + height; var ch = onBorder ? borderChar : fillChar; if (ch) canvas.put(i, j, ch); } } } } class Line extends CanvasElement { constructor(x, y, toX, toY, char) { this.x = x; this.y = y; this.toX = toX; this.toY = toY; this.char = char; } get width() { return Math.abs(this.toX - this.x); } get height() { return Math.abs(this.toY - this.y); } draw(canvas) { var {x: fromX, y: fromY, toX, toY, char} = this; var deltaX = toX - fromX, deltaY = toY - fromY, r = Math.sqrt(deltaX**2 + deltaY**2), normX = num.roundTo(deltaX / r, .01), normY = num.roundTo(deltaY / r, .01), x = fromX, y = fromY; var counter = 0; var prevDist = Infinity; while (true) { if (counter++ > 10000) throw "stop"; var dx = x - toX, dy = y - toY; var dist = Math.sqrt(dx * dx + dy * dy); // if (dist <= 1) break; if (dist > prevDist) break; prevDist = dist; // console.log(dist) // if (num.between(x, toX - 1, toX + 1) || num.between(y, toY - 1, toY + 1)) // break; canvas.put(Math.round(x), Math.round(y), char); x += normX; y += normY; } } } class Connector extends CanvasElement { constructor(objA, objB, char) { this.objA = objA; this.objB = objB; this.char = char; } get x() { return this.objA.center[0]; } get y() { return this.objA.center[1]; } get toX() { return this.objB.center[0]; } get toY() { return this.objB.center[1]; } get width() { return Math.abs(this.toX - this.x); } get height() { return Math.abs(this.toY - this.y); } draw(canvas) { Line.prototype.draw.call(this, canvas); } } class CanvasTreeNode extends CanvasElement { constructor(canvasEl, padding = [1,1,1,1], children = []) { [this.paddingLeft, this.paddingTop, this.paddingRight, this.paddingBottom] = padding; this.canvasElement = canvasEl; this.x = 0; this.y = 0; this.depth = 0; this.children = children; this.parent = null; } get width() { return this.canvasElement.width + this.paddingLeft + this.paddingRight; } get height() { return this.canvasElement.height + this.paddingTop + this.paddingBottom; } draw(canvas) { this.children.forEach(ea => new Connector(this, ea, "•").draw(canvas)); var text = ""; if (this.paddingTop) text += "\n".repeat(this.paddingTop) var lines = this.canvasElement.print().split("\n"); lines.forEach(line => { if (this.paddingLeft) text += " ".repeat(this.paddingLeft) text += line; if (this.paddingRight) text += " ".repeat(this.paddingRight) text += "\n" }); if (this.paddingBottom) text += "\n".repeat(this.paddingBottom) return new Text(this.x, this.y, text).draw(canvas); } } class CanvasTree extends CanvasElement { constructor(x, y, tree, nodePrinter, childGetter, options = {}) { // nodePrinter: (TreeNode) => String|CanvasElement // childGetter: (TreeNode) => [TreeNode]|null this.x = x; this.y = y; this.nodePrinter = nodePrinter; this.childGetter = childGetter; this.tree = tree; this.padding = options.padding || [2, 2, 2, 2]; // left, top, right, bottom } visit(node, iterator, parent = null, depth = 0) { iterator(node, parent, depth); (node.children || []).forEach(child => this.visit(child, iterator, node, depth+1)); } printNodes() { var canvasTree = lively.lang.tree.mapTree(this.tree, (node, mappedChildren) => { var canvasEl = this.nodePrinter(node); if (typeof canvasEl === "string") canvasEl = new Text(0, 0, canvasEl); if (!(canvasEl instanceof CanvasElement)) throw new Error(`Cannot convert node ${node} to cavnas element`);; var canvasNode = new CanvasTreeNode(canvasEl, this.padding, mappedChildren) mappedChildren.forEach(ea => ea.parent = canvasNode); return canvasNode; }, this.childGetter); var canvasNodes = []; this.visit(canvasTree, (ea, _, depth) => { ea.depth = depth; canvasNodes.push(ea) }); // canvasNodes.map(ea => ea.canvasElement.print()) // canvasNodes.map(ea => ea.width) // canvasNodes[0].canvasElement.width // canvasNodes[0].width var height = 0; arr.groupBy(canvasNodes, ({depth}) => depth).toArray().forEach(treeLevel => { var width = 0, maxHeight = 1; treeLevel.forEach((treeObj) => { var name = treeObj.name; var parentX = treeObj.parent ? treeObj.parent.x : 0; treeObj.x = Math.max(width, parentX); treeObj.y = height; width = treeObj.x + treeObj.width; maxHeight = Math.max(maxHeight, treeObj.height); return treeObj; }); height += maxHeight; }) // while (true) { for (var i = 0; i < 100; i++) { var changed = false; arr.groupBy(canvasNodes, ({depth}) => depth).toArray().forEach(treeLevel => { treeLevel.forEach((canvasNode,i) => { var children = canvasNode.children; if (!children || !children.length) return; var left = children[0].left, right = arr.last(children).right, centerX = Math.floor(left + (right - left) / 2); // var right = arr.last(children).right; // console.log(children[0].left) if (canvasNode.centerX >= centerX) return; var delta = centerX - canvasNode.centerX; // if (canvasNode.right >= right) return; // var delta = right - canvasNode.right; canvasNode.centerX += delta;; // canvasNode.right += delta;; treeLevel.slice(i+1).forEach(ea => ea.left += delta); changed = true; }); }) if (!changed) break; } var cvs = new Canvas() cvs.objects = canvasNodes; cvs.draw() return cvs.print(); } draw(canvas) { return new Text(this.x, this.y, this.printNodes()).draw(canvas); } } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- export default class Canvas { constructor(args) { this.objects = []; this.clear(); } clear() { this.rows = []; } draw() { this.objects.forEach(ea => ea.draw(this)); } put(x, y, ch) { var row = this.rows[y] || (this.rows[y] = []); row[x] = ch; } print() { var maxCol = this.rows.reduce((maxCol, row) => Math.max((row || []).length, maxCol), 0), printed = ""; for (var j = 0; j < this.rows.length; j++) { var row = this.rows[j] || (this.rows[j] = []); for (var i = 0; i < row.length; i++) row[i] || (row[i] = " "); var line = row.join(""); printed += string.pad(line, maxCol - line.length, false) + "\n"; } return printed; } } export function printTree(tree, nodePrinter, childGetter, options) { return new CanvasTree(0, 0, tree, nodePrinter, childGetter, options).print(); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // printTree($$world, morph => String(morph), morph => morph.submorphs), {padding: [2,4,2,4]} var cvs = new Canvas() cvs.objects = [ new Rectangle(2, 3, 10,20, "-"), ...arr.range(4, 22).map(n => new Text(3, n, " " + n + " fooo")) ] cvs.objects[0].width = 23 cvs.draw() console.log(cvs.print()) var tree = {name: "foo", children: [ {name: "zork", children: [{name: "oink"}]}, {name: new Rectangle(0,0, 10, 4, "x"), children: [ {name: "zork", children: [{name: "oink"}]}, {name: "baz", children: [ {name: " ----\n|zork|\n ----", children: [{name: "oink"}]}, {name: "baz", children: [ {name: "zork", children: [{name: "oink"}]}, {name: "baz", children: [ {name: "zork", children: [{name: "oink"}]}, {name: "baz"} ]} ]} ]} ]} ]} console.log(printTree(tree, node => node.name, ({children}) => children || [])) // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- var a = new Rectangle(2, 3, 10, 2, "-") var b = new Rectangle(4, 9, 3, 2, "-") var c = new Rectangle(30, 4, 3, 2, "-") var cvs = new Canvas() cvs.objects = [new Connector(a, b, "+"), new Connector(a, c, "."), new Connector(b, c, "•"), a, b, c]; cvs.draw() console.log(cvs.print())