Created
June 30, 2014 09:24
-
-
Save bcho/68fc40b06ffce11d2576 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| body { | |
| background: #000; | |
| } | |
| #app-canvas { | |
| margin: 0; | |
| padding: 0; | |
| } |
This file contains hidden or 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
| // Generated by CoffeeScript 1.7.1 | |
| (function() { | |
| var main, | |
| __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | |
| main = function() { | |
| var $queryPeopleA, $queryPeopleB, $queryResult, APP, NODES, NODES_COLLECTION, checkRelationship, containsName, find, findIndexNodeByName, getNames, getNodes, prepareNodes, query, union, unionNodes; | |
| APP = Processing.getInstanceById('app-canvas'); | |
| NODES_COLLECTION = APP.getNC(); | |
| NODES = NODES_COLLECTION.nodes; | |
| $queryPeopleA = $('#app-query-people-a'); | |
| $queryPeopleB = $('#app-query-people-b'); | |
| $queryResult = $('#app-query-result'); | |
| getNames = function() { | |
| var i, nodesCount, _i, _ref, _results; | |
| nodesCount = NODES.size(); | |
| if (!(nodesCount > 0)) { | |
| return []; | |
| } | |
| _results = []; | |
| for (i = _i = _ref = nodesCount - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { | |
| _results.push(NODES.get(i).name); | |
| } | |
| return _results; | |
| }; | |
| getNodes = function() { | |
| var i, nodesCount, _i, _ref, _results; | |
| nodesCount = NODES.size(); | |
| if (!(nodesCount > 0)) { | |
| return []; | |
| } | |
| _results = []; | |
| for (i = _i = _ref = nodesCount - 1; _ref <= 0 ? _i <= 0 : _i >= 0; i = _ref <= 0 ? ++_i : --_i) { | |
| _results.push(NODES.get(i)); | |
| } | |
| return _results; | |
| }; | |
| containsName = function(name) { | |
| return __indexOf.call(getNames(), name) >= 0; | |
| }; | |
| window.checkRelationship = checkRelationship = function() { | |
| var aName, bName; | |
| aName = $queryPeopleA.val(); | |
| bName = $queryPeopleB.val(); | |
| if (aName === '') { | |
| $queryResult.text('请输入第一个查询名字'); | |
| return; | |
| } | |
| if (bName === '') { | |
| $queryResult.text('请输入第二个查询名字'); | |
| return; | |
| } | |
| if (aName === bName) { | |
| $queryResult.text("" + aName + " 和 " + bName + " 是同一个人噢"); | |
| return; | |
| } | |
| if (!containsName(aName)) { | |
| $queryResult.text("" + aName + " 不在亲戚列表中"); | |
| return; | |
| } | |
| if (!containsName(bName)) { | |
| $queryResult.text("" + bName + " 不在亲戚列表中"); | |
| } | |
| if (query(aName, bName)) { | |
| return $queryResult.text("" + aName + " 和 " + bName + " 是亲戚关系"); | |
| } else { | |
| return $queryResult.text("" + aName + " 和 " + bName + " 不是亲戚关系"); | |
| } | |
| }; | |
| $queryPeopleA.keyup(checkRelationship); | |
| $queryPeopleB.keyup(checkRelationship); | |
| prepareNodes = function() { | |
| var i, node, nodes, _i, _len, _ref; | |
| nodes = []; | |
| _ref = getNodes(); | |
| for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { | |
| node = _ref[i]; | |
| nodes.push({ | |
| name: node.name, | |
| parent: i, | |
| node: node | |
| }); | |
| } | |
| return nodes; | |
| }; | |
| find = function(a, nodes) { | |
| while (a !== nodes[a].parent) { | |
| a = nodes[a].parent; | |
| } | |
| return a; | |
| }; | |
| union = function(a, b, nodes) { | |
| var aParent, bParent; | |
| aParent = find(a, nodes); | |
| bParent = find(b, nodes); | |
| return nodes[aParent].parent = bParent; | |
| }; | |
| unionNodes = function(nodes) { | |
| var i, j, node, otherNode, _i, _j, _len, _len1; | |
| for (i = _i = 0, _len = nodes.length; _i < _len; i = ++_i) { | |
| node = nodes[i]; | |
| for (j = _j = 0, _len1 = nodes.length; _j < _len1; j = ++_j) { | |
| otherNode = nodes[j]; | |
| if (i === j) { | |
| continue; | |
| } | |
| if (node.node.isNear(otherNode.node)) { | |
| union(i, j, nodes); | |
| } | |
| } | |
| } | |
| return nodes; | |
| }; | |
| findIndexNodeByName = function(name, nodes) { | |
| var i, node, _i, _len; | |
| for (i = _i = 0, _len = nodes.length; _i < _len; i = ++_i) { | |
| node = nodes[i]; | |
| if (node.name === name) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| }; | |
| return query = function(aName, bName) { | |
| var aIndex, bIndex, nodes; | |
| nodes = unionNodes(prepareNodes()); | |
| aIndex = findIndexNodeByName(aName, nodes); | |
| if (aIndex === -1) { | |
| return false; | |
| } | |
| bIndex = findIndexNodeByName(bName, nodes); | |
| if (bIndex === -1) { | |
| return false; | |
| } | |
| return find(aIndex, nodes) === find(bIndex, nodes); | |
| }; | |
| }; | |
| window.setTimeout(main, 500); | |
| }).call(this); |
This file contains hidden or 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
| // app.pde | |
| // | |
| // A demo application for ``relationship query program`` using Processing.js. | |
| // | |
| // You can double click in the canvas to generate a node. This node will have | |
| // a random unique name. To attach a node to an existed family tree, you just | |
| // need to drag it to any nodes in the family tree. | |
| // | |
| // To query a relationship (e.g. is Bob and Amy from same family?), you can | |
| // type two names in the input box and press query button, the program will | |
| // give you the answer. | |
| // ===================== | |
| // CONSTANTS | |
| // ===================== | |
| String[] US_NAMES = { | |
| "Aaron", | |
| "Abbey", | |
| "Abbie", | |
| "Abby", | |
| "Abdul", | |
| "Abe", | |
| "Abel", | |
| "Abigail", | |
| "Abraham", | |
| "Abram", | |
| "Babara", | |
| "Babette", | |
| "Bailey", | |
| "Bambi", | |
| "Bao", | |
| "Barabara", | |
| "Barb", | |
| "Barbar", | |
| "Barbara", | |
| "Caitlin", | |
| "Caitlyn", | |
| "Calandra", | |
| "Caleb", | |
| "Calista", | |
| "Callie", | |
| "Calvin", | |
| "Camelia", | |
| "Camellia", | |
| "Cameron", | |
| "Cami", | |
| "Dacia", | |
| "Dagmar", | |
| "Dagny", | |
| "Dahlia", | |
| "Daina", | |
| "Daine", | |
| "Daisey", | |
| "Daisy", | |
| "Dakota", | |
| "Dale", | |
| "Earl", | |
| "Earle", | |
| "Earlean", | |
| "Earleen", | |
| "Earlene", | |
| "Earlie", | |
| "Earline", | |
| "Earnest", | |
| "Earnestine", | |
| "Eartha" | |
| }; | |
| color FG_COLOR = #FEFEFE; | |
| color BG_COLOR = #000000; | |
| color HI_COLOR = #17FF9B; | |
| color NODE_COLOR = #4080BB; | |
| float NODE_RADIUS = 50; | |
| float NODE_KERNEL_RADIUS = 3; | |
| float NODE_RADIUS_RATE = .75; | |
| float FRAME_RATE = 25; | |
| // ===================== | |
| // Global Components | |
| // ===================== | |
| NodesCollection nc; | |
| // ===================== | |
| // Main Routines | |
| // ===================== | |
| void setup() { | |
| frameRate(FRAME_RATE); | |
| size(window.innerWidth, window.innerHeight - 50); | |
| nc = new NodesCollection(); | |
| } | |
| void draw() { | |
| size(window.innerWidth, window.innerHeight - 50); | |
| background(BG_COLOR); | |
| nc.draw(); | |
| } | |
| void mouseClicked() { | |
| nc.handleMouseClicked(); | |
| } | |
| void mousePressed() { | |
| nc.handleMousePressed(); | |
| } | |
| void mouseReleased() { | |
| nc.handleMouseReleased(); | |
| } | |
| // ===================== | |
| // Exposed Interface | |
| // ===================== | |
| NodesCollection getNC() { | |
| return nc; | |
| } | |
| // ===================== | |
| // Components | |
| // ===================== | |
| /** | |
| * Node | |
| * | |
| * Represents a node. | |
| * | |
| * @param r node's radius. | |
| * @param x node's initial x position. | |
| * @param y node's initial y position. | |
| * @param name node's name. | |
| */ | |
| class Node { | |
| // Current r/x/y. | |
| float r, x, y; | |
| // Max (final) radius. | |
| float maximumR; | |
| String name; | |
| // pre-defined settings. | |
| color normalColor = NODE_COLOR; | |
| color highlightColor = HI_COLOR; | |
| color kernelColor = BG_COLOR; | |
| color nodeTextColor = FG_COLOR; | |
| float kernelRadius = NODE_KERNEL_RADIUS; | |
| float radiusRate = NODE_RADIUS_RATE; | |
| Node(float r_, float x_, float y_, String name_) { | |
| _setup(r_, x_, y_, name_); | |
| } | |
| void _setup(float r_, float x_, float y_, String name_) { | |
| r = 0; | |
| maximumR = r_; | |
| x = x_; | |
| y = y_; | |
| name = name_; | |
| } | |
| /** | |
| * If the mouse move into this node? | |
| */ | |
| boolean isCointainedMouse() { | |
| return sq(x - mouseX) + sq(y - mouseY) < sq(r / 2); | |
| } | |
| /** | |
| * If the node near to other node? | |
| */ | |
| boolean isNear(node otherNode) { | |
| float distance = sq((r + otherNode.r)); | |
| return sq(x - otherNode.x) + sq(y - otherNode.y) < distance; | |
| } | |
| void increaseRadius() { | |
| if (r < maximumR) { | |
| r += min(maximumR - r, radiusRate); | |
| } | |
| } | |
| void draw() { | |
| noStroke(); | |
| // Draw outer circle. | |
| if (isCointainedMouse()) { | |
| fill(highlightColor, 100); | |
| } else { | |
| fill(normalColor, 100); | |
| } | |
| ellipse(x, y, r, r); | |
| // Draw inner circle (kernel). | |
| fill(kernelColor, 100); | |
| ellipse(x, y, kernelRadius, kernelRadius); | |
| // Draw node name. | |
| fill(nodeTextColor, 100); | |
| text(name, x + r / 2, y - r / 2); | |
| increaseRadius(); | |
| } | |
| } | |
| /** | |
| * NodesCollection | |
| * | |
| * Nodes manager. | |
| */ | |
| class NodesCollection { | |
| // Owned nodes. | |
| ArrayList nodes; | |
| int _draggingNodeIndex; | |
| boolean _dragging; | |
| float _draggingOffsetX; | |
| float _draggingOffsetY; | |
| int _mouseClickedTimes; | |
| float _mouseLastClickedTimeMS; | |
| float mouseClickIntervalMS = 500; | |
| NodesCollection() { | |
| nodes = new ArrayList(); | |
| _draggingNodeIndex = -1; | |
| _dragging = false; | |
| _draggingOffsetX = 0.0; | |
| _draggingOffsetY = 0.0; | |
| _mouseClickedTimes = 0; | |
| _mouseLastClickedTimeMS = millis(); | |
| } | |
| /** | |
| * Add a node to collection. | |
| * | |
| * The new node will have a random and unique name. | |
| * | |
| * @param x/y node's position. | |
| */ | |
| void add(float x, float y) { | |
| String name = generateName(); | |
| nodes.add(new Node(NODE_RADIUS, x, y, name)); | |
| } | |
| /** | |
| * Draw nodes. | |
| */ | |
| void draw() { | |
| Node node, neighbourNode; | |
| for (int i = nodes.size() - 1; i >= 0; i--) { | |
| node = (Node) nodes.get(i); | |
| if (i == _draggingNodeIndex && _dragging) { | |
| node.x = mouseX - _draggingOffsetX; | |
| node.y = mouseY - _draggingOffsetY; | |
| } | |
| node.draw(); | |
| // Connect with neighbour node that near by. | |
| // FIXME it's O(N^2) | |
| for (int j = nodes.size() - 1; j >= 0; j--) { | |
| if (i == j) { | |
| continue; | |
| } | |
| neighbourNode = (Node) nodes.get(j); | |
| if (node.isNear(neighbourNode)) { | |
| stroke(HI_COLOR, 100); | |
| line(node.x, node.y, neighbourNode.x, neighbourNode.y); | |
| } | |
| } | |
| } | |
| // Recheck when the nodes is updated. | |
| if (window.checkRelationship) { // wraps for js side's timeout | |
| window.checkRelationship(); | |
| } | |
| } | |
| String generateName() { | |
| String name; | |
| boolean conflictName; | |
| Node node; | |
| conflictName = true; | |
| while (conflictName) { | |
| name = US_NAMES[int(random(US_NAMES.length))]; | |
| conflictName = false; | |
| for (int i = nodes.size() - 1; i >= 0; i--) { | |
| node = (Node) nodes.get(i); | |
| if (node.name == name) { | |
| conflictName = true; | |
| break; | |
| } | |
| } | |
| } | |
| return name; | |
| } | |
| /** | |
| * Hook for mouse clicked event. | |
| */ | |
| void handleMouseClicked() { | |
| float nowMS = millis(); | |
| if (nowMS - _mouseLastClickedTimeMS > mouseClickIntervalMS) { | |
| _mouseClickedTimes = 0; | |
| } | |
| _mouseClickedTimes++; | |
| if (_mouseClickedTimes == 2) { | |
| _mouseClickedTimes = 0; | |
| handleMouseDoubleClicked(); | |
| } | |
| _mouseLastClickedTimeMS = nowMS; | |
| } | |
| /** | |
| * Hook for mouse double clicked event. | |
| */ | |
| void handleMouseDoubleClicked() { | |
| // Add a node in current mouse position. | |
| if (nodes.size() < US_NAMES.length * 0.85) { | |
| add(mouseX, mouseY); | |
| // Recheck after adding a new node. | |
| window.checkRelationship(); | |
| } else { | |
| console.log("Too many nodes."); | |
| } | |
| } | |
| /** | |
| * Hook for mouse pressed event. | |
| */ | |
| void handleMousePressed() { | |
| // Select a dragging node. | |
| Node node; | |
| float dx, dy; | |
| for (int i = nodes.size() - 1; i >= 0; i--) { | |
| node = (Node) nodes.get(i); | |
| if (node.isCointainedMouse()) { | |
| _draggingNodeIndex = i; | |
| _draggingOffsetX = mouseX - node.x; | |
| _draggingOffsetY = mouseY - node.y; | |
| _dragging = true; | |
| break; | |
| } | |
| } | |
| } | |
| /** | |
| * Hook for mouse released event. | |
| */ | |
| void handleMouseReleased() { | |
| _dragging = false; | |
| _draggingOffsetX = 0; | |
| _draggingOffsetY = 0; | |
| _draggingNodeIndex = -1; | |
| } | |
| } |
This file contains hidden or 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
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Relationships</title> | |
| <link rel="stylesheet" href="http://cdn.staticfile.org/twitter-bootstrap/3.1.1/css/bootstrap.min.css"> | |
| <link rel="stylesheet" href="app.css"> | |
| </head> | |
| <body class="container-fluid"> | |
| <div class="row"> | |
| <canvas id="app-canvas" class="col-md-12" data-processing-sources="app.pde"></canvas> | |
| </div> | |
| <div class="row"> | |
| <div class="navbar navbar-default navbar-fixed-bottom" role="navigation"> | |
| <form class="navbar-form navbar-left" role="search"> | |
| <div class="form-group"> | |
| <input id="app-query-people-a" class="form-control" type="text"> | |
| </div> | |
| 和 | |
| <div class="form-group"> | |
| <input id="app-query-people-b" class="form-control" type="text"> | |
| </div> | |
| <span id="app-query-result">请输入第一个查询名字</span> | |
| </form> | |
| </div> | |
| </div> | |
| </body> | |
| <script src="http://cdn.staticfile.org/jquery/2.1.1-rc2/jquery.min.js"></script> | |
| <script src="http://cdn.staticfile.org/processing.js/1.4.8/processing.min.js"></script> | |
| <script src="app.js"></script> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment