Last active
October 17, 2017 13:48
-
-
Save corvuscrypto/41fc962f54c99164783807f2f4698fd1 to your computer and use it in GitHub Desktop.
file for the graph visualisation included in one of my blog posts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
I'll let you use my code for manipulation and reposting on the condition that you listen to my tale. | |
FADE IN: | |
EXT. UNNAMED CITY - DAY | |
The hustle and bustle is a symphony of progress. | |
We pan past windows, each of which contain a different story, | |
to find JACEY LAKIMS, 28... hot, but doesn't know it. | |
Jacey stops when her high heel gets caught in the grating of a sewer. | |
Suddenly, a man steps into frame and points a gun at her. | |
This is not her day. | |
FADE TO BLACK | |
TITLE: Three weeks earlier | |
... | |
INT. UNNAMED BAR - NIGHT | |
(pause for laugh) | |
... | |
BLANE | |
Maybe I don't need a new friend. | |
JACEY | |
Maybe you're the only friend I need. | |
BLANE | |
Need, or want? | |
JACEY | |
I've never been much for wanting. | |
BLANE | |
Spoken like someone with needs. | |
Jacey reaches out and touches his face. It's clear he needs what she wants. | |
She's a woman. He's a man. The city burns in the background as he takes her in his arms. | |
FADE OUT | |
TITLE: The end? | |
*/ | |
// simple color map | |
var COLORS = { | |
unvisited: "#FFFFFF", | |
active: "#FF0000", | |
queued: "#0000FF", | |
done: "#000000", | |
path: "#00FF00", | |
} | |
// naive reverse color map for contrasting text colors | |
var TEXT_COLORS = { | |
"#FFFFFF": "#000000", | |
"#FF0000": "#000000", | |
"#0000FF": "#FFFFFF", | |
"#000000": "#FFFFFF", | |
"#00FF00": "#000000", | |
} | |
// More convenient infinity definition than the float Infinity | |
const INF = 1<<30; | |
// Node types to use as artificial weights (Currently unused) | |
const TYPE_SLOW = 0; | |
const TYPE_MEDIUM = 1; | |
const TYPE_FAST = 3; | |
// Some other needed constants | |
const GRAPH_SIZE = 7; // Size is in number of nodes across | |
const PAUSE_TIME = 1000; // Time to wait (in ms) between each step | |
const NODE_RADIUS = 12; | |
/** | |
* Queue represents a normal FIFO queue without any shuffling | |
*/ | |
function Queue() { | |
this.q = []; | |
} | |
Queue.prototype.enqueue = function(node) { | |
this.q.push(node); | |
} | |
Queue.prototype.dequeue = function() { | |
return this.q.shift(); | |
} | |
/** | |
* NaivePriorityQueue is a naive implementation of a queue with priority shuffling. | |
* Speed isn't important here, but if you're into it, just use a min-priority heap instead | |
*/ | |
function NaivePriorityQueue() { | |
this.q = []; | |
} | |
NaivePriorityQueue.prototype.sort = function() { | |
this.q.sort(function(a, b){return a[1]-b[1]}); | |
} | |
NaivePriorityQueue.prototype.enqueue = function(node, priority) { | |
this.q.push([node, priority]); | |
} | |
NaivePriorityQueue.prototype.adjust = function(node, priority) { | |
this.q.forEach(function(a){ | |
if (a[0] === node) { | |
a[1] = priority | |
} | |
}) | |
} | |
NaivePriorityQueue.prototype.contains = function(node) { | |
for (var i in this.q) { | |
if (this.q[i][0] === node) { | |
return true; | |
} | |
} | |
return false; | |
} | |
NaivePriorityQueue.prototype.dequeue = function() { | |
this.sort(); | |
return (this.q.shift()||[null])[0]; | |
} | |
/** | |
* Node is the representation of graph connection points. | |
* | |
* Args: | |
* x (int): x coordinate on the graph | |
* y (int): y coordinate on the graph | |
*/ | |
function Node(x, y){ | |
this.x = x; | |
this.y = y; | |
this.children = []; | |
this.visited = false; | |
this.distance = INF; | |
this.type = TYPE_SLOW; | |
this.color = COLORS.unvisited; | |
this.predecessor = null; | |
} | |
/** | |
* Convenience function to calculate euclidian distance to a nother node. | |
* Truncates result to precision of 1e-1. | |
* | |
* Args: | |
* n (Node): another Node object. | |
* | |
* Returns float64 | |
*/ | |
Node.prototype.calculateDistanceTo = function(n) { | |
return Math.floor(Math.sqrt(Math.pow(this.x - n.x, 2) + Math.pow(this.y - n.y, 2)) * 10) / 10; | |
} | |
/** | |
* Interface to satisfy requirements for visualization of graph search algorithms. | |
* | |
* Args: | |
* setupFunction ( ()=>null ): setup function that performs any preparations before initiating the search. | |
* iterativeFunction ( ()=>boolean ): iterative function for performing the iterative steps of the algorithm. | |
* finishFunction ( ()=>boolean ): the path reconstruction function. | |
*/ | |
function GenericSearcher(setupFunction, iterativeFunction, finishFunction){ | |
this.timer = null; | |
this.startNode = null; | |
this.finishNode = null; | |
this.nodes = null; | |
this.path = null; | |
this.setup = setupFunction.bind(this); | |
this.iterate = iterativeFunction.bind(this); | |
this.finish = finishFunction.bind(this); | |
} | |
/** | |
* This is the wrapper that handles running graph search algorithms. | |
* | |
* Args: | |
* nodeList (Array<Node>): list of nodes. | |
* start (Node): starting node. | |
* finish (Node): node we are trying to find. | |
*/ | |
GenericSearcher.prototype.search = function(nodeList, start, finish) { | |
var self = this; | |
clearInterval(this.timer); | |
this.startNode = start; | |
this.finishNode = finish; | |
this.nodes = nodeList; | |
this.setup(); | |
this.timer = setInterval(function(){ | |
if (!self.iterate()) { | |
clearInterval(self.timer); | |
self.timer = setInterval(function(){ | |
if (!self.finish()) { | |
clearInterval(self.timer); | |
} | |
}, 1000); | |
} | |
}, 1000); | |
} | |
var breadthFirstSearch = new GenericSearcher( | |
function (){ | |
this.nodes.forEach(function(n){ | |
n.visited = false; | |
n.color = COLORS.unvisited; | |
n.distance = INF; | |
}) | |
this.startNode.visited = true; | |
this.startNode.distance = 0; | |
var queue = new Queue(); | |
queue.enqueue(this.startNode); | |
this.queue = queue; | |
this.predecessor = null; | |
this.path = null; | |
}, | |
function (){ | |
if (this.predecessor) this.predecessor.color = COLORS.done; | |
var node = this.queue.dequeue(); | |
var self = this; | |
if (node === this.finishNode) { | |
this.finishNode.color = COLORS.path; | |
return false; | |
} | |
node.visited = true; | |
node.color = COLORS.active; | |
node.children.forEach(function(n){ | |
if (n.visited || self.queue.q.indexOf(n) !== -1) return; | |
n.distance = node.distance + 1; | |
n.color = COLORS.queued; | |
self.queue.enqueue(n); | |
}) | |
this.predecessor = node; | |
return true; | |
}, | |
function (){ | |
if (!this.path){ | |
this.path = []; | |
this.path.unshift(this.finishNode); | |
} | |
var node = this.path[0]; | |
var leastDist = INF; | |
var leastDistNode = null | |
node.children.forEach(function(n){ | |
if (n.distance < leastDist) { | |
leastDist = n.distance; | |
leastDistNode = n; | |
} | |
}) | |
leastDistNode.color = COLORS.path | |
this.path.unshift(leastDistNode); | |
return leastDistNode !== this.startNode; | |
} | |
); | |
var dijkstraSearch = new GenericSearcher( | |
function (){ | |
var self = this; | |
this.nodes.forEach(function(n){ | |
n.visited = false; | |
n.color = COLORS.unvisited; | |
n.distance = INF; | |
}) | |
this.startNode.visited = true; | |
this.startNode.distance = 0; | |
var queue = new NaivePriorityQueue(); | |
queue.enqueue(this.startNode, 0); | |
this.queue = queue; | |
this.predecessor = null; | |
this.path = null; | |
}, | |
function (){ | |
var node = this.queue.dequeue(); | |
if (!node) return false; | |
if (this.predecessor) this.predecessor.color = COLORS.done; | |
node.visited = true; | |
node.color = COLORS.active; | |
if (node === this.finishNode) { | |
return false; | |
} | |
var self = this; | |
node.children.forEach(function(n){ | |
var distance = node.distance + node.calculateDistanceTo(n); | |
distance = Math.round(distance * 10) / 10; | |
if (distance < n.distance){ | |
n.distance = distance; | |
n.predecessor = node | |
if (self.queue.contains(n)){ | |
self.queue.adjust(n, n.distance) | |
} else { | |
n.color = COLORS.queued; | |
self.queue.enqueue(n, n.distance) | |
} | |
} | |
}) | |
this.predecessor = node; | |
return true; | |
}, | |
function (){ | |
if (!this.path){ | |
this.path = []; | |
this.path.unshift(this.finishNode); | |
} | |
var node = this.path[0]; | |
node.color = COLORS.path | |
this.path.unshift(node.predecessor); | |
return node !== this.startNode; | |
} | |
); | |
var astarSearch = new GenericSearcher( | |
function (){ | |
var self = this; | |
this.nodes.forEach(function(n){ | |
n.visited = false; | |
n.color = COLORS.unvisited; | |
n.distance = INF; | |
}) | |
this.startNode.visited = true; | |
this.startNode.distance = 0; | |
var queue = new NaivePriorityQueue(); | |
queue.enqueue(this.startNode, 0); | |
this.queue = queue; | |
this.predecessor = null; | |
this.path = null; | |
}, | |
function (){ | |
var node = this.queue.dequeue(); | |
if (!node) return false; | |
if (this.predecessor) this.predecessor.color = COLORS.done; | |
node.visited = true; | |
node.color = COLORS.active; | |
if (node === this.finishNode) { | |
return false; | |
} | |
var self = this; | |
node.children.forEach(function(n){ | |
var distance = node.distance + node.calculateDistanceTo(n); | |
distance = Math.round(distance * 10) / 10; // truncating needed due to float addition | |
// inb4 "eval is bad" | |
var fscore = distance + function(node, n, finishNode){let result = 0; eval(document.getElementById("astarHeuristic").value); return result}(node, n, self.finishNode); | |
fscore = Math.round(fscore * 10) / 10; | |
if (distance < n.distance){ | |
n.distance = distance; | |
n.predecessor = node | |
if (self.queue.contains(n)){ | |
self.queue.adjust(n, fscore) | |
} else { | |
n.color = COLORS.queued; | |
self.queue.enqueue(n, fscore) | |
} | |
} | |
}) | |
this.predecessor = node; | |
return true; | |
}, | |
function (){ | |
if (!this.path){ | |
if (!this.finishNode.predecessor) return false; | |
this.path = []; | |
this.path.unshift(this.finishNode); | |
} | |
var node = this.path[0]; | |
node.color = COLORS.path | |
this.path.unshift(node.predecessor); | |
return node !== this.startNode; | |
} | |
); | |
/** | |
* GraphCreator is the object representation of the user interface that allows | |
* creation of arbitrary graphs for searching. Pretty quickly hacked together. Probably best | |
* just to avoid the brain hurt and not worry about this. | |
* | |
* Args: | |
* element (HTMLCanvasElement): the canvas element which is to be turned into the graph creation interface. | |
*/ | |
function GraphCreator(element){ | |
var self = this; | |
this.canvas = element; | |
this.ctx = element.getContext('2d'); | |
element.onmousemove = function(e){ | |
self.mouseIn = true; | |
self.mouseX = e.layerX; | |
self.mouseY = e.layerY; | |
} | |
element.onmouseout = function(e){ | |
self.mouseIn = false; | |
} | |
element.onclick = this.clickHandler.bind(this) | |
this.editState = GraphCreator.STATE_NO_EDIT; | |
document.getElementById("btnAddNodes").onclick = function(){ | |
self.editState = GraphCreator.STATE_ADD_NODE | |
} | |
document.getElementById("btnAddConnections").onclick = function(){ | |
self.editState = GraphCreator.STATE_ADD_CONNECTION | |
self.partialConnectionStart = null; | |
} | |
document.getElementById("btnReset").onclick = function(){ | |
self.editState = 0 | |
self.partialConnectionStart = null; | |
self.nodeMap = {} | |
self.recountNodes(); | |
self.adjustSelects(); | |
} | |
this.partialConnectionStart = null; | |
this.nodeSeparation = (this.canvas.width - (2 * NODE_RADIUS * GRAPH_SIZE)) / (GRAPH_SIZE - 2) | |
this.nodeMap = {} | |
this.startNode = 0; | |
this.finishNode = 0; | |
var startSearch = function(searcher){ | |
var nodes = [] | |
var startNode = null; | |
var finishNode = null; | |
for (var k in self.nodeMap) { | |
var node = self.nodeMap[k]; | |
nodes.push(self.nodeMap[k]) | |
if (node.label == self.startNode) startNode = node; | |
if (node.label == self.finishNode) finishNode = node; | |
} | |
searcher.search(nodes, startNode, finishNode) | |
} | |
document.getElementById("selectStartNode").onchange = function(){ | |
self.startNode = this.value | |
} | |
document.getElementById("selectFinishNode").onchange = function(){ | |
self.finishNode = this.value | |
} | |
document.getElementById("btnRunBFS").onclick = function(){ | |
startSearch(breadthFirstSearch); | |
} | |
document.getElementById("btnRunDijkstra").onclick = function(){ | |
startSearch(dijkstraSearch); | |
} | |
document.getElementById("btnRunAStar").onclick = function(){ | |
startSearch(astarSearch); | |
} | |
self.showLabels = true; | |
self.showEdgeDist = true; | |
self.showNodeDist = true; | |
document.getElementById("checkShowLabels").onchange = function(){ | |
self.showLabels = this.checked; | |
} | |
document.getElementById("checkShowEdgeDist").onchange = function(){ | |
self.showEdgeDist = this.checked; | |
} | |
document.getElementById("checkShowNodeDist").onchange = function(){ | |
self.showNodeDist = this.checked; | |
} | |
this.drawCreate(); | |
} | |
// GraphCreator constants | |
GraphCreator.STATE_NO_EDIT = 0 | |
GraphCreator.STATE_ADD_NODE = 1 | |
GraphCreator.STATE_ADD_CONNECTION = 2 | |
GraphCreator.STATE_PLACING_CONNECTION = 3 | |
GraphCreator.prototype.clickHandler = function(){ | |
switch (this.editState) { | |
case GraphCreator.STATE_ADD_NODE: | |
this.createNode(); | |
break; | |
case GraphCreator.STATE_ADD_CONNECTION: | |
if (this.connectionCreate()) | |
this.editState = GraphCreator.STATE_PLACING_CONNECTION; | |
break; | |
case GraphCreator.STATE_PLACING_CONNECTION: | |
this.connectionCreate() | |
this.editState = GraphCreator.STATE_ADD_CONNECTION; | |
this.partialConnectionStart = null; | |
break; | |
} | |
this.recountNodes(); | |
this.adjustSelects(); | |
} | |
GraphCreator.prototype.recountNodes = function(){ | |
var i = 0; | |
for (var k in this.nodeMap){ | |
this.nodeMap[k].label = i++; | |
} | |
} | |
GraphCreator.prototype.adjustSelects = function(){ | |
var startSelect = document.getElementById("selectStartNode") | |
var finishSelect = document.getElementById("selectFinishNode") | |
startSelect.innerHTML = "" | |
finishSelect.innerHTML = "" | |
var i = 0; | |
for (var k in this.nodeMap){ | |
var option = new Option(); | |
option.value = i; | |
var option2 = option.cloneNode() | |
option.text = "Node " + i; | |
option2.text = "Node " + i; | |
startSelect.add(option); | |
finishSelect.add(option2); | |
i++ | |
} | |
startSelect.value = this.startNode; | |
finishSelect.value = this.finishNode; | |
} | |
GraphCreator.prototype.drawCreate = function(){ | |
var ctx = this.ctx | |
ctx.clearRect(0, 0, 500, 500) | |
var separation = this.nodeSeparation | |
ctx.lineWidth = 1; | |
for (var i = 0; i < GRAPH_SIZE; i++){ | |
for (var j = 0; j < GRAPH_SIZE; j++){ | |
ctx.beginPath(); | |
ctx.arc((i+0.5) * separation, (j + 0.5) * separation, 3, 0, Math.PI * 2, true); | |
ctx.fillStyle = "#777" | |
ctx.fill(); | |
ctx.closePath(); | |
} | |
} | |
// draw Nodes | |
var edgesDrawn = {}; | |
for (var k in this.nodeMap) { | |
var node = this.nodeMap[k]; | |
// draw edges first | |
node.children.forEach(function(n){ | |
if (edgesDrawn[n.label+"-"+node.label]) return | |
ctx.beginPath(); | |
ctx.moveTo((node.x+0.5) * separation, (node.y + 0.5) * separation); | |
ctx.lineStyle = "#000000" | |
ctx.lineTo((n.x+0.5) * separation, (n.y + 0.5) * separation) | |
ctx.stroke(); | |
ctx.closePath(); | |
edgesDrawn[node.label+"-"+n.label] = true | |
}) | |
ctx.beginPath(); | |
ctx.arc((node.x+0.5) * separation, (node.y + 0.5) * separation, NODE_RADIUS, 0, Math.PI * 2, true); | |
ctx.fillStyle = node.color | |
ctx.fill(); | |
if (node.distance !== INF && this.showNodeDist) { | |
ctx.fillStyle = TEXT_COLORS[node.color] | |
ctx.font = "8pt monospace" | |
ctx.textAlign = "center" | |
ctx.fillText(node.distance, (node.x+0.5) * separation, (node.y + 0.5) * separation + 5); | |
} | |
ctx.stroke(); | |
ctx.closePath(); | |
if (this.showLabels){ | |
ctx.beginPath(); | |
ctx.fillStyle = "#FFFFFF" | |
ctx.fillRect((node.x + 0.13) * separation, (node.y + 0.27) * separation, 15, 15); | |
ctx.strokeRect((node.x + 0.13) * separation, (node.y + 0.27) * separation, 15, 15); | |
ctx.font = "10pt monospace" | |
ctx.fillStyle = "#000000" | |
ctx.textAlign = "center" | |
ctx.fillText(node.label, (node.x + 0.24) * separation, (node.y + 0.45) * separation); | |
ctx.closePath(); | |
} | |
} | |
// second pass on Nodes to draw the edge distance labels | |
if (this.showEdgeDist){ | |
var edgesDrawn = {}; | |
for (var k in this.nodeMap) { | |
var node = this.nodeMap[k]; | |
// draw edges first | |
node.children.forEach(function(n){ | |
// draw the edge distance | |
var distance = node.calculateDistanceTo(n); | |
var halfX = ((n.x - node.x) / 2) + node.x + 0.5; | |
var halfY = ((n.y - node.y) / 2) + node.y + 0.5; | |
ctx.beginPath(); | |
ctx.fillStyle = "#CCCCCC" | |
ctx.lineStyle = "#000000" | |
ctx.fillRect(halfX * separation - 15, halfY * separation - 11, 20, 15); | |
ctx.strokeRect(halfX * separation - 15, halfY * separation - 11, 20, 15); | |
ctx.font = "8pt monospace" | |
ctx.fillStyle = "#000000" | |
ctx.textAlign = "center" | |
ctx.fillText(distance, halfX * separation - 5, halfY * separation); | |
ctx.closePath(); | |
edgesDrawn[node.label+"-"+n.label] = true | |
}) | |
} | |
} | |
if (this.mouseIn) { | |
switch (this.editState) { | |
case 1: | |
this.drawAddNode(); | |
break; | |
case 2: | |
this.drawAddConnection(); | |
break; | |
case 3: | |
this.drawPlacingConnection(); | |
break; | |
} | |
} | |
requestAnimationFrame(this.drawCreate.bind(this)) | |
} | |
GraphCreator.prototype.drawAddNode = function(){ | |
var ctx = this.ctx; | |
var separation = this.nodeSeparation; | |
ctx.beginPath(); | |
ctx.arc((Math.floor(this.mouseX / separation) + 0.5) * separation, (Math.floor(this.mouseY / separation) + 0.5) * separation, NODE_RADIUS, 0, Math.PI * 2, true); | |
ctx.lineStyle = "#000000" | |
ctx.lineWidth = 2; | |
ctx.fillStyle = "#FFFFFF" | |
ctx.globalAlpha = 0.5 | |
ctx.stroke(); | |
ctx.globalAlpha = 1 | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.arc(this.mouseX, this.mouseY, NODE_RADIUS / 2, 0, Math.PI * 2, true); | |
ctx.lineStyle = "#000000" | |
ctx.lineWidth = 2; | |
ctx.fillStyle = "#FFFFFF" | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
} | |
GraphCreator.prototype.drawAddConnection = function(){ | |
var ctx = this.ctx; | |
var separation = this.nodeSeparation; | |
ctx.lineStyle = "#000000" | |
ctx.lineWidth = 2; | |
if (this.getNodeNearestMouse()) { | |
ctx.beginPath(); | |
ctx.arc((Math.floor(this.mouseX / separation) + 0.5) * separation, (Math.floor(this.mouseY / separation) + 0.5) * separation, 3, 0, Math.PI * 2, true); | |
ctx.fillStyle = "#0000FF" | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
} | |
ctx.fillStyle = "#FFFFFF" | |
ctx.beginPath(); | |
ctx.arc(this.mouseX-5, this.mouseY+5, 1, 0, Math.PI * 2, true); | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.arc(this.mouseX+5, this.mouseY-5, 1, 0, Math.PI * 2, true); | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.moveTo(this.mouseX-5, this.mouseY+5); | |
ctx.lineTo(this.mouseX+5, this.mouseY-5); | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
ctx.closePath(); | |
} | |
GraphCreator.prototype.drawPlacingConnection = function(){ | |
var ctx = this.ctx; | |
var separation = this.nodeSeparation; | |
ctx.lineStyle = "#000000" | |
if (this.getNodeNearestMouse()) { | |
ctx.beginPath(); | |
ctx.arc((Math.floor(this.mouseX / separation) + 0.5) * separation, (Math.floor(this.mouseY / separation) + 0.5) * separation, 3, 0, Math.PI * 2, true); | |
ctx.fillStyle = "#0000FF" | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.moveTo((this.partialConnectionStart.x + 0.5) * separation, (this.partialConnectionStart.y + 0.5) * separation) | |
ctx.lineTo((Math.floor(this.mouseX / separation) + 0.5) * separation, (Math.floor(this.mouseY / separation) + 0.5) * separation) | |
ctx.stroke(); | |
ctx.closePath(); | |
} else { | |
ctx.beginPath(); | |
ctx.moveTo((this.partialConnectionStart.x + 0.5) * separation, (this.partialConnectionStart.y + 0.5) * separation) | |
ctx.lineTo(this.mouseX, this.mouseY) | |
ctx.stroke(); | |
ctx.closePath(); | |
} | |
ctx.beginPath(); | |
ctx.arc((this.partialConnectionStart.x + 0.5) * separation, (this.partialConnectionStart.y + 0.5) * separation, 3, 0, Math.PI * 2, true); | |
ctx.fillStyle = "#0000FF" | |
ctx.fill(); | |
ctx.stroke(); | |
ctx.closePath(); | |
ctx.lineStyle = "#000000" | |
ctx.lineWidth = 2; | |
ctx.fillStyle = "#FFFFFF" | |
ctx.beginPath(); | |
ctx.arc(this.mouseX-5, this.mouseY+5, 1, 0, Math.PI * 2, true); | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.arc(this.mouseX+5, this.mouseY-5, 1, 0, Math.PI * 2, true); | |
ctx.stroke(); | |
ctx.fill(); | |
ctx.closePath(); | |
ctx.beginPath(); | |
ctx.moveTo(this.mouseX-5, this.mouseY+5); | |
ctx.lineTo(this.mouseX+5, this.mouseY-5); | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
ctx.closePath(); | |
} | |
GraphCreator.prototype.getNodeNearestMouse = function(){ | |
var separation = this.nodeSeparation; | |
var coordX = Math.floor(this.mouseX / separation); | |
var coordY = Math.floor(this.mouseY / separation); | |
return this.nodeMap[coordX+","+coordY] || null | |
} | |
GraphCreator.prototype.createNode = function(){ | |
var separation = this.nodeSeparation; | |
var coordX = Math.floor(this.mouseX / separation); | |
var coordY = Math.floor(this.mouseY / separation); | |
if (!this.nodeMap[coordX+","+coordY]) | |
this.nodeMap[coordX+","+coordY] = new Node(coordX, coordY) | |
} | |
GraphCreator.prototype.connectionCreate = function(){ | |
if (!this.partialConnectionStart){ | |
var node = this.getNodeNearestMouse() | |
this.partialConnectionStart = node; | |
return node !== null | |
} | |
var connectNode = this.getNodeNearestMouse(); | |
if (connectNode === null || connectNode === this.partialConnectionStart) return false | |
if (this.partialConnectionStart.children.indexOf(connectNode) === -1) { | |
this.partialConnectionStart.children.push(connectNode) | |
connectNode.children.push(this.partialConnectionStart) | |
} | |
return true | |
} | |
var cvs = new GraphCreator(document.getElementById("creator")); | |
// load in default | |
var defaultGraph = [ | |
[0, 0, [1]], | |
[2, 1, [2]], | |
[1, 2, [3, 4, 5]], | |
[5, 1, [6]], | |
[1, 4, [7]], | |
[3, 4, []], | |
[4, 3, [7]], | |
[5, 5, []] | |
] | |
defaultGraph.forEach(function(n, i){ | |
var node = new Node(n[0], n[1]) | |
node.label = i; | |
cvs.nodeMap[n[0]+","+n[1]] = node | |
}) | |
defaultGraph.forEach(function(n, i){ | |
var node = cvs.nodeMap[n[0]+","+n[1]]; | |
n[2].forEach(function(j){ | |
var o = defaultGraph[j]; | |
var onode = cvs.nodeMap[o[0]+","+o[1]]; | |
onode.children.push(node); | |
node.children.push(onode); | |
}) | |
}) | |
cvs.finishNode = 7; | |
cvs.adjustSelects(); | |
console.log("This console has super bird powers!"); | |
function caw(){ | |
console.log( | |
" ,::::.._ *~caw~*\n" + | |
" ,':::::::::. /\n" + | |
" _,_`:::,: /\\ ::`-,.._\n" + | |
" _._., ', `:::::::::;'-..__`.\n" + | |
" _.-'' ' ,' ,' ,\:::,'::-`'''\n" + | |
" _.-'' , ' , ,' ' ,' `::WW/\n" + | |
" _..-'' , ' , ' ,' , ,' ',' '/wwww\n" + | |
" _...:::'`-..'_, ' , ,' , ' ,'' , ,':,WWw\n" + | |
" _`.:::::,':::::,'::`-:..'_',_'_,'..-'::,:W,\n" + | |
" _..-:::'::,':::::::,':::,':,'::,':::,'::::::,':::;\n" + | |
" `':,'::::::,:,':::::::::::::::::':::,'::_:::,'/\n" + | |
" __..:'::,':::::::--''' `-:,':,':::'::-' ,':::/\n" + | |
" _.::::::,:::.-''-`-`..'_,'. ,', , ' , ,' ', `','\n" + | |
" ,::::::''''` \:. . ,' ' ,',' '_,'\n" + | |
" ``::._,'_'_,',.-'\n" + | |
" \\ \\\n" + | |
" \\_\\\n" + | |
" \\`-`.-'_\n" + | |
" .`-.\\__`. ``\n" + | |
" ``-.-._\n" + | |
" `" | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment