From 8d557368e97d93e3418b49e0216ef404a3668903 Mon Sep 17 00:00:00 2001 From: pskala Date: Mon, 12 Sep 2016 15:02:58 +0200 Subject: [PATCH] Demos: added demos from jointjs.com (#399) --- Gruntfile.js | 161 ++ demo/chess/css/chess.css | 30 + demo/chess/index.html | 40 + demo/chess/src/chess.js | 268 +++ demo/chess/src/garbochess.js | 2515 +++++++++++++++++++++++++++++ demo/common.css | 22 + demo/devs/index.html | 14 + demo/erd/css/erd.css | 15 + demo/erd/index.html | 27 + demo/erd/src/erd.js | 311 ++++ demo/fsa.html | 32 - demo/fsa/css/fsa.css | 3 + demo/fsa/index.html | 30 + demo/{ => fsa/src}/fsa.js | 0 demo/links/index.html | 27 + demo/links/src/links.js | 130 ++ demo/logic/css/logic.css | 116 ++ demo/logic/index.html | 27 + demo/logic/src/logic.js | 162 ++ demo/org/images/female.png | Bin 0 -> 5051 bytes demo/org/images/male.png | Bin 0 -> 5330 bytes demo/org/index.html | 27 + demo/org/src/org.js | 65 + demo/paper/css/paper.css | 173 ++ demo/paper/index.html | 282 ++-- demo/paper/paper.css | 152 -- demo/paper/paper.js | 236 --- demo/paper/src/paper.js | 438 +++++ demo/petri nets/index.html | 28 + demo/petri nets/src/pn.js | 167 ++ demo/petri nets/src/smil.user.js | 1482 +++++++++++++++++ demo/routing/css/routing.css | 16 + demo/routing/index.html | 41 + demo/routing/src/routing.js | 99 ++ demo/umlcd/css/umlcd.css | 4 + demo/umlcd/index.html | 29 + demo/umlcd/src/umlcd.js | 179 ++ demo/umlsc/index.html | 28 + demo/umlsc/src/umlsc.js | 149 ++ package.json | 4 + test/e2e/chess.js | 68 + test/e2e/devs.js | 83 + test/e2e/erd.js | 75 + test/e2e/finite-state-machines.js | 84 + test/e2e/links.js | 89 + test/e2e/logic-circuits.js | 75 + test/e2e/organizational-charts.js | 76 + test/e2e/paper-attributes.js | 161 ++ test/e2e/petri-nets.js | 80 + test/e2e/smart-routing.js | 130 ++ test/e2e/umlcd.js | 76 + test/e2e/umlsc.js | 80 + test/e2eHelpers.js | 187 +++ 53 files changed, 8222 insertions(+), 571 deletions(-) create mode 100644 demo/chess/css/chess.css create mode 100644 demo/chess/index.html create mode 100644 demo/chess/src/chess.js create mode 100644 demo/chess/src/garbochess.js create mode 100644 demo/common.css create mode 100644 demo/devs/index.html create mode 100644 demo/erd/css/erd.css create mode 100644 demo/erd/index.html create mode 100644 demo/erd/src/erd.js delete mode 100644 demo/fsa.html create mode 100644 demo/fsa/css/fsa.css create mode 100644 demo/fsa/index.html rename demo/{ => fsa/src}/fsa.js (100%) create mode 100644 demo/links/index.html create mode 100644 demo/links/src/links.js create mode 100644 demo/logic/css/logic.css create mode 100644 demo/logic/index.html create mode 100644 demo/logic/src/logic.js create mode 100644 demo/org/images/female.png create mode 100644 demo/org/images/male.png create mode 100644 demo/org/index.html create mode 100644 demo/org/src/org.js create mode 100644 demo/paper/css/paper.css delete mode 100644 demo/paper/paper.css delete mode 100644 demo/paper/paper.js create mode 100644 demo/paper/src/paper.js create mode 100644 demo/petri nets/index.html create mode 100644 demo/petri nets/src/pn.js create mode 100644 demo/petri nets/src/smil.user.js create mode 100644 demo/routing/css/routing.css create mode 100644 demo/routing/index.html create mode 100644 demo/routing/src/routing.js create mode 100644 demo/umlcd/css/umlcd.css create mode 100644 demo/umlcd/index.html create mode 100644 demo/umlcd/src/umlcd.js create mode 100644 demo/umlsc/index.html create mode 100644 demo/umlsc/src/umlsc.js create mode 100644 test/e2e/chess.js create mode 100644 test/e2e/devs.js create mode 100644 test/e2e/erd.js create mode 100644 test/e2e/finite-state-machines.js create mode 100644 test/e2e/links.js create mode 100644 test/e2e/logic-circuits.js create mode 100644 test/e2e/organizational-charts.js create mode 100644 test/e2e/paper-attributes.js create mode 100644 test/e2e/petri-nets.js create mode 100644 test/e2e/smart-routing.js create mode 100644 test/e2e/umlcd.js create mode 100644 test/e2e/umlsc.js create mode 100644 test/e2eHelpers.js diff --git a/Gruntfile.js b/Gruntfile.js index 1633fa4db8..6416230e0d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,8 @@ 'use strict'; +var phantomjs = require('phantomjs-prebuilt'); +var selenium = require('selenium-standalone'); + module.exports = function(grunt) { var cheerio = require('cheerio'); @@ -419,6 +422,16 @@ module.exports = function(grunt) { } }, mochaTest: { + e2e: { + src: [ + 'test/e2e/*.js' + ], + options: { + reporter: 'spec', + timeout: 120000, + clearRequireCache: true + } + }, server: { src: [ 'test/*-nodejs/*' @@ -510,6 +523,9 @@ module.exports = function(grunt) { ), tasks: ['build'] } + }, + env: { + } }; @@ -774,4 +790,149 @@ module.exports = function(grunt) { grunt.registerTask('install', ['bowerInstall', 'build:all']); grunt.registerTask('default', ['install', 'build', 'watch']); + + /* + List of Available Platforms on Sauce Labs: + https://saucelabs.com/platforms + */ + var e2eBrowsers = { + 'chrome': { + 'browserName': 'chrome', + 'name': 'Chrome' + }, + 'chrome-linux': { + 'browserName': 'chrome', + 'platform': 'linux', + 'name': 'Chrome on Linux' + }, + 'chrome-windows7': { + 'browserName': 'chrome', + 'platform': 'windows', + 'name': 'Chrome on Windows 7' + }, + 'chrome-mac': { + 'browserName': 'chrome', + 'platform': 'mac', + 'name': 'Chrome on Mac' + }, + 'firefox': { + 'browserName': 'firefox', + 'name': 'Firefox' + }, + 'firefox-linux': { + 'browserName': 'firefox', + 'platform': 'linux', + 'name': 'Firefox on Linux' + }, + 'firefox-mac': { + 'browserName': 'firefox', + 'platform': 'mac', + 'name': 'Firefox on Mac' + }, + 'phantomjs': { + 'browserName': 'phantomjs', + 'name': 'PhantomJS' + }, + 'phantomjs-2.x': { + 'browserName': 'phantomjs', + // Set the path to the PhantomJS 2.x binary. + // Can be in different places depending upon the current environment. + // For example, if phantomjs is on the current user's PATH (with the correct version). + 'phantomjs.binary.path': phantomjs.path, + 'name': 'PhantomJS 2.x' + } + }; + + Object.keys(e2eBrowsers).forEach(function(key) { + + var browser = e2eBrowsers[key]; + + config.env[key] = { + E2E_DESIRED: JSON.stringify(browser) + }; + }); + + Object.keys(e2eBrowsers).forEach(function(key) { + grunt.registerTask('test:e2e:' + key, [ + 'env:' + key, + 'mochaTest:e2e' + ]); + }); + + grunt.registerTask('test:e2e', ['mochaTest:e2e']); + + grunt.registerTask('test:e2e:all', [ + 'test:e2e:chrome-linux', + 'test:e2e:chrome-windows7', + 'test:e2e:chrome-mac', + 'test:e2e:firefox-linux', + 'test:e2e:firefox-mac' + ]); + + grunt.registerTask('selenium', function(action) { + + var done = this.async(); + + switch (action) { + + case 'install': + return installSelenium(done); + + case 'start': + return installSelenium(function(error) { + if (error) return done(error); + startSelenium(done); + }); + + case 'stop': + return stopSelenium(done); + + // For backwards compatibility (`grunt selenium`). + // This task starts the local selenium server and then waits. + default: + return installSelenium(function(error) { + if (error) return done(error); + startSelenium(function(error) { + if (error) return done(error); + grunt.log.writeln('Selenium started'); + grunt.log.writeln('Exit this process ' + '[CTRL+C]'['white'].bold + ' to stop selenium'); + // Never call done. + // This allows selenium to continue running until the grunt process is killed. + }); + }); + } + }); + + var seleniumInstalled = (function() { + + return grunt.file.exists(__dirname + '/node_modules/selenium-standalone/.selenium/selenium-server'); + + }()); + + var seleniumChildProcess; + + function startSelenium(cb) { + grunt.log.writeln('Starting selenium..'); + selenium.start(function(error, child) { + if (error) return cb(error); + seleniumChildProcess = child; + cb(); + }); + } + + function stopSelenium(cb) { + if (seleniumChildProcess) seleniumChildProcess.kill(); + cb(); + } + + function installSelenium(cb) { + if (seleniumInstalled) return cb(); + grunt.log.writeln('Installing selenium..'); + seleniumInstalled = true; + selenium.install(cb); + } + process.on('exit', function() { + // Kill selenium server process if it is running. + if (seleniumChildProcess) seleniumChildProcess.kill(); + }); }; diff --git a/demo/chess/css/chess.css b/demo/chess/css/chess.css new file mode 100644 index 0000000000..a50369f604 --- /dev/null +++ b/demo/chess/css/chess.css @@ -0,0 +1,30 @@ +#board { + margin: 0 auto; + border-style: none; + width: 400px; +} +.board-bg { + border-spacing: 0; + width: 402px; + height: 402px; + z-index: -1; + border-collapse: inherit; + margin: 0 auto; +} +#board > svg { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} +.board-bg td { + width: 46px; + height: 46px; + border-style: none; + background: #30d0c6; +} +.board-bg tr:nth-child(odd) td:nth-child(even), +.board-bg tr:nth-child(even) td:nth-child(odd) { + background: #7c68fd; +} diff --git a/demo/chess/index.html b/demo/chess/index.html new file mode 100644 index 0000000000..3747d5b5f2 --- /dev/null +++ b/demo/chess/index.html @@ -0,0 +1,40 @@ + + + + + + Chess + + + + + + +
+

Chess

+

powered by GarboChess.js

+
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + \ No newline at end of file diff --git a/demo/chess/src/chess.js b/demo/chess/src/chess.js new file mode 100644 index 0000000000..9a4e3653b0 --- /dev/null +++ b/demo/chess/src/chess.js @@ -0,0 +1,268 @@ +var Board = joint.dia.Paper.extend({ + + options: _.extend(joint.dia.Paper.prototype.options, { + + letters: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], + + namespace: joint.shapes.chess, + + startup: { + 'a1': 'RookWhite', 'a2': 'PawnWhite', 'a7': 'PawnBlack', 'a8': 'RookBlack', + 'b1': 'KnightWhite', 'b2': 'PawnWhite', 'b7': 'PawnBlack', 'b8': 'KnightBlack', + 'c1': 'BishopWhite', 'c2': 'PawnWhite', 'c7': 'PawnBlack', 'c8': 'BishopBlack', + 'd1': 'QueenWhite', 'd2': 'PawnWhite', 'd7': 'PawnBlack', 'd8': 'QueenBlack', + 'e1': 'KingWhite', 'e2': 'PawnWhite', 'e7': 'PawnBlack', 'e8': 'KingBlack', + 'f1': 'BishopWhite', 'f2': 'PawnWhite', 'f7': 'PawnBlack', 'f8': 'BishopBlack', + 'g1': 'KnightWhite', 'g2': 'PawnWhite', 'g7': 'PawnBlack', 'g8': 'KnightBlack', + 'h1': 'RookWhite', 'h2': 'PawnWhite', 'h7': 'PawnBlack', 'h8': 'RookBlack' + }, + + width: 8 * 50, + + height: 8 * 50, + + gridSize: 1 + + }), + + initialize: function() { + + this.model = new joint.dia.Graph; + + joint.dia.Paper.prototype.initialize.apply(this, arguments); + + this.on('cell:pointerdown', function(cellView) { + + cellView.model.toFront(); + + this._p0 = cellView.model.get('position'); + + this.trigger('piece:touch', cellView.model, this._p2n(this._p0)); + }); + + this.on('cell:pointerup', function(cellView) { + + var pos = cellView.model.get('position'); + var p0 = this._p0; + var p1 = { x: g.snapToGrid(pos.x, 50), y: g.snapToGrid(pos.y, 50) }; + + cellView.model.set('position', p1); + + this.trigger('piece:drop', cellView.model, this._p2n(p0), this._p2n(p1), function() { + cellView.model.set('position', p0); + }); + }); + + this.reset(); + }, + + reset: function() { + + this.model.resetCells(); + + _.each(this.options.startup, this.addPiece, this); + }, + + at: function(square) { + + return _.pluck(this.findViewsFromPoint(this._mid(this._n2p(square))), 'model'); + }, + + addPiece: function(piece, square) { + + this.model.addCell(new this.options.namespace[piece]({ position: this._n2p(square) })); + }, + + movePiece: function(from, to, opts) { + + opts = opts || {}; + + var pc = this.at(from); + + if (!this.options.animation || opts.animation === false) { + + _.invoke(pc, 'set', 'position', this._n2p(to)); + + } else { + + _.invoke(pc, 'transition', 'position', this._n2p(to), { + valueFunction: joint.util.interpolate.object + }); + } + }, + + addPointer: function(from, to) { + + this.model.addCell(new joint.dia.Link({ + source: this._mid(this._n2p(from)), + target: this._mid(this._n2p(to)), + z: -1, + attrs: { + '.': { + opacity: .2, + stroke: 'black' + }, + '.marker-target': { + d: "m 0, -10 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0", + fill: 'black' + }, + '.connection': { + 'stroke-width': '4px' + } + } + })); + }, + + addPointers: function(from, toArray) { + + var p1 = this._n2p(from); + + _.chain(toArray) + .map(this._n2p, this) + .groupBy(function(p0) { + return g.point(p0).theta(p1); + }) + .map(function(group) { + return _.max(group, function(p0) { + return g.point(p1).distance(p0); + }); + }) + .each(_.compose(_.partial(this.addPointer, from), this._p2n), this); + }, + + removePointers: function() { + + _.invoke(this.model.getLinks(), 'remove'); + }, + + _p2n: function(p) { + + return this.options.letters[p.x / 50] + (8 - p.y / 50); + }, + + _n2p: function(n) { + + return { + x: this.options.letters.indexOf(n[0]) * 50, + y: (8 - n[1]) * 50 + }; + }, + + _mid: function(p) { + + return { x: p.x + 25, y: p.y + 25 }; + } + +}); + +// Garbochess integration + +var Chessboard = Board.extend({ + + playMove: function(transition, mv) { + + var from = FormatSquare(mv & 0xFF); + var to = FormatSquare((mv >> 8) & 0xFF); + var opts = { animation: transition }; + + _.invoke(this.at(to), 'remove'); + + board.movePiece(from, to, opts); + + if (mv & moveflagPromotion) { + + var promote = _.bind(function(color) { + + _.invoke(this.at(to), 'remove'); + this.addPiece('Queen' + color, to); + + }, this, (g_toMove ? 'White' : 'Black')); + + if (transition) { + this.listenToOnce(this.model, 'transition:end', promote); + } else { + promote(); + } + + } else if (mv & moveflagCastleQueen) { + + this.movePiece('a'+ to[1], 'd' + to[1], opts); + + } else if (mv & moveflagCastleKing) { + + this.movePiece('h'+ to[1], 'f' + to[1], opts); + + } else if (mv & moveflagEPC) { + + _.invoke(this.at(to[0] + from[1]), 'remove'); + } + + var msg = ['message', g_moveCount, GetMoveSAN(mv), '']; + + MakeMove(mv); + + if (GenerateValidMoves().length == 0) { + + msg[3] = g_inCheck ? !g_toMove ? '1 : 0' : '0 : 1' : '½ : ½'; + + this.isGameOver = true; + } + + this.trigger.apply(this, msg); + }, + + getMove: function(from, to) { + + var s = from + to; + return _.find(GenerateValidMoves(), _.compose(function(m) { + return m == s || m == s + 'q'; + }, FormatMove)); + }, + + whereToGo: function(from) { + + return _.chain(GenerateValidMoves()) + .map(FormatMove) + .filter(function(move) { + return !move.lastIndexOf(from); + }) + .invoke('slice', 2, 4) + .value(); + }, + + findBestMove: function(callback) { + + Search(callback, 99, null); + } +}); + +// User interaction + +var board = new Chessboard({ el: $('#board'), animation: true }); + +board.on('piece:touch', function(piece, from) { + + this.addPointers(from, this.whereToGo(from)); +}); + +board.on('piece:drop', function(piece, from, to, undo) { + + this.removePointers(); + + undo(); + + var mv = this.getMove(from, to); + + if (mv) { + this.playMove(false, mv); + this.isGameOver || this.findBestMove(_.bind(this.playMove, this, true)); + } +}); + +board.on('message', function(rnd, mov, res) { + + var text = (rnd % 2 ? '' : (1 + rnd / 2) + '. ') + mov + ' ' + res; + document.getElementById('message').textContent += text; +}); + +ResetGame(); diff --git a/demo/chess/src/garbochess.js b/demo/chess/src/garbochess.js new file mode 100644 index 0000000000..127b33b5fc --- /dev/null +++ b/demo/chess/src/garbochess.js @@ -0,0 +1,2515 @@ +"use strict"; + +// Perf TODO: +// Merge material updating with psq values +// Put move scoring inline in generator +// Remove need for fliptable in psq tables. Access them by color +// Optimize pawn move generation + +// Non-perf todo: +// Checks in first q? +// Pawn eval. +// Better king evaluation +// Better move sorting in PV nodes (especially root) + +var g_debug = true; +var g_timeout = 40; + +function GetFen(){ + var result = ""; + for (var row = 0; row < 8; row++) { + if (row != 0) + result += '/'; + var empty = 0; + for (var col = 0; col < 8; col++) { + var piece = g_board[((row + 2) << 4) + col + 4]; + if (piece == 0) { + empty++; + } + else { + if (empty != 0) + result += empty; + empty = 0; + + var pieceChar = [" ", "p", "n", "b", "r", "q", "k", " "][(piece & 0x7)]; + result += ((piece & colorWhite) != 0) ? pieceChar.toUpperCase() : pieceChar; + } + } + if (empty != 0) { + result += empty; + } + } + + result += g_toMove == colorWhite ? " w" : " b"; + result += " "; + if (g_castleRights == 0) { + result += "-"; + } + else { + if ((g_castleRights & 1) != 0) + result += "K"; + if ((g_castleRights & 2) != 0) + result += "Q"; + if ((g_castleRights & 4) != 0) + result += "k"; + if ((g_castleRights & 8) != 0) + result += "q"; + } + + result += " "; + + if (g_enPassentSquare == -1) { + result += '-'; + } + else { + result += FormatSquare(g_enPassentSquare); + } + + return result; +} + +function GetMoveSAN(move, validMoves) { + var from = move & 0xFF; + var to = (move >> 8) & 0xFF; + + if (move & moveflagCastleKing) return "O-O"; + if (move & moveflagCastleQueen) return "O-O-O"; + + var pieceType = g_board[from] & 0x7; + var result = ["", "", "N", "B", "R", "Q", "K", ""][pieceType]; + + var dupe = false, rowDiff = true, colDiff = true; + if (validMoves == null) { + validMoves = GenerateValidMoves(); + } + for (var i = 0; i < validMoves.length; i++) { + var moveFrom = validMoves[i] & 0xFF; + var moveTo = (validMoves[i] >> 8) & 0xFF; + if (moveFrom != from && + moveTo == to && + (g_board[moveFrom] & 0x7) == pieceType) { + dupe = true; + if ((moveFrom & 0xF0) == (from & 0xF0)) { + rowDiff = false; + } + if ((moveFrom & 0x0F) == (from & 0x0F)) { + colDiff = false; + } + } + } + + if (dupe) { + if (colDiff) { + result += FormatSquare(from).charAt(0); + } else if (rowDiff) { + result += FormatSquare(from).charAt(1); + } else { + result += FormatSquare(from); + } + } else if (pieceType == piecePawn && (g_board[to] != 0 || (move & moveflagEPC))) { + result += FormatSquare(from).charAt(0); + } + + if (g_board[to] != 0 || (move & moveflagEPC)) { + result += "x"; + } + + result += FormatSquare(to); + + if (move & moveflagPromotion) { + if (move & moveflagPromoteBishop) result += "=B"; + else if (move & moveflagPromoteKnight) result += "=N"; + else if (move & moveflagPromoteQueen) result += "=Q"; + else result += "=R"; + } + + MakeMove(move); + if (g_inCheck) { + result += GenerateValidMoves().length == 0 ? "#" : "+"; + } + UnmakeMove(move); + + return result; +} + +function FormatSquare(square) { + var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + return letters[(square & 0xF) - 4] + ((9 - (square >> 4)) + 1); +} + +function FormatMove(move) { + var result = FormatSquare(move & 0xFF) + FormatSquare((move >> 8) & 0xFF); + if (move & moveflagPromotion) { + if (move & moveflagPromoteBishop) result += "b"; + else if (move & moveflagPromoteKnight) result += "n"; + else if (move & moveflagPromoteQueen) result += "q"; + else result += "r"; + } + return result; +} + +function GetMoveFromString(moveString) { + var moves = GenerateValidMoves(); + for (var i = 0; i < moves.length; i++) { + if (FormatMove(moves[i]) == moveString) { + return moves[i]; + } + } + alert("busted! ->" + moveString + " fen:" + GetFen()); +} + +function PVFromHash(move, ply) { + if (ply == 0) + return ""; + + if (move == 0) { + if (g_inCheck) return "checkmate"; + return "stalemate"; + } + + var pvString = " " + GetMoveSAN(move); + MakeMove(move); + + var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; + if (hashNode != null && hashNode.lock == g_hashKeyHigh && hashNode.bestMove != null) { + pvString += PVFromHash(hashNode.bestMove, ply - 1); + } + + UnmakeMove(move); + + return pvString; +} + +// +// Searching code +// + +var g_startTime; + +var g_nodeCount; +var g_qNodeCount; +var g_searchValid; +var g_globalPly = 0; + +function Search(finishMoveCallback, maxPly, finishPlyCallback) { + var lastEval; + var alpha = minEval; + var beta = maxEval; + + g_globalPly++; + g_nodeCount = 0; + g_qNodeCount = 0; + g_searchValid = true; + + var bestMove = 0; + var value; + + g_startTime = (new Date()).getTime(); + + var i; + for (i = 1; i <= maxPly && g_searchValid; i++) { + var tmp = AlphaBeta(i, 0, alpha, beta); + if (!g_searchValid) break; + + value = tmp; + + if (value > alpha && value < beta) { + alpha = value - 500; + beta = value + 500; + + if (alpha < minEval) alpha = minEval; + if (beta > maxEval) beta = maxEval; + } else if (alpha != minEval) { + alpha = minEval; + beta = maxEval; + i--; + } + + if (g_hashTable[g_hashKeyLow & g_hashMask] != null) { + bestMove = g_hashTable[g_hashKeyLow & g_hashMask].bestMove; + } + + if (finishPlyCallback != null) { + finishPlyCallback(bestMove, value, (new Date()).getTime() - g_startTime, i); + } + } + + if (finishMoveCallback != null) { + finishMoveCallback(bestMove, value, (new Date()).getTime() - g_startTime, i - 1); + } +} + +var minEval = -2000000; +var maxEval = +2000000; + +var minMateBuffer = minEval + 2000; +var maxMateBuffer = maxEval - 2000; + +var materialTable = [0, 800, 3350, 3450, 5000, 9750, 600000]; + +var pawnAdj = +[ + 0, 0, 0, 0, 0, 0, 0, 0, + -25, 105, 135, 270, 270, 135, 105, -25, + -80, 0, 30, 176, 176, 30, 0, -80, + -85, -5, 25, 175, 175, 25, -5, -85, + -90, -10, 20, 125, 125, 20, -10, -90, + -95, -15, 15, 75, 75, 15, -15, -95, + -100, -20, 10, 70, 70, 10, -20, -100, + 0, 0, 0, 0, 0, 0, 0, 0 +]; + +var knightAdj = + [-200, -100, -50, -50, -50, -50, -100, -200, + -100, 0, 0, 0, 0, 0, 0, -100, + -50, 0, 60, 60, 60, 60, 0, -50, + -50, 0, 30, 60, 60, 30, 0, -50, + -50, 0, 30, 60, 60, 30, 0, -50, + -50, 0, 30, 30, 30, 30, 0, -50, + -100, 0, 0, 0, 0, 0, 0, -100, + -200, -50, -25, -25, -25, -25, -50, -200 + ]; + +var bishopAdj = + [ -50,-50,-25,-10,-10,-25,-50,-50, + -50,-25,-10, 0, 0,-10,-25,-50, + -25,-10, 0, 25, 25, 0,-10,-25, + -10, 0, 25, 40, 40, 25, 0,-10, + -10, 0, 25, 40, 40, 25, 0,-10, + -25,-10, 0, 25, 25, 0,-10,-25, + -50,-25,-10, 0, 0,-10,-25,-50, + -50,-50,-25,-10,-10,-25,-50,-50 + ]; + +var rookAdj = + [ -60, -30, -10, 20, 20, -10, -30, -60, + 40, 70, 90,120,120, 90, 70, 40, + -60, -30, -10, 20, 20, -10, -30, -60, + -60, -30, -10, 20, 20, -10, -30, -60, + -60, -30, -10, 20, 20, -10, -30, -60, + -60, -30, -10, 20, 20, -10, -30, -60, + -60, -30, -10, 20, 20, -10, -30, -60, + -60, -30, -10, 20, 20, -10, -30, -60 + ]; + +var kingAdj = + [ 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 50, 150, -25, -125, -125, -25, 150, 50, + 150, 250, 75, -25, -25, 75, 250, 150 + ]; + +var emptyAdj = + [0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + ]; + +var pieceSquareAdj = new Array(8); + +// Returns the square flipped +var flipTable = new Array(256); + +function PawnEval(color) { + var pieceIdx = (color | 1) << 4; + var from = g_pieceList[pieceIdx++]; + while (from != 0) { + from = g_pieceList[pieceIdx++]; + } +} + +function Mobility(color) { + var result = 0; + var from, to, mob, pieceIdx; + var enemy = color == 8 ? 0x10 : 0x8 + var mobUnit = color == 8 ? g_mobUnit[0] : g_mobUnit[1]; + + // Knight mobility + mob = -3; + pieceIdx = (color | 2) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + mob += mobUnit[g_board[from + 31]]; + mob += mobUnit[g_board[from + 33]]; + mob += mobUnit[g_board[from + 14]]; + mob += mobUnit[g_board[from - 14]]; + mob += mobUnit[g_board[from - 31]]; + mob += mobUnit[g_board[from - 33]]; + mob += mobUnit[g_board[from + 18]]; + mob += mobUnit[g_board[from - 18]]; + from = g_pieceList[pieceIdx++]; + } + result += 65 * mob; + + // Bishop mobility + mob = -4; + pieceIdx = (color | 3) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 15; while (g_board[to] == 0) { to -= 15; mob++; } + if (g_board[to] & enemy) { + mob++; + if (!(g_board[to] & piecePawn)) { + to -= 15; while (g_board[to] == 0) to -= 15; + mob += mobUnit[g_board[to]] << 2; + } + } + + to = from - 17; while (g_board[to] == 0) { to -= 17; mob++; } + if (g_board[to] & enemy) { + mob++; + if (!(g_board[to] & piecePawn)) { + to -= 17; while (g_board[to] == 0) to -= 17; + mob += mobUnit[g_board[to]] << 2; + } + } + + to = from + 15; while (g_board[to] == 0) { to += 15; mob++; } + if (g_board[to] & enemy) { + mob++; + if (!(g_board[to] & piecePawn)) { + to += 15; while (g_board[to] == 0) to += 15; + mob += mobUnit[g_board[to]] << 2; + } + } + + to = from + 17; while (g_board[to] == 0) { to += 17; mob++; } + if (g_board[to] & enemy) { + mob++; + if (!(g_board[to] & piecePawn)) { + to += 17; while (g_board[to] == 0) to += 17; + mob += mobUnit[g_board[to]] << 2; + } + } + + from = g_pieceList[pieceIdx++]; + } + result += 44 * mob; + + // Rook mobility + mob = -4; + pieceIdx = (color | 4) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 1; while (g_board[to] == 0) { to--; mob++;} if (g_board[to] & enemy) mob++; + to = from + 1; while (g_board[to] == 0) { to++; mob++; } if (g_board[to] & enemy) mob++; + to = from + 16; while (g_board[to] == 0) { to += 16; mob++; } if (g_board[to] & enemy) mob++; + to = from - 16; while (g_board[to] == 0) { to -= 16; mob++; } if (g_board[to] & enemy) mob++; + from = g_pieceList[pieceIdx++]; + } + result += 25 * mob; + + // Queen mobility + mob = -2; + pieceIdx = (color | 5) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 15; while (g_board[to] == 0) { to -= 15; mob++; } if (g_board[to] & enemy) mob++; + to = from - 17; while (g_board[to] == 0) { to -= 17; mob++; } if (g_board[to] & enemy) mob++; + to = from + 15; while (g_board[to] == 0) { to += 15; mob++; } if (g_board[to] & enemy) mob++; + to = from + 17; while (g_board[to] == 0) { to += 17; mob++; } if (g_board[to] & enemy) mob++; + to = from - 1; while (g_board[to] == 0) { to--; mob++; } if (g_board[to] & enemy) mob++; + to = from + 1; while (g_board[to] == 0) { to++; mob++; } if (g_board[to] & enemy) mob++; + to = from + 16; while (g_board[to] == 0) { to += 16; mob++; } if (g_board[to] & enemy) mob++; + to = from - 16; while (g_board[to] == 0) { to -= 16; mob++; } if (g_board[to] & enemy) mob++; + from = g_pieceList[pieceIdx++]; + } + result += 22 * mob; + + return result; +} + +function Evaluate() { + var curEval = g_baseEval; + + var evalAdjust = 0; + // Black queen gone, then cancel white's penalty for king movement + if (g_pieceList[pieceQueen << 4] == 0) + evalAdjust -= pieceSquareAdj[pieceKing][g_pieceList[(colorWhite | pieceKing) << 4]]; + // White queen gone, then cancel black's penalty for king movement + if (g_pieceList[(colorWhite | pieceQueen) << 4] == 0) + evalAdjust += pieceSquareAdj[pieceKing][flipTable[g_pieceList[pieceKing << 4]]]; + + // Black bishop pair + if (g_pieceCount[pieceBishop] >= 2) + evalAdjust -= 500; + // White bishop pair + if (g_pieceCount[pieceBishop | colorWhite] >= 2) + evalAdjust += 500; + + var mobility = Mobility(8) - Mobility(0); + + if (g_toMove == 0) { + // Black + curEval -= mobility; + curEval -= evalAdjust; + } + else { + curEval += mobility; + curEval += evalAdjust; + } + + return curEval; +} + +function ScoreMove(move){ + var moveTo = (move >> 8) & 0xFF; + var captured = g_board[moveTo] & 0x7; + var piece = g_board[move & 0xFF]; + var score; + if (captured != 0) { + var pieceType = piece & 0x7; + score = (captured << 5) - pieceType; + } else { + score = historyTable[piece & 0xF][moveTo]; + } + return score; +} + +function QSearch(alpha, beta, ply) { + g_qNodeCount++; + + var realEval = g_inCheck ? (minEval + 1) : Evaluate(); + + if (realEval >= beta) + return realEval; + + if (realEval > alpha) + alpha = realEval; + + var moves = new Array(); + var moveScores = new Array(); + var wasInCheck = g_inCheck; + + if (wasInCheck) { + // TODO: Fast check escape generator and fast checking moves generator + GenerateCaptureMoves(moves, null); + GenerateAllMoves(moves); + + for (var i = 0; i < moves.length; i++) { + moveScores[i] = ScoreMove(moves[i]); + } + } else { + GenerateCaptureMoves(moves, null); + + for (var i = 0; i < moves.length; i++) { + var captured = g_board[(moves[i] >> 8) & 0xFF] & 0x7; + var pieceType = g_board[moves[i] & 0xFF] & 0x7; + + moveScores[i] = (captured << 5) - pieceType; + } + } + + for (var i = 0; i < moves.length; i++) { + var bestMove = i; + for (var j = moves.length - 1; j > i; j--) { + if (moveScores[j] > moveScores[bestMove]) { + bestMove = j; + } + } + { + var tmpMove = moves[i]; + moves[i] = moves[bestMove]; + moves[bestMove] = tmpMove; + + var tmpScore = moveScores[i]; + moveScores[i] = moveScores[bestMove]; + moveScores[bestMove] = tmpScore; + } + + if (!wasInCheck && !See(moves[i])) { + continue; + } + + if (!MakeMove(moves[i])) { + continue; + } + + var value = -QSearch(-beta, -alpha, ply - 1); + + UnmakeMove(moves[i]); + + if (value > realEval) { + if (value >= beta) + return value; + + if (value > alpha) + alpha = value; + + realEval = value; + } + } + + /* Disable checks... Too slow currently + + if (ply == 0 && !wasInCheck) { + moves = new Array(); + GenerateAllMoves(moves); + + for (var i = 0; i < moves.length; i++) { + moveScores[i] = ScoreMove(moves[i]); + } + + for (var i = 0; i < moves.length; i++) { + var bestMove = i; + for (var j = moves.length - 1; j > i; j--) { + if (moveScores[j] > moveScores[bestMove]) { + bestMove = j; + } + } + { + var tmpMove = moves[i]; + moves[i] = moves[bestMove]; + moves[bestMove] = tmpMove; + + var tmpScore = moveScores[i]; + moveScores[i] = moveScores[bestMove]; + moveScores[bestMove] = tmpScore; + } + + if (!MakeMove(moves[i])) { + continue; + } + var checking = g_inCheck; + UnmakeMove(moves[i]); + + if (!checking) { + continue; + } + + if (!See(moves[i])) { + continue; + } + + MakeMove(moves[i]); + + var value = -QSearch(-beta, -alpha, ply - 1); + + UnmakeMove(moves[i]); + + if (value > realEval) { + if (value >= beta) + return value; + + if (value > alpha) + alpha = value; + + realEval = value; + } + } + } + */ + + return realEval; +} + +function StoreHash(value, flags, ply, move, depth) { + if (value >= maxMateBuffer) + value += depth; + else if (value <= minMateBuffer) + value -= depth; + g_hashTable[g_hashKeyLow & g_hashMask] = new HashEntry(g_hashKeyHigh, value, flags, ply, move); +} + +function IsHashMoveValid(hashMove) { + var from = hashMove & 0xFF; + var to = (hashMove >> 8) & 0xFF; + var ourPiece = g_board[from]; + var pieceType = ourPiece & 0x7; + if (pieceType < piecePawn || pieceType > pieceKing) return false; + // Can't move a piece we don't control + if (g_toMove != (ourPiece & 0x8)) + return false; + // Can't move to a square that has something of the same color + if (g_board[to] != 0 && (g_toMove == (g_board[to] & 0x8))) + return false; + if (pieceType == piecePawn) { + if (hashMove & moveflagEPC) { + return false; + } + + // Valid moves are push, capture, double push, promotions + var dir = to - from; + if ((g_toMove == colorWhite) != (dir < 0)) { + // Pawns have to move in the right direction + return false; + } + + var row = to & 0xF0; + if (((row == 0x90 && !g_toMove) || + (row == 0x20 && g_toMove)) != (hashMove & moveflagPromotion)) { + // Handle promotions + return false; + } + + if (dir == -16 || dir == 16) { + // White/Black push + return g_board[to] == 0; + } else if (dir == -15 || dir == -17 || dir == 15 || dir == 17) { + // White/Black capture + return g_board[to] != 0; + } else if (dir == -32) { + // Double white push + if (row != 0x60) return false; + if (g_board[to] != 0) return false; + if (g_board[from - 16] != 0) return false; + } else if (dir == 32) { + // Double black push + if (row != 0x50) return false; + if (g_board[to] != 0) return false; + if (g_board[from + 16] != 0) return false; + } else { + return false; + } + + return true; + } else { + // This validates that this piece type can actually make the attack + if (hashMove >> 16) return false; + return IsSquareAttackableFrom(to, from); + } +} + +function IsRepDraw() { + var stop = g_moveCount - 1 - g_move50; + stop = stop < 0 ? 0 : stop; + for (var i = g_moveCount - 5; i >= stop; i -= 2) { + if (g_repMoveStack[i] == g_hashKeyLow) + return true; + } + return false; +} + +function MovePicker(hashMove, depth, killer1, killer2) { + this.hashMove = hashMove; + this.depth = depth; + this.killer1 = killer1; + this.killer2 = killer2; + + this.moves = new Array(); + this.losingCaptures = null; + this.moveCount = 0; + this.atMove = -1; + this.moveScores = null; + this.stage = 0; + + this.nextMove = function () { + if (++this.atMove == this.moveCount) { + this.stage++; + if (this.stage == 1) { + if (this.hashMove != null && IsHashMoveValid(hashMove)) { + this.moves[0] = hashMove; + this.moveCount = 1; + } + if (this.moveCount != 1) { + this.hashMove = null; + this.stage++; + } + } + + if (this.stage == 2) { + GenerateCaptureMoves(this.moves, null); + this.moveCount = this.moves.length; + this.moveScores = new Array(this.moveCount); + // Move ordering + for (var i = this.atMove; i < this.moveCount; i++) { + var captured = g_board[(this.moves[i] >> 8) & 0xFF] & 0x7; + var pieceType = g_board[this.moves[i] & 0xFF] & 0x7; + this.moveScores[i] = (captured << 5) - pieceType; + } + // No moves, onto next stage + if (this.atMove == this.moveCount) this.stage++; + } + + if (this.stage == 3) { + if (IsHashMoveValid(this.killer1) && + this.killer1 != this.hashMove) { + this.moves[this.moves.length] = this.killer1; + this.moveCount = this.moves.length; + } else { + this.killer1 = 0; + this.stage++; + } + } + + if (this.stage == 4) { + if (IsHashMoveValid(this.killer2) && + this.killer2 != this.hashMove) { + this.moves[this.moves.length] = this.killer2; + this.moveCount = this.moves.length; + } else { + this.killer2 = 0; + this.stage++; + } + } + + if (this.stage == 5) { + GenerateAllMoves(this.moves); + this.moveCount = this.moves.length; + // Move ordering + for (var i = this.atMove; i < this.moveCount; i++) this.moveScores[i] = ScoreMove(this.moves[i]); + // No moves, onto next stage + if (this.atMove == this.moveCount) this.stage++; + } + + if (this.stage == 6) { + // Losing captures + if (this.losingCaptures != null) { + for (var i = 0; i < this.losingCaptures.length; i++) { + this.moves[this.moves.length] = this.losingCaptures[i]; + } + for (var i = this.atMove; i < this.moveCount; i++) this.moveScores[i] = ScoreMove(this.moves[i]); + this.moveCount = this.moves.length; + } + // No moves, onto next stage + if (this.atMove == this.moveCount) this.stage++; + } + + if (this.stage == 7) + return 0; + } + + var bestMove = this.atMove; + for (var j = this.atMove + 1; j < this.moveCount; j++) { + if (this.moveScores[j] > this.moveScores[bestMove]) { + bestMove = j; + } + } + + if (bestMove != this.atMove) { + var tmpMove = this.moves[this.atMove]; + this.moves[this.atMove] = this.moves[bestMove]; + this.moves[bestMove] = tmpMove; + + var tmpScore = this.moveScores[this.atMove]; + this.moveScores[this.atMove] = this.moveScores[bestMove]; + this.moveScores[bestMove] = tmpScore; + } + + var candidateMove = this.moves[this.atMove]; + if ((this.stage > 1 && candidateMove == this.hashMove) || + (this.stage > 3 && candidateMove == this.killer1) || + (this.stage > 4 && candidateMove == this.killer2)) { + return this.nextMove(); + } + + if (this.stage == 2 && !See(candidateMove)) { + if (this.losingCaptures == null) { + this.losingCaptures = new Array(); + } + this.losingCaptures[this.losingCaptures.length] = candidateMove; + return this.nextMove(); + } + + return this.moves[this.atMove]; + } +} + +function AllCutNode(ply, depth, beta, allowNull) { + if (ply <= 0) { + return QSearch(beta - 1, beta, 0); + } + + if ((g_nodeCount & 127) == 127) { + if ((new Date()).getTime() - g_startTime > g_timeout) { + // Time cutoff + g_searchValid = false; + return beta - 1; + } + } + + g_nodeCount++; + + if (IsRepDraw()) + return 0; + + // Mate distance pruning + if (minEval + depth >= beta) + return beta; + + if (maxEval - (depth + 1) < beta) + return beta - 1; + + var hashMove = null; + var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; + if (hashNode != null && hashNode.lock == g_hashKeyHigh) { + hashMove = hashNode.bestMove; + if (hashNode.hashDepth >= ply) { + var hashValue = hashNode.value; + + // Fixup mate scores + if (hashValue >= maxMateBuffer) + hashValue -= depth; + else if (hashValue <= minMateBuffer) + hashValue += depth; + + if (hashNode.flags == hashflagExact) + return hashValue; + if (hashNode.flags == hashflagAlpha && hashValue < beta) + return hashValue; + if (hashNode.flags == hashflagBeta && hashValue >= beta) + return hashValue; + } + } + + // TODO - positional gain? + + if (!g_inCheck && + allowNull && + beta > minMateBuffer && + beta < maxMateBuffer) { + // Try some razoring + if (hashMove == null && + ply < 4) { + var razorMargin = 2500 + 200 * ply; + if (g_baseEval < beta - razorMargin) { + var razorBeta = beta - razorMargin; + var v = QSearch(razorBeta - 1, razorBeta, 0); + if (v < razorBeta) + return v; + } + } + + // TODO - static null move + + // Null move + if (ply > 1 && + g_baseEval >= beta - (ply >= 4 ? 2500 : 0) && + // Disable null move if potential zugzwang (no big pieces) + (g_pieceCount[pieceBishop | g_toMove] != 0 || + g_pieceCount[pieceKnight | g_toMove] != 0 || + g_pieceCount[pieceRook | g_toMove] != 0 || + g_pieceCount[pieceQueen | g_toMove] != 0)) { + var r = 3 + (ply >= 5 ? 1 : ply / 4); + if (g_baseEval - beta > 1500) r++; + + g_toMove = 8 - g_toMove; + g_baseEval = -g_baseEval; + g_hashKeyLow ^= g_zobristBlackLow; + g_hashKeyHigh ^= g_zobristBlackHigh; + + var value = -AllCutNode(ply - r, depth + 1, -(beta - 1), false); + + g_hashKeyLow ^= g_zobristBlackLow; + g_hashKeyHigh ^= g_zobristBlackHigh; + g_toMove = 8 - g_toMove; + g_baseEval = -g_baseEval; + + if (value >= beta) + return beta; + } + } + + var moveMade = false; + var realEval = minEval - 1; + var inCheck = g_inCheck; + + var movePicker = new MovePicker(hashMove, depth, g_killers[depth][0], g_killers[depth][1]); + + for (;;) { + var currentMove = movePicker.nextMove(); + if (currentMove == 0) { + break; + } + + var plyToSearch = ply - 1; + + if (!MakeMove(currentMove)) { + continue; + } + + var value; + var doFullSearch = true; + + if (g_inCheck) { + // Check extensions + plyToSearch++; + } else { + var reduced = plyToSearch - (movePicker.atMove > 14 ? 2 : 1); + + // Futility pruning +/* if (movePicker.stage == 5 && !inCheck) { + if (movePicker.atMove >= (15 + (1 << (5 * ply) >> 2)) && + realEval > minMateBuffer) { + UnmakeMove(currentMove); + continue; + } + + if (ply < 7) { + var reducedPly = reduced <= 0 ? 0 : reduced; + var futilityValue = -g_baseEval + (900 * (reducedPly + 2)) - (movePicker.atMove * 10); + if (futilityValue < beta) { + if (futilityValue > realEval) { + realEval = futilityValue; + } + UnmakeMove(currentMove); + continue; + } + } + }*/ + + // Late move reductions + if (movePicker.stage == 5 && movePicker.atMove > 5 && ply >= 3) { + value = -AllCutNode(reduced, depth + 1, -(beta - 1), true); + doFullSearch = (value >= beta); + } + } + + if (doFullSearch) { + value = -AllCutNode(plyToSearch, depth + 1, -(beta - 1), true); + } + + moveMade = true; + + UnmakeMove(currentMove); + + if (!g_searchValid) { + return beta - 1; + } + + if (value > realEval) { + if (value >= beta) { + var histTo = (currentMove >> 8) & 0xFF; + if (g_board[histTo] == 0) { + var histPiece = g_board[currentMove & 0xFF] & 0xF; + historyTable[histPiece][histTo] += ply * ply; + if (historyTable[histPiece][histTo] > 32767) { + historyTable[histPiece][histTo] >>= 1; + } + + if (g_killers[depth][0] != currentMove) { + g_killers[depth][1] = g_killers[depth][0]; + g_killers[depth][0] = currentMove; + } + } + + StoreHash(value, hashflagBeta, ply, currentMove, depth); + return value; + } + + realEval = value; + hashMove = currentMove; + } + } + + if (!moveMade) { + // If we have no valid moves it's either stalemate or checkmate + if (g_inCheck) + // Checkmate. + return minEval + depth; + else + // Stalemate + return 0; + } + + StoreHash(realEval, hashflagAlpha, ply, hashMove, depth); + + return realEval; +} + +function AlphaBeta(ply, depth, alpha, beta) { + if (ply <= 0) { + return QSearch(alpha, beta, 0); + } + + g_nodeCount++; + + if (depth > 0 && IsRepDraw()) + return 0; + + // Mate distance pruning + var oldAlpha = alpha; + alpha = alpha < minEval + depth ? alpha : minEval + depth; + beta = beta > maxEval - (depth + 1) ? beta : maxEval - (depth + 1); + if (alpha >= beta) + return alpha; + + var hashMove = null; + var hashFlag = hashflagAlpha; + var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; + if (hashNode != null && hashNode.lock == g_hashKeyHigh) { + hashMove = hashNode.bestMove; + } + + var inCheck = g_inCheck; + + var moveMade = false; + var realEval = minEval; + + var movePicker = new MovePicker(hashMove, depth, g_killers[depth][0], g_killers[depth][1]); + + for (;;) { + var currentMove = movePicker.nextMove(); + if (currentMove == 0) { + break; + } + + var plyToSearch = ply - 1; + + if (!MakeMove(currentMove)) { + continue; + } + + if (g_inCheck) { + // Check extensions + plyToSearch++; + } + + var value; + if (moveMade) { + value = -AllCutNode(plyToSearch, depth + 1, -alpha, true); + if (value > alpha) { + value = -AlphaBeta(plyToSearch, depth + 1, -beta, -alpha); + } + } else { + value = -AlphaBeta(plyToSearch, depth + 1, -beta, -alpha); + } + + moveMade = true; + + UnmakeMove(currentMove); + + if (!g_searchValid) { + return alpha; + } + + if (value > realEval) { + if (value >= beta) { + var histTo = (currentMove >> 8) & 0xFF; + if (g_board[histTo] == 0) { + var histPiece = g_board[currentMove & 0xFF] & 0xF; + historyTable[histPiece][histTo] += ply * ply; + if (historyTable[histPiece][histTo] > 32767) { + historyTable[histPiece][histTo] >>= 1; + } + + if (g_killers[depth][0] != currentMove) { + g_killers[depth][1] = g_killers[depth][0]; + g_killers[depth][0] = currentMove; + } + } + + StoreHash(value, hashflagBeta, ply, currentMove, depth); + return value; + } + + if (value > oldAlpha) { + hashFlag = hashflagExact; + alpha = value; + } + + realEval = value; + hashMove = currentMove; + } + } + + if (!moveMade) { + // If we have no valid moves it's either stalemate or checkmate + if (inCheck) + // Checkmate. + return minEval + depth; + else + // Stalemate + return 0; + } + + StoreHash(realEval, hashFlag, ply, hashMove, depth); + + return realEval; +} + +// +// Board code +// + +// This somewhat funky scheme means that a piece is indexed by it's lower 4 bits when accessing in arrays. The fifth bit (black bit) +// is used to allow quick edge testing on the board. +var colorBlack = 0x10; +var colorWhite = 0x08; + +var pieceEmpty = 0x00; +var piecePawn = 0x01; +var pieceKnight = 0x02; +var pieceBishop = 0x03; +var pieceRook = 0x04; +var pieceQueen = 0x05; +var pieceKing = 0x06; + +var g_vectorDelta = new Array(256); + +var g_bishopDeltas = [-15, -17, 15, 17]; +var g_knightDeltas = [31, 33, 14, -14, -31, -33, 18, -18]; +var g_rookDeltas = [-1, +1, -16, +16]; +var g_queenDeltas = [-1, +1, -15, +15, -17, +17, -16, +16]; + +var g_castleRightsMask = [ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 7,15,15,15, 3,15,15,11, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, +0, 0, 0, 0,13,15,15,15,12,15,15,14, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +var moveflagEPC = 0x2 << 16; +var moveflagCastleKing = 0x4 << 16; +var moveflagCastleQueen = 0x8 << 16; +var moveflagPromotion = 0x10 << 16; +var moveflagPromoteKnight = 0x20 << 16; +var moveflagPromoteQueen = 0x40 << 16; +var moveflagPromoteBishop = 0x80 << 16; + +function MT() { + var N = 624; + var M = 397; + var MAG01 = [0x0, 0x9908b0df]; + + this.mt = new Array(N); + this.mti = N + 1; + + this.setSeed = function() + { + var a = arguments; + switch (a.length) { + case 1: + if (a[0].constructor === Number) { + this.mt[0]= a[0]; + for (var i = 1; i < N; ++i) { + var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); + this.mt[i] = ((1812433253 * ((s & 0xffff0000) >>> 16)) + << 16) + + 1812433253 * (s & 0x0000ffff) + + i; + } + this.mti = N; + return; + } + + this.setSeed(19650218); + + var l = a[0].length; + var i = 1; + var j = 0; + + for (var k = N > l ? N : l; k != 0; --k) { + var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30) + this.mt[i] = (this.mt[i] + ^ (((1664525 * ((s & 0xffff0000) >>> 16)) << 16) + + 1664525 * (s & 0x0000ffff))) + + a[0][j] + + j; + if (++i >= N) { + this.mt[0] = this.mt[N - 1]; + i = 1; + } + if (++j >= l) { + j = 0; + } + } + + for (var k = N - 1; k != 0; --k) { + var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); + this.mt[i] = (this.mt[i] + ^ (((1566083941 * ((s & 0xffff0000) >>> 16)) << 16) + + 1566083941 * (s & 0x0000ffff))) + - i; + if (++i >= N) { + this.mt[0] = this.mt[N-1]; + i = 1; + } + } + + this.mt[0] = 0x80000000; + return; + default: + var seeds = new Array(); + for (var i = 0; i < a.length; ++i) { + seeds.push(a[i]); + } + this.setSeed(seeds); + return; + } + } + + this.setSeed(0x1BADF00D); + + this.next = function (bits) + { + if (this.mti >= N) { + var x = 0; + + for (var k = 0; k < N - M; ++k) { + x = (this.mt[k] & 0x80000000) | (this.mt[k + 1] & 0x7fffffff); + this.mt[k] = this.mt[k + M] ^ (x >>> 1) ^ MAG01[x & 0x1]; + } + for (var k = N - M; k < N - 1; ++k) { + x = (this.mt[k] & 0x80000000) | (this.mt[k + 1] & 0x7fffffff); + this.mt[k] = this.mt[k + (M - N)] ^ (x >>> 1) ^ MAG01[x & 0x1]; + } + x = (this.mt[N - 1] & 0x80000000) | (this.mt[0] & 0x7fffffff); + this.mt[N - 1] = this.mt[M - 1] ^ (x >>> 1) ^ MAG01[x & 0x1]; + + this.mti = 0; + } + + var y = this.mt[this.mti++]; + y ^= y >>> 11; + y ^= (y << 7) & 0x9d2c5680; + y ^= (y << 15) & 0xefc60000; + y ^= y >>> 18; + return (y >>> (32 - bits)) & 0xFFFFFFFF; + } +} + +// Position variables +var g_board = new Array(256); // Sentinel 0x80, pieces are in low 4 bits, 0x8 for color, 0x7 bits for piece type +var g_toMove; // side to move, 0 or 8, 0 = black, 8 = white +var g_castleRights; // bitmask representing castling rights, 1 = wk, 2 = wq, 4 = bk, 8 = bq +var g_enPassentSquare; +var g_baseEval; +var g_hashKeyLow, g_hashKeyHigh; +var g_inCheck; + +// Utility variables +var g_moveCount = 0; +var g_moveUndoStack = new Array(); + +var g_move50 = 0; +var g_repMoveStack = new Array(); + +var g_hashSize = 1 << 22; +var g_hashMask = g_hashSize - 1; +var g_hashTable; + +var g_killers; +var historyTable = new Array(32); + +var g_zobristLow; +var g_zobristHigh; +var g_zobristBlackLow; +var g_zobristBlackHigh; + +// Evaulation variables +var g_mobUnit; + +var hashflagAlpha = 1; +var hashflagBeta = 2; +var hashflagExact = 3; + +function HashEntry(lock, value, flags, hashDepth, bestMove, globalPly) { + this.lock = lock; + this.value = value; + this.flags = flags; + this.hashDepth = hashDepth; + this.bestMove = bestMove; +} + +function MakeSquare(row, column) { + return ((row + 2) << 4) | (column + 4); +} + +function MakeTable(table) { + var result = new Array(256); + for (var i = 0; i < 256; i++) { + result[i] = 0; + } + for (var row = 0; row < 8; row++) { + for (var col = 0; col < 8; col++) { + result[MakeSquare(row, col)] = table[row * 8 + col]; + } + } + return result; +} + +function ResetGame() { + g_killers = new Array(128); + for (var i = 0; i < 128; i++) { + g_killers[i] = [0, 0]; + } + + g_hashTable = new Array(g_hashSize); + + for (var i = 0; i < 32; i++) { + historyTable[i] = new Array(256); + for (var j = 0; j < 256; j++) + historyTable[i][j] = 0; + } + + var mt = new MT(0x1badf00d); + + g_zobristLow = new Array(256); + g_zobristHigh = new Array(256); + for (var i = 0; i < 256; i++) { + g_zobristLow[i] = new Array(16); + g_zobristHigh[i] = new Array(16); + for (var j = 0; j < 16; j++) { + g_zobristLow[i][j] = mt.next(32); + g_zobristHigh[i][j] = mt.next(32); + } + } + g_zobristBlackLow = mt.next(32); + g_zobristBlackHigh = mt.next(32); + + for (var row = 0; row < 8; row++) { + for (var col = 0; col < 8; col++) { + var square = MakeSquare(row, col); + flipTable[square] = MakeSquare(7 - row, col); + } + } + + pieceSquareAdj[piecePawn] = MakeTable(pawnAdj); + pieceSquareAdj[pieceKnight] = MakeTable(knightAdj); + pieceSquareAdj[pieceBishop] = MakeTable(bishopAdj); + pieceSquareAdj[pieceRook] = MakeTable(rookAdj); + pieceSquareAdj[pieceQueen] = MakeTable(emptyAdj); + pieceSquareAdj[pieceKing] = MakeTable(kingAdj); + + var pieceDeltas = [[], [], g_knightDeltas, g_bishopDeltas, g_rookDeltas, g_queenDeltas, g_queenDeltas]; + + for (var i = 0; i < 256; i++) { + g_vectorDelta[i] = new Object(); + g_vectorDelta[i].delta = 0; + g_vectorDelta[i].pieceMask = new Array(2); + g_vectorDelta[i].pieceMask[0] = 0; + g_vectorDelta[i].pieceMask[1] = 0; + } + + // Initialize the vector delta table + for (var row = 0; row < 0x80; row += 0x10) + for (var col = 0; col < 0x8; col++) { + var square = row | col; + + // Pawn moves + var index = square - (square - 17) + 128; + g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << piecePawn); + index = square - (square - 15) + 128; + g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << piecePawn); + + index = square - (square + 17) + 128; + g_vectorDelta[index].pieceMask[0] |= (1 << piecePawn); + index = square - (square + 15) + 128; + g_vectorDelta[index].pieceMask[0] |= (1 << piecePawn); + + for (var i = pieceKnight; i <= pieceKing; i++) { + for (var dir = 0; dir < pieceDeltas[i].length; dir++) { + var target = square + pieceDeltas[i][dir]; + while (!(target & 0x88)) { + index = square - target + 128; + + g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << i); + g_vectorDelta[index].pieceMask[0] |= (1 << i); + + var flip = -1; + if (square < target) + flip = 1; + + if ((square & 0xF0) == (target & 0xF0)) { + // On the same row + g_vectorDelta[index].delta = flip * 1; + } else if ((square & 0x0F) == (target & 0x0F)) { + // On the same column + g_vectorDelta[index].delta = flip * 16; + } else if ((square % 15) == (target % 15)) { + g_vectorDelta[index].delta = flip * 15; + } else if ((square % 17) == (target % 17)) { + g_vectorDelta[index].delta = flip * 17; + } + + if (i == pieceKnight) { + g_vectorDelta[index].delta = pieceDeltas[i][dir]; + break; + } + + if (i == pieceKing) + break; + + target += pieceDeltas[i][dir]; + } + } + } + } + + InitializeEval(); + InitializeFromFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); +} + +function InitializeEval() { + g_mobUnit = new Array(2); + for (var i = 0; i < 2; i++) { + g_mobUnit[i] = new Array(); + var enemy = i == 0 ? 0x10 : 8; + var friend = i == 0 ? 8 : 0x10; + g_mobUnit[i][0] = 1; + g_mobUnit[i][0x80] = 0; + g_mobUnit[i][enemy | piecePawn] = 1; + g_mobUnit[i][enemy | pieceBishop] = 2; + g_mobUnit[i][enemy | pieceKnight] = 2; + g_mobUnit[i][enemy | pieceRook] = 4; + g_mobUnit[i][enemy | pieceQueen] = 6; + g_mobUnit[i][enemy | pieceKing] = 6; + g_mobUnit[i][friend | piecePawn] = 0; + g_mobUnit[i][friend | pieceBishop] = 0; + g_mobUnit[i][friend | pieceKnight] = 0; + g_mobUnit[i][friend | pieceRook] = 0; + g_mobUnit[i][friend | pieceQueen] = 0; + g_mobUnit[i][friend | pieceKing] = 0; + } +} + +function SetHash() { + var result = new Object(); + result.hashKeyLow = 0; + result.hashKeyHigh = 0; + + for (var i = 0; i < 256; i++) { + var piece = g_board[i]; + if (piece & 0x18) { + result.hashKeyLow ^= g_zobristLow[i][piece & 0xF] + result.hashKeyHigh ^= g_zobristHigh[i][piece & 0xF] + } + } + + if (!g_toMove) { + result.hashKeyLow ^= g_zobristBlackLow; + result.hashKeyHigh ^= g_zobristBlackHigh; + } + + return result; +} + +function InitializeFromFen(fen) { + var chunks = fen.split(' '); + + for (var i = 0; i < 256; i++) + g_board[i] = 0x80; + + var row = 0; + var col = 0; + + var pieces = chunks[0]; + for (var i = 0; i < pieces.length; i++) { + var c = pieces.charAt(i); + + if (c == '/') { + row++; + col = 0; + } + else { + if (c >= '0' && c <= '9') { + for (var j = 0; j < parseInt(c); j++) { + g_board[MakeSquare(row, col)] = 0; + col++; + } + } + else { + var isBlack = c >= 'a' && c <= 'z'; + var piece = isBlack ? colorBlack : colorWhite; + if (!isBlack) + c = pieces.toLowerCase().charAt(i); + switch (c) { + case 'p': + piece |= piecePawn; + break; + case 'b': + piece |= pieceBishop; + break; + case 'n': + piece |= pieceKnight; + break; + case 'r': + piece |= pieceRook; + break; + case 'q': + piece |= pieceQueen; + break; + case 'k': + piece |= pieceKing; + break; + } + + g_board[MakeSquare(row, col)] = piece; + col++; + } + } + } + + InitializePieceList(); + + g_toMove = chunks[1].charAt(0) == 'w' ? colorWhite : 0; + var them = 8 - g_toMove; + + g_castleRights = 0; + if (chunks[2].indexOf('K') != -1) { + if (g_board[MakeSquare(7, 4)] != (pieceKing | colorWhite) || + g_board[MakeSquare(7, 7)] != (pieceRook | colorWhite)) { + return 'Invalid FEN: White kingside castling not allowed'; + } + g_castleRights |= 1; + } + if (chunks[2].indexOf('Q') != -1) { + if (g_board[MakeSquare(7, 4)] != (pieceKing | colorWhite) || + g_board[MakeSquare(7, 0)] != (pieceRook | colorWhite)) { + return 'Invalid FEN: White queenside castling not allowed'; + } + g_castleRights |= 2; + } + if (chunks[2].indexOf('k') != -1) { + if (g_board[MakeSquare(0, 4)] != (pieceKing | colorBlack) || + g_board[MakeSquare(0, 7)] != (pieceRook | colorBlack)) { + return 'Invalid FEN: Black kingside castling not allowed'; + } + g_castleRights |= 4; + } + if (chunks[2].indexOf('q') != -1) { + if (g_board[MakeSquare(0, 4)] != (pieceKing | colorBlack) || + g_board[MakeSquare(0, 0)] != (pieceRook | colorBlack)) { + return 'Invalid FEN: Black queenside castling not allowed'; + } + g_castleRights |= 8; + } + + g_enPassentSquare = -1; + if (chunks[3].indexOf('-') == -1) { + var col = chunks[3].charAt(0).charCodeAt() - 'a'.charCodeAt(); + var row = 8 - (chunks[3].charAt(1).charCodeAt() - '0'.charCodeAt()); + g_enPassentSquare = MakeSquare(row, col); + } + + var hashResult = SetHash(); + g_hashKeyLow = hashResult.hashKeyLow; + g_hashKeyHigh = hashResult.hashKeyHigh; + + g_baseEval = 0; + for (var i = 0; i < 256; i++) { + if (g_board[i] & colorWhite) { + g_baseEval += pieceSquareAdj[g_board[i] & 0x7][i]; + g_baseEval += materialTable[g_board[i] & 0x7]; + } else if (g_board[i] & colorBlack) { + g_baseEval -= pieceSquareAdj[g_board[i] & 0x7][flipTable[i]]; + g_baseEval -= materialTable[g_board[i] & 0x7]; + } + } + if (!g_toMove) g_baseEval = -g_baseEval; + + g_move50 = 0; + g_inCheck = IsSquareAttackable(g_pieceList[(g_toMove | pieceKing) << 4], them); + + // Check for king capture (invalid FEN) + if (IsSquareAttackable(g_pieceList[(them | pieceKing) << 4], g_toMove)) { + return 'Invalid FEN: Can capture king'; + } + + // Checkmate/stalemate + if (GenerateValidMoves().length == 0) { + return g_inCheck ? 'Checkmate' : 'Stalemate'; + } + + return ''; +} + +var g_pieceIndex = new Array(256); +var g_pieceList = new Array(2 * 8 * 16); +var g_pieceCount = new Array(2 * 8); + +function InitializePieceList() { + for (var i = 0; i < 16; i++) { + g_pieceCount[i] = 0; + for (var j = 0; j < 16; j++) { + // 0 is used as the terminator for piece lists + g_pieceList[(i << 4) | j] = 0; + } + } + + for (var i = 0; i < 256; i++) { + g_pieceIndex[i] = 0; + if (g_board[i] & (colorWhite | colorBlack)) { + var piece = g_board[i] & 0xF; + + g_pieceList[(piece << 4) | g_pieceCount[piece]] = i; + g_pieceIndex[i] = g_pieceCount[piece]; + g_pieceCount[piece]++; + } + } +} + +function MakeMove(move){ + var me = g_toMove >> 3; + var otherColor = 8 - g_toMove; + + var flags = move & 0xFF0000; + var to = (move >> 8) & 0xFF; + var from = move & 0xFF; + var captured = g_board[to]; + var piece = g_board[from]; + var epcEnd = to; + + if (flags & moveflagEPC) { + epcEnd = me ? (to + 0x10) : (to - 0x10); + captured = g_board[epcEnd]; + g_board[epcEnd] = pieceEmpty; + } + + g_moveUndoStack[g_moveCount] = new UndoHistory(g_enPassentSquare, g_castleRights, g_inCheck, g_baseEval, g_hashKeyLow, g_hashKeyHigh, g_move50, captured); + g_moveCount++; + + g_enPassentSquare = -1; + + if (flags) { + if (flags & moveflagCastleKing) { + if (IsSquareAttackable(from + 1, otherColor) || + IsSquareAttackable(from + 2, otherColor)) { + g_moveCount--; + return false; + } + + var rook = g_board[to + 1]; + + g_hashKeyLow ^= g_zobristLow[to + 1][rook & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to + 1][rook & 0xF]; + g_hashKeyLow ^= g_zobristLow[to - 1][rook & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to - 1][rook & 0xF]; + + g_board[to - 1] = rook; + g_board[to + 1] = pieceEmpty; + + g_baseEval -= pieceSquareAdj[rook & 0x7][me == 0 ? flipTable[to + 1] : (to + 1)]; + g_baseEval += pieceSquareAdj[rook & 0x7][me == 0 ? flipTable[to - 1] : (to - 1)]; + + var rookIndex = g_pieceIndex[to + 1]; + g_pieceIndex[to - 1] = rookIndex; + g_pieceList[((rook & 0xF) << 4) | rookIndex] = to - 1; + } else if (flags & moveflagCastleQueen) { + if (IsSquareAttackable(from - 1, otherColor) || + IsSquareAttackable(from - 2, otherColor)) { + g_moveCount--; + return false; + } + + var rook = g_board[to - 2]; + + g_hashKeyLow ^= g_zobristLow[to -2][rook & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to - 2][rook & 0xF]; + g_hashKeyLow ^= g_zobristLow[to + 1][rook & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to + 1][rook & 0xF]; + + g_board[to + 1] = rook; + g_board[to - 2] = pieceEmpty; + + g_baseEval -= pieceSquareAdj[rook & 0x7][me == 0 ? flipTable[to - 2] : (to - 2)]; + g_baseEval += pieceSquareAdj[rook & 0x7][me == 0 ? flipTable[to + 1] : (to + 1)]; + + var rookIndex = g_pieceIndex[to - 2]; + g_pieceIndex[to + 1] = rookIndex; + g_pieceList[((rook & 0xF) << 4) | rookIndex] = to + 1; + } + } + + if (captured) { + // Remove our piece from the piece list + var capturedType = captured & 0xF; + g_pieceCount[capturedType]--; + var lastPieceSquare = g_pieceList[(capturedType << 4) | g_pieceCount[capturedType]]; + g_pieceIndex[lastPieceSquare] = g_pieceIndex[epcEnd]; + g_pieceList[(capturedType << 4) | g_pieceIndex[lastPieceSquare]] = lastPieceSquare; + g_pieceList[(capturedType << 4) | g_pieceCount[capturedType]] = 0; + + g_baseEval += materialTable[captured & 0x7]; + g_baseEval += pieceSquareAdj[captured & 0x7][me ? flipTable[epcEnd] : epcEnd]; + + g_hashKeyLow ^= g_zobristLow[epcEnd][capturedType]; + g_hashKeyHigh ^= g_zobristHigh[epcEnd][capturedType]; + g_move50 = 0; + } else if ((piece & 0x7) == piecePawn) { + var diff = to - from; + if (diff < 0) diff = -diff; + if (diff > 16) { + g_enPassentSquare = me ? (to + 0x10) : (to - 0x10); + } + g_move50 = 0; + } + + g_hashKeyLow ^= g_zobristLow[from][piece & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[from][piece & 0xF]; + g_hashKeyLow ^= g_zobristLow[to][piece & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to][piece & 0xF]; + g_hashKeyLow ^= g_zobristBlackLow; + g_hashKeyHigh ^= g_zobristBlackHigh; + + g_castleRights &= g_castleRightsMask[from] & g_castleRightsMask[to]; + + g_baseEval -= pieceSquareAdj[piece & 0x7][me == 0 ? flipTable[from] : from]; + + // Move our piece in the piece list + g_pieceIndex[to] = g_pieceIndex[from]; + g_pieceList[((piece & 0xF) << 4) | g_pieceIndex[to]] = to; + + if (flags & moveflagPromotion) { + var newPiece = piece & (~0x7); + if (flags & moveflagPromoteKnight) + newPiece |= pieceKnight; + else if (flags & moveflagPromoteQueen) + newPiece |= pieceQueen; + else if (flags & moveflagPromoteBishop) + newPiece |= pieceBishop; + else + newPiece |= pieceRook; + + g_hashKeyLow ^= g_zobristLow[to][piece & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to][piece & 0xF]; + g_board[to] = newPiece; + g_hashKeyLow ^= g_zobristLow[to][newPiece & 0xF]; + g_hashKeyHigh ^= g_zobristHigh[to][newPiece & 0xF]; + + g_baseEval += pieceSquareAdj[newPiece & 0x7][me == 0 ? flipTable[to] : to]; + g_baseEval -= materialTable[piecePawn]; + g_baseEval += materialTable[newPiece & 0x7]; + + var pawnType = piece & 0xF; + var promoteType = newPiece & 0xF; + + g_pieceCount[pawnType]--; + + var lastPawnSquare = g_pieceList[(pawnType << 4) | g_pieceCount[pawnType]]; + g_pieceIndex[lastPawnSquare] = g_pieceIndex[to]; + g_pieceList[(pawnType << 4) | g_pieceIndex[lastPawnSquare]] = lastPawnSquare; + g_pieceList[(pawnType << 4) | g_pieceCount[pawnType]] = 0; + g_pieceIndex[to] = g_pieceCount[promoteType]; + g_pieceList[(promoteType << 4) | g_pieceIndex[to]] = to; + g_pieceCount[promoteType]++; + } else { + g_board[to] = g_board[from]; + + g_baseEval += pieceSquareAdj[piece & 0x7][me == 0 ? flipTable[to] : to]; + } + g_board[from] = pieceEmpty; + + g_toMove = otherColor; + g_baseEval = -g_baseEval; + + if ((piece & 0x7) == pieceKing || g_inCheck) { + if (IsSquareAttackable(g_pieceList[(pieceKing | (8 - g_toMove)) << 4], otherColor)) { + UnmakeMove(move); + return false; + } + } else { + var kingPos = g_pieceList[(pieceKing | (8 - g_toMove)) << 4]; + + if (ExposesCheck(from, kingPos)) { + UnmakeMove(move); + return false; + } + + if (epcEnd != to) { + if (ExposesCheck(epcEnd, kingPos)) { + UnmakeMove(move); + return false; + } + } + } + + g_inCheck = false; + + if (flags <= moveflagEPC) { + var theirKingPos = g_pieceList[(pieceKing | g_toMove) << 4]; + + // First check if the piece we moved can attack the enemy king + g_inCheck = IsSquareAttackableFrom(theirKingPos, to); + + if (!g_inCheck) { + // Now check if the square we moved from exposes check on the enemy king + g_inCheck = ExposesCheck(from, theirKingPos); + + if (!g_inCheck) { + // Finally, ep. capture can cause another square to be exposed + if (epcEnd != to) { + g_inCheck = ExposesCheck(epcEnd, theirKingPos); + } + } + } + } + else { + // Castle or promotion, slow check + g_inCheck = IsSquareAttackable(g_pieceList[(pieceKing | g_toMove) << 4], 8 - g_toMove); + } + + g_repMoveStack[g_moveCount - 1] = g_hashKeyLow; + g_move50++; + + return true; +} + +function UnmakeMove(move){ + g_toMove = 8 - g_toMove; + g_baseEval = -g_baseEval; + + g_moveCount--; + g_enPassentSquare = g_moveUndoStack[g_moveCount].ep; + g_castleRights = g_moveUndoStack[g_moveCount].castleRights; + g_inCheck = g_moveUndoStack[g_moveCount].inCheck; + g_baseEval = g_moveUndoStack[g_moveCount].baseEval; + g_hashKeyLow = g_moveUndoStack[g_moveCount].hashKeyLow; + g_hashKeyHigh = g_moveUndoStack[g_moveCount].hashKeyHigh; + g_move50 = g_moveUndoStack[g_moveCount].move50; + + var otherColor = 8 - g_toMove; + var me = g_toMove >> 3; + var them = otherColor >> 3; + + var flags = move & 0xFF0000; + var captured = g_moveUndoStack[g_moveCount].captured; + var to = (move >> 8) & 0xFF; + var from = move & 0xFF; + + var piece = g_board[to]; + + if (flags) { + if (flags & moveflagCastleKing) { + var rook = g_board[to - 1]; + g_board[to + 1] = rook; + g_board[to - 1] = pieceEmpty; + + var rookIndex = g_pieceIndex[to - 1]; + g_pieceIndex[to + 1] = rookIndex; + g_pieceList[((rook & 0xF) << 4) | rookIndex] = to + 1; + } + else if (flags & moveflagCastleQueen) { + var rook = g_board[to + 1]; + g_board[to - 2] = rook; + g_board[to + 1] = pieceEmpty; + + var rookIndex = g_pieceIndex[to + 1]; + g_pieceIndex[to - 2] = rookIndex; + g_pieceList[((rook & 0xF) << 4) | rookIndex] = to - 2; + } + } + + if (flags & moveflagPromotion) { + piece = (g_board[to] & (~0x7)) | piecePawn; + g_board[from] = piece; + + var pawnType = g_board[from] & 0xF; + var promoteType = g_board[to] & 0xF; + + g_pieceCount[promoteType]--; + + var lastPromoteSquare = g_pieceList[(promoteType << 4) | g_pieceCount[promoteType]]; + g_pieceIndex[lastPromoteSquare] = g_pieceIndex[to]; + g_pieceList[(promoteType << 4) | g_pieceIndex[lastPromoteSquare]] = lastPromoteSquare; + g_pieceList[(promoteType << 4) | g_pieceCount[promoteType]] = 0; + g_pieceIndex[to] = g_pieceCount[pawnType]; + g_pieceList[(pawnType << 4) | g_pieceIndex[to]] = to; + g_pieceCount[pawnType]++; + } + else { + g_board[from] = g_board[to]; + } + + var epcEnd = to; + if (flags & moveflagEPC) { + if (g_toMove == colorWhite) + epcEnd = to + 0x10; + else + epcEnd = to - 0x10; + g_board[to] = pieceEmpty; + } + + g_board[epcEnd] = captured; + + // Move our piece in the piece list + g_pieceIndex[from] = g_pieceIndex[to]; + g_pieceList[((piece & 0xF) << 4) | g_pieceIndex[from]] = from; + + if (captured) { + // Restore our piece to the piece list + var captureType = captured & 0xF; + g_pieceIndex[epcEnd] = g_pieceCount[captureType]; + g_pieceList[(captureType << 4) | g_pieceCount[captureType]] = epcEnd; + g_pieceCount[captureType]++; + } +} + +function ExposesCheck(from, kingPos){ + var index = kingPos - from + 128; + // If a queen can't reach it, nobody can! + if ((g_vectorDelta[index].pieceMask[0] & (1 << (pieceQueen))) != 0) { + var delta = g_vectorDelta[index].delta; + var pos = kingPos + delta; + while (g_board[pos] == 0) pos += delta; + + var piece = g_board[pos]; + if (((piece & (g_board[kingPos] ^ 0x18)) & 0x18) == 0) + return false; + + // Now see if the piece can actually attack the king + var backwardIndex = pos - kingPos + 128; + return (g_vectorDelta[backwardIndex].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) != 0; + } + return false; +} + +function IsSquareOnPieceLine(target, from) { + var index = from - target + 128; + var piece = g_board[from]; + return (g_vectorDelta[index].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) ? true : false; +} + +function IsSquareAttackableFrom(target, from){ + var index = from - target + 128; + var piece = g_board[from]; + if (g_vectorDelta[index].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) { + // Yes, this square is pseudo-attackable. Now, check for real attack + var inc = g_vectorDelta[index].delta; + do { + from += inc; + if (from == target) + return true; + } while (g_board[from] == 0); + } + + return false; +} + +function IsSquareAttackable(target, color) { + // Attackable by pawns? + var inc = color ? -16 : 16; + var pawn = (color ? colorWhite : colorBlack) | 1; + if (g_board[target - (inc - 1)] == pawn) + return true; + if (g_board[target - (inc + 1)] == pawn) + return true; + + // Attackable by pieces? + for (var i = 2; i <= 6; i++) { + var index = (color | i) << 4; + var square = g_pieceList[index]; + while (square != 0) { + if (IsSquareAttackableFrom(target, square)) + return true; + square = g_pieceList[++index]; + } + } + return false; +} + +function GenerateMove(from, to) { + return from | (to << 8); +} + +function GenerateMove(from, to, flags){ + return from | (to << 8) | flags; +} + +function GenerateValidMoves() { + var moveList = new Array(); + var allMoves = new Array(); + GenerateCaptureMoves(allMoves, null); + GenerateAllMoves(allMoves); + + for (var i = allMoves.length - 1; i >= 0; i--) { + if (MakeMove(allMoves[i])) { + moveList[moveList.length] = allMoves[i]; + UnmakeMove(allMoves[i]); + } + } + + return moveList; +} + +function GenerateAllMoves(moveStack) { + var from, to, piece, pieceIdx; + + // Pawn quiet moves + pieceIdx = (g_toMove | 1) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + GeneratePawnMoves(moveStack, from); + from = g_pieceList[pieceIdx++]; + } + + // Knight quiet moves + pieceIdx = (g_toMove | 2) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from + 31; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 33; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 14; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 14; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 31; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 33; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 18; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 18; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + from = g_pieceList[pieceIdx++]; + } + + // Bishop quiet moves + pieceIdx = (g_toMove | 3) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 15; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 15; } + to = from - 17; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 17; } + to = from + 15; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 15; } + to = from + 17; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 17; } + from = g_pieceList[pieceIdx++]; + } + + // Rook quiet moves + pieceIdx = (g_toMove | 4) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 1; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to--; } + to = from + 1; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to++; } + to = from + 16; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 16; } + to = from - 16; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 16; } + from = g_pieceList[pieceIdx++]; + } + + // Queen quiet moves + pieceIdx = (g_toMove | 5) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from - 15; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 15; } + to = from - 17; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 17; } + to = from + 15; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 15; } + to = from + 17; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 17; } + to = from - 1; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to--; } + to = from + 1; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to++; } + to = from + 16; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 16; } + to = from - 16; while (g_board[to] == 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 16; } + from = g_pieceList[pieceIdx++]; + } + + // King quiet moves + { + pieceIdx = (g_toMove | 6) << 4; + from = g_pieceList[pieceIdx]; + to = from - 15; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 17; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 15; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 17; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 1; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 1; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 16; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 16; if (g_board[to] == 0) moveStack[moveStack.length] = GenerateMove(from, to); + + if (!g_inCheck) { + var castleRights = g_castleRights; + if (!g_toMove) + castleRights >>= 2; + if (castleRights & 1) { + // Kingside castle + if (g_board[from + 1] == pieceEmpty && g_board[from + 2] == pieceEmpty) { + moveStack[moveStack.length] = GenerateMove(from, from + 0x02, moveflagCastleKing); + } + } + if (castleRights & 2) { + // Queenside castle + if (g_board[from - 1] == pieceEmpty && g_board[from - 2] == pieceEmpty && g_board[from - 3] == pieceEmpty) { + moveStack[moveStack.length] = GenerateMove(from, from - 0x02, moveflagCastleQueen); + } + } + } + } +} + +function GenerateCaptureMoves(moveStack, moveScores) { + var from, to, piece, pieceIdx; + var inc = (g_toMove == 8) ? -16 : 16; + var enemy = g_toMove == 8 ? 0x10 : 0x8; + + // Pawn captures + pieceIdx = (g_toMove | 1) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from + inc - 1; + if (g_board[to] & enemy) { + MovePawnTo(moveStack, from, to); + } + + to = from + inc + 1; + if (g_board[to] & enemy) { + MovePawnTo(moveStack, from, to); + } + + from = g_pieceList[pieceIdx++]; + } + + if (g_enPassentSquare != -1) { + var inc = (g_toMove == colorWhite) ? -16 : 16; + var pawn = g_toMove | piecePawn; + + var from = g_enPassentSquare - (inc + 1); + if ((g_board[from] & 0xF) == pawn) { + moveStack[moveStack.length] = GenerateMove(from, g_enPassentSquare, moveflagEPC); + } + + from = g_enPassentSquare - (inc - 1); + if ((g_board[from] & 0xF) == pawn) { + moveStack[moveStack.length] = GenerateMove(from, g_enPassentSquare, moveflagEPC); + } + } + + // Knight captures + pieceIdx = (g_toMove | 2) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from + 31; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 33; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 14; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 14; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 31; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 33; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 18; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 18; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + from = g_pieceList[pieceIdx++]; + } + + // Bishop captures + pieceIdx = (g_toMove | 3) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from; do { to -= 15; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to -= 17; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 15; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 17; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + from = g_pieceList[pieceIdx++]; + } + + // Rook captures + pieceIdx = (g_toMove | 4) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from; do { to--; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to++; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to -= 16; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 16; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + from = g_pieceList[pieceIdx++]; + } + + // Queen captures + pieceIdx = (g_toMove | 5) << 4; + from = g_pieceList[pieceIdx++]; + while (from != 0) { + to = from; do { to -= 15; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to -= 17; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 15; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 17; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to--; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to++; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to -= 16; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from; do { to += 16; } while (g_board[to] == 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + from = g_pieceList[pieceIdx++]; + } + + // King captures + { + pieceIdx = (g_toMove | 6) << 4; + from = g_pieceList[pieceIdx]; + to = from - 15; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 17; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 15; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 17; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 1; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 1; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from - 16; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + to = from + 16; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); + } +} + +function MovePawnTo(moveStack, start, square) { + var row = square & 0xF0; + if ((row == 0x90) || (row == 0x20)) { + moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteQueen); + moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteKnight); + moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteBishop); + moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion); + } + else { + moveStack[moveStack.length] = GenerateMove(start, square, 0); + } +} + +function GeneratePawnMoves(moveStack, from) { + var piece = g_board[from]; + var color = piece & colorWhite; + var inc = (color == colorWhite) ? -16 : 16; + + // Quiet pawn moves + var to = from + inc; + if (g_board[to] == 0) { + MovePawnTo(moveStack, from, to, pieceEmpty); + + // Check if we can do a 2 square jump + if ((((from & 0xF0) == 0x30) && color != colorWhite) || + (((from & 0xF0) == 0x80) && color == colorWhite)) { + to += inc; + if (g_board[to] == 0) { + moveStack[moveStack.length] = GenerateMove(from, to); + } + } + } +} + +function UndoHistory(ep, castleRights, inCheck, baseEval, hashKeyLow, hashKeyHigh, move50, captured) { + this.ep = ep; + this.castleRights = castleRights; + this.inCheck = inCheck; + this.baseEval = baseEval; + this.hashKeyLow = hashKeyLow; + this.hashKeyHigh = hashKeyHigh; + this.move50 = move50; + this.captured = captured; +} + +var g_seeValues = [0, 1, 3, 3, 5, 9, 900, 0, + 0, 1, 3, 3, 5, 9, 900, 0]; + +function See(move) { + var from = move & 0xFF; + var to = (move >> 8) & 0xFF; + + var fromPiece = g_board[from]; + + var fromValue = g_seeValues[fromPiece & 0xF]; + var toValue = g_seeValues[g_board[to] & 0xF]; + + if (fromValue <= toValue) { + return true; + } + + if (move >> 16) { + // Castles, promotion, ep are always good + return true; + } + + var us = (fromPiece & colorWhite) ? colorWhite : 0; + var them = 8 - us; + + // Pawn attacks + // If any opponent pawns can capture back, this capture is probably not worthwhile (as we must be using knight or above). + var inc = (fromPiece & colorWhite) ? -16 : 16; // Note: this is capture direction from to, so reversed from normal move direction + if (((g_board[to + inc + 1] & 0xF) == (piecePawn | them)) || + ((g_board[to + inc - 1] & 0xF) == (piecePawn | them))) { + return false; + } + + var themAttacks = new Array(); + + // Knight attacks + // If any opponent knights can capture back, and the deficit we have to make up is greater than the knights value, + // it's not worth it. We can capture on this square again, and the opponent doesn't have to capture back. + var captureDeficit = fromValue - toValue; + SeeAddKnightAttacks(to, them, themAttacks); + if (themAttacks.length != 0 && captureDeficit > g_seeValues[pieceKnight]) { + return false; + } + + // Slider attacks + g_board[from] = 0; + for (var pieceType = pieceBishop; pieceType <= pieceQueen; pieceType++) { + if (SeeAddSliderAttacks(to, them, themAttacks, pieceType)) { + if (captureDeficit > g_seeValues[pieceType]) { + g_board[from] = fromPiece; + return false; + } + } + } + + // Pawn defenses + // At this point, we are sure we are making a "losing" capture. The opponent can not capture back with a + // pawn. They cannot capture back with a minor/major and stand pat either. So, if we can capture with + // a pawn, it's got to be a winning or equal capture. + if (((g_board[to - inc + 1] & 0xF) == (piecePawn | us)) || + ((g_board[to - inc - 1] & 0xF) == (piecePawn | us))) { + g_board[from] = fromPiece; + return true; + } + + // King attacks + SeeAddSliderAttacks(to, them, themAttacks, pieceKing); + + // Our attacks + var usAttacks = new Array(); + SeeAddKnightAttacks(to, us, usAttacks); + for (var pieceType = pieceBishop; pieceType <= pieceKing; pieceType++) { + SeeAddSliderAttacks(to, us, usAttacks, pieceType); + } + + g_board[from] = fromPiece; + + // We are currently winning the amount of material of the captured piece, time to see if the opponent + // can get it back somehow. We assume the opponent can capture our current piece in this score, which + // simplifies the later code considerably. + var seeValue = toValue - fromValue; + + for (; ; ) { + var capturingPieceValue = 1000; + var capturingPieceIndex = -1; + + // Find the least valuable piece of the opponent that can attack the square + for (var i = 0; i < themAttacks.length; i++) { + if (themAttacks[i] != 0) { + var pieceValue = g_seeValues[g_board[themAttacks[i]] & 0x7]; + if (pieceValue < capturingPieceValue) { + capturingPieceValue = pieceValue; + capturingPieceIndex = i; + } + } + } + + if (capturingPieceIndex == -1) { + // Opponent can't capture back, we win + return true; + } + + // Now, if seeValue < 0, the opponent is winning. If even after we take their piece, + // we can't bring it back to 0, then we have lost this battle. + seeValue += capturingPieceValue; + if (seeValue < 0) { + return false; + } + + var capturingPieceSquare = themAttacks[capturingPieceIndex]; + themAttacks[capturingPieceIndex] = 0; + + // Add any x-ray attackers + SeeAddXrayAttack(to, capturingPieceSquare, us, usAttacks, themAttacks); + + // Our turn to capture + capturingPieceValue = 1000; + capturingPieceIndex = -1; + + // Find our least valuable piece that can attack the square + for (var i = 0; i < usAttacks.length; i++) { + if (usAttacks[i] != 0) { + var pieceValue = g_seeValues[g_board[usAttacks[i]] & 0x7]; + if (pieceValue < capturingPieceValue) { + capturingPieceValue = pieceValue; + capturingPieceIndex = i; + } + } + } + + if (capturingPieceIndex == -1) { + // We can't capture back, we lose :( + return false; + } + + // Assume our opponent can capture us back, and if we are still winning, we can stand-pat + // here, and assume we've won. + seeValue -= capturingPieceValue; + if (seeValue >= 0) { + return true; + } + + capturingPieceSquare = usAttacks[capturingPieceIndex]; + usAttacks[capturingPieceIndex] = 0; + + // Add any x-ray attackers + SeeAddXrayAttack(to, capturingPieceSquare, us, usAttacks, themAttacks); + } +} + +function SeeAddXrayAttack(target, square, us, usAttacks, themAttacks) { + var index = square - target + 128; + var delta = -g_vectorDelta[index].delta; + if (delta == 0) + return; + square += delta; + while (g_board[square] == 0) { + square += delta; + } + + if ((g_board[square] & 0x18) && IsSquareOnPieceLine(target, square)) { + if ((g_board[square] & 8) == us) { + usAttacks[usAttacks.length] = square; + } else { + themAttacks[themAttacks.length] = square; + } + } +} + +// target = attacking square, us = color of knights to look for, attacks = array to add squares to +function SeeAddKnightAttacks(target, us, attacks) { + var pieceIdx = (us | pieceKnight) << 4; + var attackerSq = g_pieceList[pieceIdx++]; + + while (attackerSq != 0) { + if (IsSquareOnPieceLine(target, attackerSq)) { + attacks[attacks.length] = attackerSq; + } + attackerSq = g_pieceList[pieceIdx++]; + } +} + +function SeeAddSliderAttacks(target, us, attacks, pieceType) { + var pieceIdx = (us | pieceType) << 4; + var attackerSq = g_pieceList[pieceIdx++]; + var hit = false; + + while (attackerSq != 0) { + if (IsSquareAttackableFrom(target, attackerSq)) { + attacks[attacks.length] = attackerSq; + hit = true; + } + attackerSq = g_pieceList[pieceIdx++]; + } + + return hit; +} + +function BuildPVMessage(bestMove, value, timeTaken, ply) { + var totalNodes = g_nodeCount + g_qNodeCount; + return "Ply:" + ply + " Score:" + value + " Nodes:" + totalNodes + " NPS:" + ((totalNodes / (timeTaken / 1000)) | 0) + " " + PVFromHash(bestMove, 15); +} + +////////////////////////////////////////////////// +// Test Harness +////////////////////////////////////////////////// +function FinishPlyCallback(bestMove, value, timeTaken, ply) { + postMessage("pv " + BuildPVMessage(bestMove, value, timeTaken, ply)); +} + +function FinishMoveLocalTesting(bestMove, value, timeTaken, ply) { + if (bestMove != null) { + MakeMove(bestMove); + postMessage(FormatMove(bestMove)); + } +} + +var needsReset = true; +self.onmessage = function (e) { + if (e.data == "go" || needsReset) { + ResetGame(); + needsReset = false; + if (e.data == "go") return; + } + if (e.data.match("^position") == "position") { + ResetGame(); + var result = InitializeFromFen(e.data.substr(9, e.data.length - 9)); + if (result.length != 0) { + postMessage("message " + result); + } + } else if (e.data.match("^search") == "search") { + g_timeout = parseInt(e.data.substr(7, e.data.length - 7), 10); + Search(FinishMoveLocalTesting, 99, FinishPlyCallback); + } else if (e.data == "analyze") { + g_timeout = 99999999999; + Search(null, 99, FinishPlyCallback); + } else { + MakeMove(GetMoveFromString(e.data)); + } +} diff --git a/demo/common.css b/demo/common.css new file mode 100644 index 0000000000..5641fffcc9 --- /dev/null +++ b/demo/common.css @@ -0,0 +1,22 @@ +body { + text-align: center; + font-family: 'Helvetica Neue Light','Helvetica Neue','Source Sans Pro',sans-serif; +} +article { + padding-left: 20%; + padding-right: 20%; +} +h2 { + font-size: 24px; + text-transform: uppercase; + padding-bottom: 30px; + margin: 32px 0 0; + font-weight: bold; + color: #3c4260; +} +article h2 { + border-bottom: 1px solid #d1d2d8; +} +#paper { + margin: 16px 0 32px; +} \ No newline at end of file diff --git a/demo/devs/index.html b/demo/devs/index.html new file mode 100644 index 0000000000..ad344bd470 --- /dev/null +++ b/demo/devs/index.html @@ -0,0 +1,14 @@ + + + + + + + Page Redirection + + + If you are not redirected automatically, follow the link to example + + \ No newline at end of file diff --git a/demo/erd/css/erd.css b/demo/erd/css/erd.css new file mode 100644 index 0000000000..903da8a19f --- /dev/null +++ b/demo/erd/css/erd.css @@ -0,0 +1,15 @@ +#paper { + width: inherit; + display: block; + border: 0; + margin-bottom: 3px; + text-align: center; + background: #464a65; +} +#paper>svg { + overflow: visible; +} +.connection, +.connection-wrap { + stroke: #ccc0ef; +} \ No newline at end of file diff --git a/demo/erd/index.html b/demo/erd/index.html new file mode 100644 index 0000000000..475bccbced --- /dev/null +++ b/demo/erd/index.html @@ -0,0 +1,27 @@ + + + + + + ER Diagrams + + + + + + +
+

ER Diagrams

+
+ +
+ + + + + + + + + + \ No newline at end of file diff --git a/demo/erd/src/erd.js b/demo/erd/src/erd.js new file mode 100644 index 0000000000..dbaf59d55b --- /dev/null +++ b/demo/erd/src/erd.js @@ -0,0 +1,311 @@ +var erd = joint.shapes.erd; + +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + el: document.getElementById('paper'), + width: 695, + height: 600, + gridSize: 1, + model: graph, + linkPinning: false, + linkConnectionPoint: joint.util.shapePerimeterConnectionPoint +}); + +// Custom highlighter - display an outline around each element that fits its shape. + +var highlighter = V('path', { + 'stroke': '#e9fc03', + 'stroke-width': '2px', + 'fill': 'transparent', + 'pointer-events': 'none' +}); + +// Define a specific highligthing path for every shape. + +erd.Attribute.prototype.getHighlighterPath = function(w, h) { + + return ['M', 0, h / 2, 'A', w / 2, h / 2, '0 1,0', w, h / 2, 'A', w / 2, h / 2, '0 1,0', 0, h / 2].join(' '); +}; + +erd.Entity.prototype.getHighlighterPath = function(w, h) { + + return ['M', w, 0, w, h, 0, h, 0, 0, 'z'].join(' '); +}; + +erd.Relationship.prototype.getHighlighterPath = function(w, h) { + + return ['M', w / 2, 0, w, w / 2, w / 2, w, 0, w / 2, 'z'].join(' '); +}; + +erd.ISA.prototype.getHighlighterPath = function(w, h) { + + return ['M', -8, 1, w + 8, 1, w / 2, h + 2, 'z'].join(' '); +}; + +// Unbind orignal highligting handlers. +paper.off('cell:highlight cell:unhighlight'); + +// Bind custom ones. +paper.on('cell:highlight', function(cellView) { + + var padding = 5; + + var bbox = g.rect(cellView.getBBox({ useModelGeometry: true })).moveAndExpand({ + x: -padding, + y: -padding, + width: 2 * padding, + height: 2 * padding + }); + + highlighter.translate(bbox.x, bbox.y, { absolute: true }); + highlighter.attr('d', cellView.model.getHighlighterPath(bbox.width, bbox.height)); + + V(paper.viewport).append(highlighter); +}); + +paper.on('cell:unhighlight', function() { + + highlighter.remove(); +}); + +// Create shapes + +var employee = new erd.Entity({ + + position: { x: 100, y: 200 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Employee', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.outer, .inner': { + fill: '#31d0c6', + stroke: 'none', + filter: { name: 'dropShadow', args: { dx: 0.5, dy: 2, blur: 2, color: '#333333' }} + } + } +}); + +var wage = new erd.WeakEntity({ + + position: { x: 530, y: 200 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Wage', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.inner': { + fill: '#31d0c6', + stroke: 'none', + points: '155,5 155,55 5,55 5,5' + }, + '.outer': { + fill: 'none', + stroke: '#31d0c6', + points: '160,0 160,60 0,60 0,0', + filter: { name: 'dropShadow', args: { dx: 0.5, dy: 2, blur: 2, color: '#333333' }} + } + } +}); + +var paid = new erd.IdentifyingRelationship({ + + position: { x: 350, y: 190 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Gets paid', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.inner': { + fill: '#7c68fd', + stroke: 'none' + }, + '.outer': { + fill: 'none', + stroke: '#7c68fd', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 1, color: '#333333' }} + } + } +}); + +var isa = new erd.ISA({ + + position: { x: 125, y: 300 }, + attrs: { + text: { + text: 'ISA', + fill: '#ffffff', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + polygon: { + fill: '#fdb664', + stroke: 'none', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 1, color: '#333333' }} + } + } +}); + +var number = new erd.Key({ + + position: { x: 1, y: 90 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Number', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.outer, .inner': { + fill: '#feb662', + stroke: 'none' + }, + '.outer': { + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 2, color: '#222138' }} + } + } +}); + +var employeeName = new erd.Normal({ + + position: { x: 75, y: 30 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Name', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.outer': { + fill: '#fe8550', + stroke: '#fe854f', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 2, color: '#222138' }} + } + } +}); + +var skills = new erd.Multivalued({ + + position: { x: 150, y: 90 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Skills', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0px 1px #333333' } + }, + '.inner': { + fill: '#fe8550', + stroke: 'none', + rx: 43, + ry: 21 + + }, + '.outer': { + fill: '#464a65', + stroke: '#fe8550', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 2, color: '#222138' }} + } + } +}); + +var amount = new erd.Derived({ + + position: { x: 440, y: 80 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Amount', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.inner': { + fill: '#fca079', + stroke: 'none', + 'display': 'block' + }, + '.outer': { + fill: '#464a65', + stroke: '#fe854f', + 'stroke-dasharray': '3,1', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 2, color: '#222138' }} + } + } +}); + +var uses = new erd.Relationship({ + + position: { x: 300, y: 390 }, + attrs: { + text: { + fill: '#ffffff', + text: 'Uses', + 'letter-spacing': 0, + style: { 'text-shadow': '1px 0 1px #333333' } + }, + '.outer': { + fill: '#797d9a', + stroke: 'none', + filter: { name: 'dropShadow', args: { dx: 0, dy: 2, blur: 1, color: '#333333' }} + } + } +}); + +// Create new shapes by cloning + +var salesman = employee.clone().translate(0, 200).attr('text/text', 'Salesman'); + +var date = employeeName.clone().position(590, 80).attr('text/text', 'Date'); + +var car = employee.clone().position(430, 400).attr('text/text', 'Company car'); + +var plate = number.clone().position(405, 500).attr('text/text', 'Plate'); + + +// Helpers + +var createLink = function(elm1, elm2) { + + var myLink = new erd.Line({ + source: { id: elm1.id }, + target: { id: elm2.id } + }); + + return myLink.addTo(graph); +}; + +var createLabel = function(txt) { + return { + labels: [{ + position: -20, + attrs: { + text: { dy: -8, text: txt, fill: '#ffffff' }, + rect: { fill: 'none' } + } + }] + }; +}; + +// Add shapes to the graph + +graph.addCells([employee, salesman, wage, paid, isa, number, employeeName, skills, amount, date, plate, car, uses]); + +createLink(employee, paid).set(createLabel('1')); +createLink(employee, number); +createLink(employee, employeeName); +createLink(employee, skills); +createLink(employee, isa); +createLink(isa, salesman); +createLink(salesman, uses).set(createLabel('0..1')); +createLink(car, uses).set(createLabel('1..1')); +createLink(car, plate); +createLink(wage, paid).set(createLabel('N')); +createLink(wage, amount); +createLink(wage, date); diff --git a/demo/fsa.html b/demo/fsa.html deleted file mode 100644 index ba1e1b34d0..0000000000 --- a/demo/fsa.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - Basic shapes - - - - - - - -
- - - - - - - - - - - - - diff --git a/demo/fsa/css/fsa.css b/demo/fsa/css/fsa.css new file mode 100644 index 0000000000..41c4af76d5 --- /dev/null +++ b/demo/fsa/css/fsa.css @@ -0,0 +1,3 @@ +#paper { + display: inline-block; +} diff --git a/demo/fsa/index.html b/demo/fsa/index.html new file mode 100644 index 0000000000..dc45744a73 --- /dev/null +++ b/demo/fsa/index.html @@ -0,0 +1,30 @@ + + + + + + Finite State Machines + + + + + + +
+

Finite State Machines

+
+ +
+ + + + + + + + + + + + + diff --git a/demo/fsa.js b/demo/fsa/src/fsa.js similarity index 100% rename from demo/fsa.js rename to demo/fsa/src/fsa.js diff --git a/demo/links/index.html b/demo/links/index.html new file mode 100644 index 0000000000..fa66e15f5f --- /dev/null +++ b/demo/links/index.html @@ -0,0 +1,27 @@ + + + + + + Links + + + + + +
+

Links

+
+ +
+ + + + + + + + + + + diff --git a/demo/links/src/links.js b/demo/links/src/links.js new file mode 100644 index 0000000000..0413059f6e --- /dev/null +++ b/demo/links/src/links.js @@ -0,0 +1,130 @@ +var graph = new joint.dia.Graph(); +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 800, + height: 600, + model: graph, + gridSize: 1 +}); + +var link = new joint.dia.Link({ + source: { x: 10, y: 20 }, + target: { x: 350, y: 20 }, + attrs: {} +}); + +/* +$rappid_green: #31d0c6; +$rappid_purple: #7c68fc; +$rappid_orange: #fe854f; +$rappid_orange2: #feb663; +$rappid_white: #f6f6f6; +$rappid_grey1: #222138; +$rappid_grey2: #33334e; +$rappid_grey3: #4b4a67; +$rappid_orange4: #3c4260; +$rappid_grey5: #6a6c8a; +$rappid_grey6: #c6c7e2; +$payne_grey: #3c4260; +*/ + +link.attr({ + '.connection': { stroke: '#222138' }, + '.marker-source': { fill: '#31d0c6', stroke: 'none', d: 'M 10 0 L 0 5 L 10 10 z' }, + '.marker-target': { fill: '#fe854f', stroke: '#7c68fc', d: 'M 10 0 L 0 5 L 10 10 z' } +}); + +var link2 = new joint.dia.Link({ + source: { x: 10, y: 80 }, + target: { x: 350, y: 80 }, + attrs: {} +}); + +link2.attr({ + '.connection': { stroke: '#fe854f', 'stroke-width': 4 }, + '.marker-source': { stroke: '#fe854f', fill: '#fe854f', d: 'M 10 0 L 0 5 L 10 10 z' }, + '.marker-target': { stroke: '#fe854f', fill: '#fe854f', d: 'M 10 0 L 0 5 L 10 10 z' } +}); + +var link3 = new joint.dia.Link({ + source: { x: 10, y: 140 }, + target: { x: 350, y: 140 }, + attrs: {} +}); + +link3.attr({ + '.connection': { stroke: '#31d0c6', 'stroke-width': 3, 'stroke-dasharray': '5 2' }, + '.marker-source': { stroke: '#31d0c6', fill: '#31d0c6', d: 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z' }, + '.marker-target': { stroke: '#31d0c6', fill: '#31d0c6', d: 'M4.834,4.834L4.833,4.833c-5.889,5.892-5.89,15.443,0.001,21.334s15.44,5.888,21.33-0.002c5.891-5.891,5.893-15.44,0.002-21.33C20.275-1.056,10.725-1.056,4.834,4.834zM25.459,5.542c0.833,0.836,1.523,1.757,2.104,2.726l-4.08,4.08c-0.418-1.062-1.053-2.06-1.912-2.918c-0.859-0.859-1.857-1.494-2.92-1.913l4.08-4.08C23.7,4.018,24.622,4.709,25.459,5.542zM10.139,20.862c-2.958-2.968-2.959-7.758-0.001-10.725c2.966-2.957,7.756-2.957,10.725,0c2.954,2.965,2.955,7.757-0.001,10.724C17.896,23.819,13.104,23.817,10.139,20.862zM5.542,25.459c-0.833-0.837-1.524-1.759-2.105-2.728l4.081-4.081c0.418,1.063,1.055,2.06,1.914,2.919c0.858,0.859,1.855,1.494,2.917,1.913l-4.081,4.081C7.299,26.982,6.379,26.292,5.542,25.459zM8.268,3.435l4.082,4.082C11.288,7.935,10.29,8.571,9.43,9.43c-0.858,0.859-1.494,1.855-1.912,2.918L3.436,8.267c0.58-0.969,1.271-1.89,2.105-2.727C6.377,4.707,7.299,4.016,8.268,3.435zM22.732,27.563l-4.082-4.082c1.062-0.418,2.061-1.053,2.919-1.912c0.859-0.859,1.495-1.857,1.913-2.92l4.082,4.082c-0.58,0.969-1.271,1.891-2.105,2.728C24.623,26.292,23.701,26.983,22.732,27.563z' } +}); + +var link4 = new joint.dia.Link({ + source: { x: 400, y: 20 }, + target: { x: 740, y: 20 }, + vertices: [{ x: 400, y: 60 }, { x: 550, y: 60 }, { x: 550, y: 20 }], + attrs: {} +}); + +link4.attr({ + '.connection': { stroke: '#3c4260', 'stroke-width': 2 }, + '.marker-source': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z' }, + '.marker-target': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z' } +}); + +var link5 = new joint.dia.Link({ + source: { x: 440, y: 100 }, + target: { x: 740, y: 100 }, + vertices: [{ x: 400, y: 140 }, { x: 550, y: 100 }, { x: 600, y: 140 }], + smooth: true, + attrs: {} +}); + +link5.attr({ + '.connection': { stroke: '#7c68fc', 'stroke-width': 2 }, + '.marker-source': { stroke: '#7c68fc', fill: '#7c68fc', d: 'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z' }, + '.marker-target': { stroke: '#feb663', fill: '#feb663', d: 'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z' } +}); + +var link6 = new joint.dia.Link({ + source: { x: 10, y: 200 }, + target: { x: 350, y: 200 }, + attrs: { + '.marker-source': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z'}, + '.marker-target': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z' } + }, + labels: [ + { position: 0.5, attrs: { text: { text: 'label' } } } + ] +}); + +var link7 = new joint.dia.Link({ + source: { x: 400, y: 200 }, + target: { x: 740, y: 200 }, + attrs: { + '.marker-source': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z' }, + '.marker-target': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z' } + }, + labels: [ + { position: 0.5, attrs: { text: { text: 'fancy label', fill: '#f6f6f6', 'font-family': 'sans-serif' }, rect: { stroke: '#7c68fc', 'stroke-width': 20, rx: 5, ry: 5 } }} + ] +}); + +var link8 = new joint.dia.Link({ + source: { x: 10, y: 280 }, + target: { x: 740, y: 280 }, + vertices: [{ x: 150, y: 350 }, { x: 250, y: 350 }, { x: 250, y: 280 }, { x: 500, y: 280 }, { x: 500, y: 350 }, { x: 630, y: 350 }], + smooth: true, + attrs: { + '.marker-source': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z'}, + '.marker-target': { fill: '#4b4a67', stroke: '#4b4a67', d: 'M 10 0 L 0 5 L 10 10 z' } + }, + labels: [ + { position: 25, attrs: { text: { text: '1..n' } }}, + { position: 0.45, attrs: { text: { text: 'multiple', fill: 'white', 'font-family': 'sans-serif' }, rect: { stroke: '#31d0c6', 'stroke-width': 20, rx: 5, ry: 5 } }}, + { position: 0.55, attrs: { text: { text: 'labels', fill: 'white', 'font-family': 'sans-serif' }, rect: { stroke: '#31d0c6', 'stroke-width': 20, rx: 5, ry: 5 } }}, + { position: -25, attrs: { text: { text: '*' } }} + ] +}); + + +graph.addCell([link, link2, link3, link4, link5, link6, link7, link8]); diff --git a/demo/logic/css/logic.css b/demo/logic/css/logic.css new file mode 100644 index 0000000000..e9b5f57ecb --- /dev/null +++ b/demo/logic/css/logic.css @@ -0,0 +1,116 @@ +.connection { + stroke: #999; +} + +.connection-wrap { + stroke-linecap: butt; + transition: all 0.5s linear 0.2s; +} + +.connection-wrap:hover { + stroke: black; + stroke-width: 9px; + transition: all 0s; +} + +.link-tools .tool-remove circle { + fill: white; + stroke: #ccc; + stroke-width: 1px; + stroke-opacity: .5; +} + +.link-tools .tool-remove:hover circle { + fill: #e74c3c; + stroke: #c0392b; +} + +.link-tools .tool-remove path { + stroke: #ccc; +} + +.link-tools .tool-remove circle { + transition: fill 1s; +} + +.link-tools .tool-remove:hover path { + stroke: none; +} + +.marker-arrowhead, .marker-vertex { + fill: #fff; + stroke: #7f8c8d; + stroke-opacity: 0.4; + stroke-width: 2px; +} + +.marker-arrowhead:hover, .marker-vertex:hover { + fill: #ecf0f1; + stroke: #bdc3c7; +} + +.marker-vertex-remove-area { + fill: white; + stroke: #ccc; + stroke-opacity: .5; +} + +.marker-vertex-remove-group:hover .marker-vertex-remove-area { + transition: fill 1s; + fill: #e74c3c; + stroke: #c0392b; +} + +.marker-vertex-remove { + stroke: #eee; +} + +.marker-vertex-remove-group:hover .marker-vertex-remove { + stroke: none; +} + +.joint-element .highlighted { + outline: none; + fill: #ecf0f1; + stroke: #bdc3c7; + cursor: crosshair; +} + +.joint-element .body { + fill: #68DDD5; + stroke: #44CCC3; + stroke-opacity: 0.5; + transition: all 0.2s; +} + +.joint-element circle { + fill: #fff; + stroke: #7f8c8d; + stroke-opacity: 0.5; + stroke-width: 2px; +} + +.joint-element text { + fill: #fff; +} + +.joint-link.live > .connection { + stroke: #7c68fc; + stroke-width: 3px; +} + +.live .connection-wrap { + stroke: #7C68FD; +} + +.joint-element.live .body { + fill: #FEB662; + stroke: #CF9452; +} + +.joint-element.live text { + fill: #ffffff; +} +.wire { + stroke: #4B4F6A; +} \ No newline at end of file diff --git a/demo/logic/index.html b/demo/logic/index.html new file mode 100644 index 0000000000..38732e64fc --- /dev/null +++ b/demo/logic/index.html @@ -0,0 +1,27 @@ + + + + + + Logic Circuits + + + + + + +
+

Logic Circuits

+
+ +
+ + + + + + + + + + diff --git a/demo/logic/src/logic.js b/demo/logic/src/logic.js new file mode 100644 index 0000000000..74816dc416 --- /dev/null +++ b/demo/logic/src/logic.js @@ -0,0 +1,162 @@ +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + + el: $('#paper'), + model: graph, + width: 1000, height: 600, gridSize: 5, + snapLinks: true, + linkPinning: false, + defaultLink: new joint.shapes.logic.Wire, + + validateConnection: function(vs, ms, vt, mt, e, vl) { + + if (e === 'target') { + + // target requires an input port to connect + if (!mt || !mt.getAttribute('class') || mt.getAttribute('class').indexOf('input') < 0) return false; + + // check whether the port is being already used + var portUsed = _.find(this.model.getLinks(), function(link) { + + return (link.id !== vl.model.id && + link.get('target').id === vt.model.id && + link.get('target').port === mt.getAttribute('port')); + }); + + return !portUsed; + + } else { // e === 'source' + + // source requires an output port to connect + return ms && ms.getAttribute('class') && ms.getAttribute('class').indexOf('output') >= 0; + } + } +}); + +// zoom the viewport by 50% +paper.scale(1.5,1.5); + +function toggleLive(model, signal) { + // add 'live' class to the element if there is a positive signal + V(paper.findViewByModel(model).el).toggleClass('live', signal > 0); +} + +function broadcastSignal(gate, signal) { + // broadcast signal to all output ports + _.defer(_.invoke, graph.getConnectedLinks(gate, { outbound: true }), 'set', 'signal', signal); +} + +function initializeSignal() { + + var signal = Math.random(); + // > 0 wire with a positive signal is alive + // < 0 wire with a negative signal means, there is no signal + // 0 none of the above - reset value + + // cancel all signals stores in wires + _.invoke(graph.getLinks(), 'set', 'signal', 0); + + // remove all 'live' classes + $('.live').each(function() { + V(this).removeClass('live'); + }); + + _.each(graph.getElements(), function(element) { + // broadcast a new signal from every input in the graph + (element instanceof joint.shapes.logic.Input) && broadcastSignal(element, signal); + }); + + return signal; +} + +// Every logic gate needs to know how to handle a situation, when a signal comes to their ports. +joint.shapes.logic.Gate.prototype.onSignal = function(signal, handler) { + handler.call(this, signal); +} +// The repeater delays a signal handling by 400ms +joint.shapes.logic.Repeater.prototype.onSignal = function(signal, handler) { + _.delay(handler, 400, signal); +} +// Output element just marks itself as alive. +joint.shapes.logic.Output.prototype.onSignal = function(signal) { + toggleLive(this, signal); +} + +// diagramm setup + +var gates = { + repeater: new joint.shapes.logic.Repeater({ position: { x: 410, y: 25 }}), + or: new joint.shapes.logic.Or({ position: { x: 550, y: 50 }}), + and: new joint.shapes.logic.And({ position: { x: 550, y: 150 }}), + not: new joint.shapes.logic.Not({ position: { x: 90, y: 140 }}), + nand: new joint.shapes.logic.Nand({ position: { x: 550, y: 250 }}), + nor: new joint.shapes.logic.Nor({ position: { x: 270, y: 190 }}), + xor: new joint.shapes.logic.Xor({ position: { x: 550, y: 200 }}), + xnor: new joint.shapes.logic.Xnor({ position: { x: 550, y: 100 }}), + input: new joint.shapes.logic.Input({ position: { x: 5, y: 45 }}), + output: new joint.shapes.logic.Output({ position: { x: 440, y: 290 }}) +}; + + +var wires = [ + { source: { id: gates.input.id, port: 'out' }, target: { id: gates.not.id, port: 'in' }}, + { source: { id: gates.not.id, port: 'out' }, target: { id: gates.nor.id, port: 'in1' }}, + { source: { id: gates.nor.id, port: 'out' }, target: { id: gates.repeater.id, port: 'in' }}, + { source: { id: gates.nor.id, port: 'out' }, target: { id: gates.output.id, port: 'in' }}, + { source: { id: gates.repeater.id, port: 'out' }, target: { id: gates.nor.id, port: 'in2'}, + vertices: [{ x: 215, y: 100 }] + } +]; + +// add gates and wires to the graph +graph.addCells(_.toArray(gates)); +_.each(wires, function(attributes) { + graph.addCell(paper.getDefaultLink().set(attributes)); +}); + +graph.on('change:source change:target', function(model, end) { + + var e = 'target' in model.changed ? 'target' : 'source'; + + if ((model.previous(e).id && !model.get(e).id) || (!model.previous(e).id && model.get(e).id)) { + // if source/target has been connected to a port or disconnected from a port reinitialize signals + current = initializeSignal(); + } +}); + +graph.on('change:signal', function(wire, signal) { + + toggleLive(wire, signal); + + var magnitude = Math.abs(signal); + + // if a new signal has been generated stop transmitting the old one + if (magnitude !== current) return; + + var gate = graph.getCell(wire.get('target').id); + + if (gate) { + + gate.onSignal(signal, function() { + + // get an array of signals on all input ports + var inputs = _.chain(graph.getConnectedLinks(gate, { inbound: true })) + .groupBy(function(wire) { + return wire.get('target').port; + }) + .map(function(wires) { + return Math.max.apply(this, _.invoke(wires, 'get', 'signal')) > 0; + }) + .value(); + + // calculate the output signal + var output = magnitude * (gate.operation.apply(gate, inputs) ? 1 : -1); + + broadcastSignal(gate, output); + }); + } +}); + +// initialize signal and keep its value +var current = initializeSignal(); diff --git a/demo/org/images/female.png b/demo/org/images/female.png new file mode 100644 index 0000000000000000000000000000000000000000..604c04903323b37ea1b2a3bf4dcbd031b21d1644 GIT binary patch literal 5051 zcmXX~2{cr1+`h=Z4v`SLq>^ltOlVYwgc4@RmL*1!B@Ef8`25EbjeTS*A!FZ0)+Bwf9LuAp67XE?%%u3&m+MD0093TLzFqx~>C^{xC;V>RL32S@D3=QXdgk^qwDto4UXj0z1<1@22LKWKJE)uJ=UHpn5&rU+ z)Gq3-WuRg79W1Du#a&5YNxhL&Ouol4Z#4Z=Q&Bdf{W{Of${g3p`DcPF(h+%G^nc5B z1FnHhFWah{f-`O+SmSs=zK(ABVy}Mbcx*V7vE+Z?hHL4~ z7>WpDHqO>>dr`@|vn{(YfwOkw_61}CSeXUQ0?kBVkz#FTfCTFO3lewRdx8n;9vkdW z0wI9B4DZ)T>2YcHoT989FAe6nZU1nUAY22>NOPh40RYbMHA{ zj@O3r%4|MyPtJ(?V2zFYjF8NOHUP%+Hd^F)r$)E0^Dk6UA8wiz5HrXR!I{mYQaYnelwN4UFRwPZF=dp_RDB ztCUvuj*gD#Br$WYc{x1!VJKGs5~$oblr)LEU~rOeuENCQgDB`*{Di+#)g8#r&MqPC z`zK5E-T>@4^L{Lyfd^JNP>+~($>{3_{Ib%CR9bDcIgIEXl(Wl^S!3O#7KD2R2OoH9 zq+m1*j`zvmovQz+i}$#w=d$8m)UQdzbv*=E`41z{eI0%_g7bkTh=f1RO_7bN;6Fq}92UtPZeh?gtJy9cdwqEUgg z3PZ2QW$Qe+wdgz^xG@{{fcec`&*d`Qq`F;Z{9JMmr(z2yJVN@$!=azgb)+Y|!DKmn zN2Nmx8JSPEx^neuj`>{%8=evL27{BO*Y))DOuDbz8=E8mJaZbrrB6@MC*gpdLCc+*s%Vw|MZUSegOH6jCmGr6(4746b!w7aDb zs+@-w2HxavW))-&a^llpUAPDvNyOXl^XR{Kz|fh$M@(-Y&nhy1vVB1H|dEPQE z&X|pzAr(EfQWDdSD8?ZH6H`-})FK2uFrKfFf37~Lw^dbDRcMK^;Jnuw@igb(P`a9W z##KD~TN>GY+N{o;aDhHZ-+s$`k!|D%;N`;;W?gO>3keP1X>cWLx&K{29SD$OWCO~* zEwUgnQ21gSD?<m@;_SJ%kT0uwz9I4|8s(8czF030+uZgijWTy$(bw0K2`IG z4Gpjyc$)~24_;(B^qk3I*t6d_(-mZn$rU2v0BLSpTa5>4G!_8m@1#x&Jqg&2Gg<7K zsQ1P;H8agJa=+dNKqD!6{JuwbdFuJ(o-&lb9PFCY3Rw*FMg3?x)Yk3O9jK54y}GMo zg&SE2-I@Cl8&mZg_1DI|W#1Wwd{e&Fo6BId#^ua0lvKgd-1$~KgtW3v}@rc zbUJgcrMOxFxx5%OAT*rN-rhcq(d}qIel)V8%&V%#+U)xpuhaB`eDEM7B*gDInfwVi zBZ~SRH|^W?On`^CFMQX~lmAU+cchp>#^hkxYZHaTmE~oSp=yJd+u`x;#iHe|_V2+{EI&SjBhEHxp7`9(X zFgYXC-h5RoZf2>`t6ZCwlwVOXF?ys|(l#R_E9*aX{B(08Ec*y&JB zP?XO3Uqj+a=#%+NO&qG43@e(-k2DdplVEDBFgF`edDYa~ zPa(M{ThSmPIyzcKbvtv~7mVGqzRgL9WvN}bZ66R;^N{r9Vab#$OpKS z$oh}#`;1)O);6Z0<1~~if8b#6DT_b=Y*Zo|hF(y*+vy|a2C0p^Rl%(r46P`Xz&yw6 zl`Cd>vjY@QYTa0HEYVQ@DW)a=^YZn&QQ;F``0)hgi&gGt@F5~GQB|WQe-*(&>#-iz z>p}q%o@?cCm*(uRl2=mVF|fM2I_P_k4@MkrY;3IeoN25}K@GeZrqOUw1f7JAp+D>d zLtDkvV2VLC`HO$ySC8K7%F!g-qM{<1hxK0ek4H*(c6PAz-H0f4Z5e>DSm7aiQ8oS* zu(q}~fWzUYSkTMdSNi+i`1ttZ*^4iwufDYu{JH&5nH@%QT+j^8+5IQ4j{QXy45HKs ziPS(OP#e>&0x2(l{ap>A#r zwf;~^czW$sGiC6bOk&s2^2UZtMdzPOXRokz55AQF?vLoDpnhn82djH~d#?%#qj2_` z<>c`X%cp9FR&ADzW4qb^X4zPEwmEkvARO~KsC9#$<~>y_fo9eNmN2u{^qw>1g9Zp1 z8?KLFVz1Zskl=w5o}D|#{eKAY)@jVQP#Kf?AKHTR=qON4J&**Nbr(b8x+B4@aNr;K z_F8|I$`>I}onwv~vly|wyc`W>aM7>l|DiG0a|y`%jxzr~gEg?XX_6W&ht?sAgza=&8l(FYCf1V0Vf43I~z^o5;1|IW0YLYb|7GNM4I ztcd}dYTGa4u{UKwB%FR3q~9wrHaBkP@ism&*te{AoAl zueEjtH)>WdGHisYi}_G$ZLqUfKNYi>1TELSxmn5DHZd{r8EW<(;i2&5%a>dBSIZ~m@*w!BCHG{#B{PJEmooa^THOg5Vx4$wJyupA-8nlm z6V}n+-+!CM&U&>#`S9EGB3Em=l(_ibU7~IBJYoj*`z)*>FE%jq32+B9!#d~2WHJ-I zC#zlGb%bE_AJ^a^NH09sX%+CR!CQh_(rrY*vV>efW_1*VwVZH7VJx7a_CRtn)}oA3 zS65bAQK2i-)`YHmLrN5dVHfBz-2m*mcwFatg^$gbg!wF1$FMOusOFl1)tRKr7z~(s zp9byX9eEv1V>nk$R0M3y{&`v^Q1$>za!s?wlFpgD-&#!VNg9zBZBH?jAKP!GLh_F{ z4<0*tCAKYLUdI@<%QEpJXchp|gq39!`0<`o4~P0sl`NmzPE2xJu;Aq2$Wi-H_O!Yr zhf`QTwwt#qz0<&IFR?tq&Yu$HpwJ92|}XFfCkd`Ve~L`9R;Bim8pd zCZ06sshy=sJI?*R7Q%E_|L*!^4TE^$E{jLb<>vhS`Aw)=_+gmWfmv=xTu#;j=S`G1 z$6w1*5~P-ZT_|C(o0*tE{BPrWB*9!S*u&JSFD0j|Zx@hE6y_0wi7Zda6&2K>D76z<;4d9PLrLV|YYWl4qQL#t z@tj2SW0Kh|RAV*6Che0G%Kt2$_x$4Eo_DzFBbR)INI7y^?nl2ej3=IydHNJR8iaZOmm$vA25G@5xtu3HWko;n`sKA zM@YB12{|&@Zj?<|yWfO<<@8M)(r3=@h3Q_a?sDV$b*LO+&thFA@$jz5ejUJ(4L&Y^ zB9j2hSk}p4%2F*^H>ELQ-*7XzS_ytOmnAO5l$V!lv~EPC5;}V4>OzKcu6yKPaqYeB z;E=1Ku3qg|b#;ao6H`$ZjB??)0TG9U zTA3^I{~hUng`+O8Is??hM+-HOU#ZR-`k4UyI6N9KTl!n!5wNg<+622$I`ym~G!)A0$hSGlIe@zmfkTD4WO&6Rl5GdLH_Z%1#v65EfI2L}E48ol}|xD&JKx zF)?o7=Cnh+{jcZEe~OE8f(xG0z7LO#j36{LG_KGi&KeQA2O(9WnVRW!+{UGfM21^O zLEL2n>3k|%%{?W^%zMSVGuQyQOg&_Gl19EgXq$E)E2(G=K7}_t@22pMQs*&_{kHU5 zMB#g&6vGBQcppRQd!OIXuy3nE{m<7-MV=RskdROYHtRLgMFG_B^e!pC{)&LL@1pKp zsh(2|){iyQui<)EI-RQ+&^#1p(bE7*{u`;(SC+m@-9QXpbJEBNTWUJb>UWfM_MKol zx}v7WPXkVEPHB|_ToSXV?t9R0PEm#&7i3r;2rtnY`Q*!x*|h&4(B)hes8k0r8@8O& zrvftcqh$VZ++oK4O{I3|E!g`8?u0BQf7#x!6NlMtWU*Grd$O|*9(==*Rnu$C_{&p2ZzP2R9_xC3=_PGFEAN#;ZN@9nG#oPr= zjg1LVHcFC%gV=Lgi29hg1D;9(Y8{aVOsHyWA7;%X#>FP3oW^k#nc3N4$}ZkT_q)Hy zo~(Ub?HPi-ILKA2I>Di_Urru6^?mvE)`?NUy&QAR^7}?5IVpXC1F=(gA1!$YT z=Zy9RqdI0=LUxI|@T_>g>_czG#*liX<9o&+L{c+J5GXqP=a_xvF1)eO+CnrYbxt?bGusjq?a{?PR-dqeJ>G85}W;>^r z`fIf`6%_pR0fTM1-lcITzDe%+vDT@m1{oJ7ApN1?z3Z);C~5l!-=DaG(K`BC{|{*AxfB2Z literal 0 HcmV?d00001 diff --git a/demo/org/images/male.png b/demo/org/images/male.png new file mode 100644 index 0000000000000000000000000000000000000000..c74900e6cfc32e992ab18991e1fc40a6944c5dc0 GIT binary patch literal 5330 zcmWky2UJr_5Pb<9K1e{3UY=9|DWOR(p(#xvAiWnsia^9rOhm;X2qID3 z_$r`;PCz;eh9*S7sRZsvdh;9@BRsaxI6j5 zB-jHfXVj}osmRD9TO7kS&VBYcowTnA*rm6|9B}2=KQ%&IpgYzNk1p@xSuiNoP=d+r5NDl$> zb!wxjUn;(qC8BykOG$$^`B;*t0UNozxtZS3)P%W)*Z?9luwwxs;w2hDSxOJJzrX*i znYp>1Fr+qVun8cBirFIOSR#T4T_UegAYk+8&3u`}v8x=y4Okz3UyX7?%9ELRK)ggB z_RHt(*X9?EqsBp=5Mn4M9~aiA)>eEUHwkf@o|xD#8ydPI6QoDszQmy16`zEw;dC*_ zb7OZ43JO^02a0jISiQR652UJyLADxSpd30~vlX1TKei_vGT-C7%2vF}=#q`nz_#RG zj81aq0-ruYfPr)*LXAS?at8UaDgnhf-7n;ndvx8vZAN&^z_V~BZ$YM`UaO9MfJDZ; zB8)Rum%14bLHT_hA-5%iL{JFC$8Fv4z(M0nux!40!V9KR0qGK37R3@_XgDM`Ha6R= zbSUEmN)5O|S#TQ{;dHT3p-<4^?s}oF#>zY>)_}Xp7zPkxZw?9yJ|HB`zGFyWqI*ZH zw%}uZjmp5fGe*|y)nRWZLN9Z?5)jN_bp3XNuE7i4<4+mZD(Km zckLdZU8*6cAtF1gXS)@bzk;4+c(uB%s{~6%I40v6(myY1YimQBzvbbk>H6GL*VKm?Y`vr;TN4>k=a%(+y7BzFKJUAUU?HrOoA(1mqPEH07G#O`Da-nd{IIY!S ztlnlE6@xBpD#YCY4Ep0Of(2ch!Z@oBZ+kJ5$Q8ybE71o!wSx8^Y@oz@km1UlxWW{SN-$;JX4~ zY^cpuc`m)sVsxN(8jXjmPnYlTUyFo9RQ$@uYRCq$uV`G(+RSuY*WfNbox|8dFW2nw z*&M%gq6Sv3{{^904G=-y#l0pURRoudc-*_iI+0g2cK?YIrwOtw$cdt76eO2p_|J=u z4g;Umi$%Ep&jw|LA6m)xvv_H|x%=@`#PM@~#)#rZxbNS-eVc+zL*_5<%OH0bsh0_- z9yIU{IVCR}gif76jNfpbD|#(NzqY*a@E&X43?WE7J4p*(#GT$9IBlmany%zU9S3e- zK`ODlk=oORxeKR!>gO<<*-<+-d4hBM_vr7fGQxOV;hqZcMxoc6+G1ZZDhCiXU0oF= zGe3)hJitwieR^qTr4P^DyY8vdJK{gK8k#P9iQtY0?ZVF*?#L692av9l?pC=CP zn~q?U6@iLoFzw`X(b*gpo!!puAjJ{1bf za;o)clQj^79p8~gF+NtI;nCs-ZKb6Pl=s~rPqUPd(LW3_3H+9?uFBKk3ZCspo%HDN z2K_YH1d(Pb$>)@X>N9bxaH=VA!0PGui({YfXS&T zj1;KkpPsz@E+ZpjkmYLmvx)jZPpEhkhW35>ebgq~rGaSrLSpEYMi=>SVV*STxw5gr zRlVT%ir<%7xq3Eo1hF{B%*^b;b!jA;QPt?%;fWKR!w>)v4GMyf!`NuhN0BRQYbnro zXAC!3#o6Z9I%Ut>h-0?s5U+3k{HdFS<6^NDFR}l#K|!rj`h&idC`g;S2l%0{eL{)F@x*t5|@l0c(3oXIfgC zl9jz=1QZT*-}Kl{z_j74w(-5>mk%O0TKVzg<1)0TV!qU-2ft>Dqy`Ok1_uXy!ALV{ zKEv$sZlAYJjM9WlW@cs@z_sU@(NA#w7Lq*X1;!~VYHB%Qq%WY8a)T znqsVXApn+e66G;GK@80^{OE8ubSgv{<(g;r`+&yw0P?P;MILjk4@)>NtCv+maH2{b ztJPDLckcg7Cp~yt+?TU1*=JT+T>-BtVjnrY&zhEC(9m`gZ|n=CE!=RTcrHo8;23FT zqaU=1X$IJP--w6^Sy`1V9zg~G3)MXOo+a;;G-f?rSH%zeac2jHEMPN>%e50Q1T(gP z#eQC8I7UiV@0Ay|GAiA4F}t&~bH-?;BFRnw0B}8lh{dgkyrA}6T@^fW)Pc3PuD`Q5 z^hSLu!yh-pNP=`U^E`TX0im{15lCHwXDSqXeR^~zlzh}Tt%f{qT|(QkysB_94g>(@ zn2qV$?{>0(6lQ#VeZ|-=+3M4l20EZ5&%ku#?kU7hA82ZPdAZf0{wpy-8^$SKdIcd3 zM8_3sy0!ZGCV%pgIc(nyxM!J+6H7+eSWEKgB#t5#i*G;x5^9D8;+CTqK)n@kQmFE} z=1S}N;@aBqY+Ss7=2L#SC)_?O)Ap zzW7CyL_;lBA=|$in5QOX+Ee;!*6x(ywQo$F@Wb45GI!zw!bx`KfShT$D*$ZihHKGD zmvP!sHQ|m9UyA#n0w*!FBNNx2p~sieK=4o=p&$G2%m4<4-H<#Ae!#TY|rIT?% zAD&8G=8KMwo^HbYz0+}R|59@I!g|VCrS`ix{gltOUpiuhm%3s-6k1pFW*C( z0ieswx-p>G->R^+(NGKMs8o$(A7VhCAEeoq3)L z{pKvjow3%{)wLlkDth5`!%dg(zi!8=;&sz;`e_yYB9_4BP2nY?px?u%0@ne6JLS{t z{NYKus<-nt8b)d(9B{)o9VxK6y`mHZh_QKfmC?&ak_RmXSF)#M0##Qi_3TVFd>L(k z2D&gOXv>!UzCZ{-Dt@P1;jkJ4TnI7TB@i@WLs@AH_qD&rD3XI1dVEryO9`G}Nqg+5 z3>X8aA=4Jf1=UP%Dku1kwvQM@0Vl6Q%%6!Ii=iVx;qP6&^7k3p(u`Dw%*T!6VdC=?N9Z4jH)x2nQZ zErCCZuu!^t-plpw;$~-9UR@0i4GGZ^Ax!}{yr>;euKmgPHLf>zwr=aTi{q_Ml5nw? zm-M3xEF`zPM09TbtlV+Q_CLUJW z80PyO&hO?G=)^F$XQCyZQ-f#b=EMX538l63I5d9mct{Xc@F6+N(zI8lN(Kac3y~C0-XW!P89na* z*DH2}M6)X~49n~5?jR_HSQG&x9dCUhPUc;e%dl81C9KX5NstrPBM$**xA9{>Y;6{qRh+jd$XA_?8m_!h$XylN zh-i3#H-D^R&{LIi<62Pn;F6hQhxB- zp;M(%%g=ARJM4>l9|J%*fWiuv=!6vM+N2U7@%-Jz2Ha1n5+za0(XYAM4`;0a|8}U) z+iP>PZE^14WFmDozC5@!d%JLH=D9$+Z6a=?>IXGYtGa9ZOeeWFd3lUZ-bA``o84N< zA<-xlir+WPj&cZz9$!eLXDxNq8`dfJHTVrV0Cxg$kR$WwzMfC%UYrS6+`?G+z1 zzt$O=nu;@yi#t3A=sWZ>t&+L~&)#p(!sx0O%E*Or&Km1-s7H)~#d zDt`u4qn0$`QD1NUd^ABU==f`6`33#2>C5kqq`InI)^b~i`xn9O^-FthxqbjWbL6Pd z)>bPk^~+b!UNMq?vT_uvP&ylPiw=#0_=9BGXLrT%`-^n@w6>I}PGEL+cl*GSL9_Qh zROH)hKSh2}ohX;tQ77!9Ix%Ba&i*mXk5%%v6A|~>!GrjK4}?0c8^FTCLY@jbFLRJi znAGJRtAgZZlwfVO)3LpMLh0R8ri;%b*En(G#HOa4wyTdIB>oW)%+a*5Pw=8HEcN9% zsXmJrDCmgyA%^BmztPUR zoNn~wPrK)s-VbD@W8a5SMH>@NSlbZ4eZ(KfuqCA;p8}&PqQN8R9gKGpemsj-k!QNt zaD=bJV{!3%d@0rfb&eB9fxR{=gxT5IOq}dbkq+Yq)6$`*VoBE!Um^>*gvir11cUy$ z01sryM09NUhEYt+o-k~m4O?d>>Rl?_belQCr@~6&wG7_MBR&5FfF%Hi>NO7OGMS&J z`LHhZNM_7RH6fTHZDq3_BGq?3Jon)qnwumh-0m8tH!mD^XUfUzvH7^!_K@+KrFu4c zuY!lvvMSrSQXc#{LxiEc%+^)Bd=>Tf+70*LUr{rjgy8o>pE3!tMgjHg0KH;ifVsQI z6kER*#+WiI*Rr_?vct_CP$3F@I!FCc?t5LSFsn79BFIZP;G&b%JZpGYzBJe*A=yg} zez}->!3pliE*IsnTSMn+zx{aK#@isI*H-2GmbjiM@{#m7>QuN>ptGYD;?5^>{qvXa zi_3#o-g5)yAJhA`*umzqrl!3uzsCz&uJky19Ul=60I&&DM5RbKS~ia_np zZq6>XshDCBMD(d(A-CEgHU>~>bnYq&pCTNb1DaH$rcpC`6EtFIEs_!kX$4vPIA1RA ziKupw%XfdCiFksW&9sob4&AS6>XC3I_ zp|StD`dTn}fUtuKal&)*VgDT8fuQC`=q|k{=*yL!?ECiXHjzkVNn6bGykH*5@uYVJ z+%8w4vIC0ko1RgOkM9J7=fR3|zm9WkrJfFFzcR2a8{xpoCY|$@{;32*QZRZE^Q1W40r`-D zmvWlgd?^JA;3mTxM`Vt7QnSf1(@k!YM?ib`-m&7nd-q!8<&H4f(7@M>1c`WhQxdQ= LwL#PvdnNt{unz + + + + + Organizational Charts + + + + + + +
+

Organizational Charts

+
+ +
+ + + + + + + + + + diff --git a/demo/org/src/org.js b/demo/org/src/org.js new file mode 100644 index 0000000000..84b633956c --- /dev/null +++ b/demo/org/src/org.js @@ -0,0 +1,65 @@ +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 800, + height: 600, + gridSize: 1, + model: graph, + perpendicularLinks: true, + restrictTranslate: true +}); + +var member = function(x, y, rank, name, image, background, textColor) { + + textColor = textColor || "#000"; + + var cell = new joint.shapes.org.Member({ + position: { x: x, y: y }, + attrs: { + '.card': { fill: background, stroke: 'none'}, + image: { 'xlink:href': 'images/'+ image, opacity: 0.7 }, + '.rank': { text: rank, fill: textColor, 'word-spacing': '-5px', 'letter-spacing': 0}, + '.name': { text: name, fill: textColor, 'font-size': 13, 'font-family': 'Arial', 'letter-spacing': 0 } + } + }); + graph.addCell(cell); + return cell; +}; + +function link(source, target, breakpoints) { + + var cell = new joint.shapes.org.Arrow({ + source: { id: source.id }, + target: { id: target.id }, + vertices: breakpoints, + attrs: { + '.connection': { + 'fill': 'none', + 'stroke-linejoin': 'round', + 'stroke-width': '2', + 'stroke': '#4b4a67' + } + } + + }); + graph.addCell(cell); + return cell; +} + +var bart = member(300, 70, 'CEO', 'Bart Simpson', 'male.png', '#30d0c6'); +var homer = member(90, 200, 'VP Marketing', 'Homer Simpson', 'male.png', '#7c68fd', '#f1f1f1'); +var marge = member(300, 200, 'VP Sales', 'Marge Simpson', 'female.png', '#7c68fd', '#f1f1f1'); +var lisa = member(500, 200, 'VP Production' , 'Lisa Simpson', 'female.png', '#7c68fd', '#f1f1f1'); +var maggie = member(400, 350, 'Manager', 'Maggie Simpson', 'female.png', '#feb563'); +var lenny = member(190, 350, 'Manager', 'Lenny Leonard', 'male.png', '#feb563'); +var carl = member(190, 500, 'Manager', 'Carl Carlson', 'male.png', '#feb563'); + + + +link(bart, marge, [{x: 385, y: 180}]); +link(bart, homer, [{x: 385, y: 180}, {x: 175, y: 180}]); +link(bart, lisa, [{x: 385, y: 180}, {x: 585, y: 180}]); +link(homer, lenny, [{x:175 , y: 380}]); +link(homer, carl, [{x:175 , y: 530}]); +link(marge, maggie, [{x:385 , y: 380}]); diff --git a/demo/paper/css/paper.css b/demo/paper/css/paper.css new file mode 100644 index 0000000000..177ecf1bc7 --- /dev/null +++ b/demo/paper/css/paper.css @@ -0,0 +1,173 @@ +.content-container { + margin: 0 0 50px 0; +} +.content-sidebar { + display: none; +} +#paper { + background: #FFF; + width: auto; + display: inline-flex; + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.3); + border-top: 1px solid #eee; +} +.left { + position: fixed; + width: 300px; + left: 18px; + top: 0px; +} +.right { + position: fixed; + right: 20px; + width: 300px; + top: 0px; +} +.panel { + margin: 10px; + border-radius: 4px; + box-shadow: 2px 2px 3px rgba(0,0,0,0.2); + font-size: 11px; + border-left: 1px solid #eee; + border-bottom: 1px solid #eee; +} +.panel-body { + padding: 10px; + background: white; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} +.panel-heading { + background: #6a6c8a; + padding: 4px; + line-height: 30px; + text-align: center; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.panel-heading a { + color: #eeeeee; +} +.form-group { + padding: 5px 0; + text-align: left; +} +.form-group label { + display: inline-block; + width: 70px; +} +.form-group input { + margin-left: 10px; +} +.form-group input[type="range"] { + width: 110px; + padding: 0; +} +output, +.output { + background: #ecf0f1; + color: #333333; + padding: 5px; + border-radius: 10px; +} +.tooltip { + background: #2c3e50; + border: 1px solid #eeeeee; + color: #eeeeee; +} +.tooltip &.left:after { + border-right-color: #2c3e50; +} +.tooltip &.right:after { + border-left-color: #2c3e50; +} +.tooltip &.left:before { + border-right-color: #eeeeee; + } +.tooltip &.right:before { + border-left-color: #eeeeee; +} +.paper-demo .checkbox input[type='checkbox'] { + margin-right: 5px; +} +@media screen and (max-width: 1240px) { + .right, .left { + position: absolute; + top: 500px; + } +} + +/* jointjs elements */ + +.joint-element.joint-type-basic-path path { + fill: #6a6c8a; + stroke-width: 0px; +} + +.joint-element.joint-type-basic-path:hover path { + fill: #FEC582; +} + +.joint-element.joint-type-basic-path text { + fill: #FFF; + font-size: 12px; + font-weight: lighter; +} + +.joint-link .connection { + stroke: #6a6c8a; + stroke-width: 2px; +} + +.joint-link * { + pointer-events: none; +} + +/* svg */ + +.bbox { + stroke: #16a085; + stroke-width: 2; + stroke-opacity: 0; + fill: none; + transition-property: stroke-opacity; + transition-duration: 0.5s; + pointer-events: none; +} + +.axis { + stroke: #95a5a6; + stroke-dasharray: 2,4; + stroke-width: 1; + pointer-events: none; +} + +.padding { + stroke: #1abc9c; + stroke-opacity: 0; + transition-property: stroke-opacity; + transition-duration: 1s; + pointer-events: none; +} + +.grid { + stroke: #16a085; + stroke-width: 0.5; + stroke-opacity: 0; + transition-property: stroke-opacity; + transition-duration: 1s; + pointer-events: none; +} + +.active { + stroke-opacity: 0.5; +} + +.padding.active, .bbox.active { + stroke-opacity: 0.2; +} + +/* IE can't handle paths without the `d` attribute for bounding box calculation */ +.marker-source, .marker-target { + display:none; +} diff --git a/demo/paper/index.html b/demo/paper/index.html index 4c1d3ac7ed..1d58123eae 100644 --- a/demo/paper/index.html +++ b/demo/paper/index.html @@ -1,168 +1,148 @@ - - - Paper Demo - - - - -
-
+ + + + Paper attributes + + + + + + + +

Paper attributes

+
-
-
-
-
Paper attributes
-
-
-
-
- - - 0 -
-
- - - 0 -
-
- - - 1.00 -
-
- - - 1.00 -
-
-
-
- - - 650 -
-
- - - 400 + +
+
+ +
+
+
+ + + 0 +
+
+ + + 0 +
+
+ + + 1.00 +
+
+ + + 1.00 +
+
+ + + 650 +
+
+ + + 400 +
+
+ + + 1 +
+
-
-
- -
-
-
Content bounding box
-
-
- - 0.0 - - 0.0 - - 0.0 - - 0.0 +
+ +
+ + 0.0 + + 0.0 + + 0.0 + + 0.0 +
-
+
-
-
Paper grid
-
-
- - - 20 +
+ +
+
+ + + 0 +
+
+ + + 1 +
+
+ + + 1 +
+
+ + +
+
-
-
- -
-
-
-
Fit to content
-
-
- - - 0 -
-
- - - 1 -
-
- - - 1 -
-
- - -
-
- -
- -
-
Scale to fit
-
-
- - - 0.0 -
-
- - - 0.0 -
-
- - - 5.0 -
-
- - - 0.0 -
-
- +
+ +
+
+ + + 0.0 +
+
+ + + 0.1 +
+
+ + + 3.0 +
+
+ + + 0.0 +
+
+ +
+
-
-
-
-
- - - - + + + - + + - - + diff --git a/demo/paper/paper.css b/demo/paper/paper.css deleted file mode 100644 index b673fb2e82..0000000000 --- a/demo/paper/paper.css +++ /dev/null @@ -1,152 +0,0 @@ -#paper { - background: white; - margin: 50px; - display: inline-flex; - box-shadow: 10px 5px 5px rgba(0,0,0,0.5); -} - -body { - color: #444444; - background: #7f8c8d; -} - -.left { - position: fixed; - width: 300px; -} - -.right { - position: fixed; - right: 0; - width: 300px; -} - -.paper-container { - position: absolute; - left: 300px; -} - -.panel { - margin: 10px; - border-radius: 4px; - box-shadow: 10px 5px 5px rgba(0,0,0,0.5); -} - -.panel-body { - padding: 10px; - position: relative; - background: white; -} - -.panel-heading { - background: #2c3e50; - color: #eeeeee; - padding: 4px; - line-height: 30px; - text-align: center; - border-top-left-radius: 4px; - border-top-right-radius: 4px; -} - -.panel-footer { - background: #ecf0f1; - padding: 4px; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; -} - -.form-group input { - margin-left: 10px; -} - -output, .output { - background: #ecf0f1; - color: #333333; - padding: 5px; - border-radius: 10px; -} - -.form-group { - padding: 5px 0; -} - -button { - background-color: #2c3e50; - color: #eeeeee; - border: none; - border-radius: 4px; - cursor: pointer; - padding: 4px; -} - -button:hover { - background: #34495e; -} - -input[type="number"] { - width: 100px; -} - -/* svg */ - -.bbox { - stroke: #16a085; - stroke-width: 2; - stroke-opacity: 0; - fill: none; - transition-property: stroke-opacity; - transition-duration: 0.5s; - pointer-events: none; -} - -.axis { - stroke: #95a5a6; - stroke-dasharray: 2,4; - stroke-width: 1; - pointer-events: none; -} - -.padding { - stroke: #1abc9c; - stroke-opacity: 0; - transition-property: stroke-opacity; - transition-duration: 1s; - pointer-events: none; -} - -.grid { - stroke: #16a085; - stroke-width: 0.5; - stroke-opacity: 0; - transition-property: stroke-opacity; - transition-duration: 1s; - pointer-events: none; -} - -.active { - stroke-opacity: 0.2; - transition-property: stroke-opacity; - transition-duration: 2s; -} - -.bbox.active { - stroke-opacity: 0.5; -} - -.joint-element.joint-type-basic-rect rect { - fill: #2ecc71; - stroke: #27ae60; - stroke-width: 1px; -} - -.joint-element.joint-type-basic-circle circle { - fill: #e74c3c; - stroke: #c0392b; - stroke-width: 1px; -} - -.joint-element.joint-type-basic-rhombus path { - fill: #f1c40f; - stroke: #f39c12; - stroke-width: 1px; -} \ No newline at end of file diff --git a/demo/paper/paper.js b/demo/paper/paper.js deleted file mode 100644 index b4a40cd47d..0000000000 --- a/demo/paper/paper.js +++ /dev/null @@ -1,236 +0,0 @@ -var graph = new joint.dia.Graph; - -var paper = new joint.dia.Paper({ - el: $('#paper'), - width: 650, - height: 400, - gridSize: 20, - model: graph, - drawGrid: true -}); - -var rect = new joint.shapes.basic.Rect({ - position: { x: 50, y: 50 }, - size: { width: 100, height: 40 } -}).addTo(graph); - -var circle = new joint.shapes.basic.Circle({ - position: { x: 300, y: 70 }, - size: { width: 100, height: 40 } -}).addTo(graph); - -var rhombus = new joint.shapes.basic.Rhombus({ - position: { x: 50, y: 250 }, - size: { width: 70, height: 70 } -}).addTo(graph); - -var $ox = $('#ox'); -var $oy = $('#oy'); -var $sx = $('#sx'); -var $sy = $('#sy'); -var $w = $('#width'); -var $h = $('#height'); -var $ftcPadding = $('#ftc-padding'); -var $ftcGridW = $('#ftc-grid-width'); -var $ftcGridH = $('#ftc-grid-height'); -var $stfPadding = $('#stf-padding'); -var $stfMinScale = $('#stf-min-scale'); -var $stfMaxScale = $('#stf-max-scale'); -var $stfScaleGrid = $('#stf-scale-grid'); -var $bboxX = $('#bbox-x'); -var $bboxY = $('#bbox-y'); -var $bboxW = $('#bbox-width'); -var $bboxH = $('#bbox-height'); -var $grid = $('#grid'); - -var svg = V(paper.svg); -var svgVertical = V('path').attr('d', 'M -10000 -1 L 10000 -1'); -var svgHorizontal = V('path').attr('d', 'M -1 -10000 L -1 10000'); -var svgRect = V('rect'); - -var svgAxisX = svgVertical.clone().addClass('axis'); -var svgAxisY = svgHorizontal.clone().addClass('axis'); - -var svgBBox = svgRect.clone().addClass('bbox'); -svgBBox.hide = _.debounce(_.bind(function() { this.removeClass('active'); }, svgBBox), 500); - -// svg Container -var svgContainer = []; -svgContainer.showAll = function() { _.each(this, function(v) { v.addClass('active'); }); }; -svgContainer.hideAll = function() { _.each(this, function(v) { v.removeClass('active'); }); }; -svgContainer.removeAll = function() { _.invoke(this, 'remove'); }; - -// Axis has to be appended to the svg, so it won't affect the viewport. -svg.append([svgAxisX, svgAxisY, svgBBox]); - -/* events */ - -$('#reset').on('click', function() { - location.reload(); -}); - -$('#fit-to-content').on('click', function() { - - svgContainer.removeAll();; - - var padding = parseInt($ftcPadding.val(), 10); - var gridW = parseInt($ftcGridW.val(), 10); - var gridH = parseInt($ftcGridH.val(), 10); - var allowNewOrigin = $('#ftc-new-origin').val(); - - paper.fitToContent({ - padding: padding, - gridWidth: gridW, - gridHeight: gridH, - allowNewOrigin: allowNewOrigin - }); - - var translated = $ox.val() > 0 || $oy.val() > 0; - - if (padding) { - - var svgPaddingBottom = svgHorizontal.clone().addClass('padding') - .translate($w.val() - padding / 2, 0, { absolute: true }) - .attr('stroke-width', padding); - - var svgPaddingRight = svgVertical.clone().addClass('padding') - .translate(0, $h.val() - padding / 2, { absolute: true }) - .attr('stroke-width', padding); - - svg.append([svgPaddingBottom, svgPaddingRight]); - svgContainer.push(svgPaddingBottom, svgPaddingRight); - } - - if (padding && allowNewOrigin) { - - var svgPaddingLeft = svgVertical.clone().addClass('padding') - .translate(0, padding / 2, { absolute: true }) - .attr('stroke-width', padding); - var svgPaddingTop = svgHorizontal.clone().addClass('padding') - .translate(padding / 2, 0, { absolute: true }) - .attr('stroke-width', padding); - - svg.append([svgPaddingTop, svgPaddingLeft]); - svgContainer.push(svgPaddingTop, svgPaddingLeft); - } - - if (gridW > 2) { - - var x = gridW; - - if (translated) x += padding; - - do { - - var svgGridX = svgHorizontal.clone().translate(x, 0, { absolute: true }).addClass('grid'); - svg.append(svgGridX); - svgContainer.push(svgGridX); - - x += gridW; - - } while (x < $w.val() - padding); - } - - if (gridH > 2) { - - var y = gridH; - - if (translated) y += padding; - - do { - - var svgGridY = svgVertical.clone().translate(0, y, { absolute: true }).addClass('grid'); - svg.append(svgGridY); - svgContainer.push(svgGridY); - y += gridH; - - } while (y < $h.val() - padding); - } - - _.defer(function() { svgContainer.showAll(); }); -}); - -$('#scale-to-fit').on('click', function() { - - svgContainer.removeAll();; - - var padding = parseInt($stfPadding.val(), 10); - - paper.scaleContentToFit({ - padding: padding, - minScale: parseFloat($stfMinScale.val()), - maxScale: parseFloat($stfMaxScale.val()), - scaleGrid: parseFloat($stfScaleGrid.val()), - preserveAspectRatio: $('#stf-ratio').is(':checked') - }); - - if (padding) { - - var svgPaddingBottom = svgHorizontal.clone().addClass('padding') - .translate($w.val() - padding / 2, 0, { absolute: true }) - .attr('stroke-width', padding); - - var svgPaddingRight = svgVertical.clone().addClass('padding') - .translate(0, $h.val() - padding / 2, { absolute: true }) - .attr('stroke-width', padding); - - var svgPaddingLeft = svgVertical.clone().addClass('padding') - .translate(0, padding / 2, { absolute: true }) - .attr('stroke-width', padding); - var svgPaddingTop = svgHorizontal.clone().addClass('padding') - .translate(padding / 2, 0, { absolute: true }) - .attr('stroke-width', padding); - - svg.append([svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft]); - svgContainer.push(svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft); - } - - _.defer(function() { svgContainer.showAll(); }); - -}); - -$ox.on('input', function() { paper.setOrigin(parseInt(this.value, 10), parseInt($oy.val(), 10)); }); -$oy.on('input', function() { paper.setOrigin(parseInt($ox.val(), 10), parseInt(this.value, 10)); }); -$sx.on('input', function() { paper.scale(parseFloat(this.value), parseFloat($sy.val())); }); -$sy.on('input', function() { paper.scale(parseFloat($sx.val()), parseFloat(this.value)); }); -$w.on('input', function() { paper.setDimensions(parseInt(this.value, 10), parseInt($h.val(),10)); }); -$h.on('input', function() { paper.setDimensions(parseInt($w.val(), 10), parseInt(this.value, 10)); }); -$grid.on('input', function() { - paper.setGridSize(this.value); -}); - -$('.range').on('input', function() { - $(this).next().val(this.value); -}); - -paper.on('scale', function(sx, sy) { - $sx.val(sx).next().val(sx.toFixed(2)); - $sy.val(sy).next().val(sy.toFixed(2)); -}); - -paper.on('translate', function(ox, oy) { - $ox.val(ox).next().val(Math.round(ox)); - $oy.val(oy).next().val(Math.round(oy)); - // translate axis - svgAxisX.translate(0, oy, { absolute: true }); - svgAxisY.translate(ox, 0, { absolute: true }); -}); - -paper.on('resize', function(width, height) { - $w.val(width).next().val(Math.round(width)); - $h.val(height).next().val(Math.round(height)); -}); - -paper.on('scale translate resize', function() { svgContainer.hideAll(); }); -paper.model.on('change', function() { - - svgContainer.hideAll(); - - var bbox = paper.getContentBBox(); - $bboxX.text(Math.round(bbox.x)); - $bboxY.text(Math.round(bbox.y)); - $bboxW.text(Math.round(bbox.width)); - $bboxH.text(Math.round(bbox.height)); - - svgBBox.attr(bbox).addClass('active').hide(); -}); diff --git a/demo/paper/src/paper.js b/demo/paper/src/paper.js new file mode 100644 index 0000000000..94093d0777 --- /dev/null +++ b/demo/paper/src/paper.js @@ -0,0 +1,438 @@ +var graph = new joint.dia.Graph(); +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 600, + height: 400, + gridSize: 1, + model: graph, + linkConnectionPoint: function(linkView, view) { + // connection point is always in the center of an element + return view.model.getBBox().center(); + } +}); + +var elements = [ + + new joint.shapes.basic.Path({ + position: { x: 75, y: 175 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'joint', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 0 0 L 100 0 80 20 100 40 0 40 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 200, y: 275 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'dia', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 200, y: 75 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'util', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 200, y: 175 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'shapes', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 325, y: 175 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'basic', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 80 20 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 450, y: 150 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'Path', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 450, y: 200 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'Text', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 325, y: 250 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'Paper', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 325, y: 300 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'Graph', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 325, y: 100 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'getByPath', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }), + + new joint.shapes.basic.Path({ + position: { x: 325, y: 50 }, + size: { width: 100, height: 40 }, + attrs: { + text: { text: 'setByPath', 'ref-y': 0.5, 'y-alignment': 'middle' }, + path: { d: 'M 20 0 L 100 0 100 40 20 40 0 20 Z' } + } + }) +]; + +// add all elements to the graph +graph.resetCells(elements); + +var linkEnds = [ + { source: 0, target: 1 }, { source: 0, target: 2 }, { source: 0, target: 3 }, + { source: 1, target: 7 }, { source: 1, target: 8 }, + { source: 2, target: 9 }, { source: 2, target: 10 }, + { source: 3, target: 4 }, + { source: 4, target: 5 }, { source: 4, target: 6 } +]; + +// add all links to the graph +_.each(linkEnds, function(ends) { + new joint.dia.Link({ + source: { id: elements[ends.source].id }, + target: { id: elements[ends.target].id }, + z: -1 // make sure all links are displayed under the elements + }).addTo(graph); +}); + +// cache important html elements +var $ox = $('#ox'); +var $oy = $('#oy'); +var $sx = $('#sx'); +var $sy = $('#sy'); +var $w = $('#width'); +var $h = $('#height'); +var $ftcPadding = $('#ftc-padding'); +var $ftcGridW = $('#ftc-grid-width'); +var $ftcGridH = $('#ftc-grid-height'); +var $ftcNewOrigin = $('#ftc-new-origin'); +var $stfPadding = $('#stf-padding'); +var $stfMinScale = $('#stf-min-scale'); +var $stfMaxScale = $('#stf-max-scale'); +var $stfScaleGrid = $('#stf-scale-grid'); +var $stfRatio = $('#stf-ratio'); +var $bboxX = $('#bbox-x'); +var $bboxY = $('#bbox-y'); +var $bboxW = $('#bbox-width'); +var $bboxH = $('#bbox-height'); +var $grid = $('#grid'); + +// cache important svg elements +var svg = V(paper.svg); +var svgVertical = V('path').attr('d', 'M -10000 -1 L 10000 -1'); +var svgHorizontal = V('path').attr('d', 'M -1 -10000 L -1 10000'); +var svgRect = V('rect'); +var svgAxisX = svgVertical.clone().addClass('axis'); +var svgAxisY = svgHorizontal.clone().addClass('axis'); +var svgBBox = svgRect.clone().addClass('bbox'); + +svgBBox.hide = _.debounce(function() { + svgBBox.removeClass('active'); +}, 500); + +// svg Container - contains all non-jointjs svg elements +var svgContainer = []; + +svgContainer.showAll = function() { + _.each(this, function(v) { v.addClass('active'); }); +}; + +svgContainer.hideAll = function() { + _.each(this, function(v) { v.removeClass('active'); }); +}; + +svgContainer.removeAll = function() { + while (this.length > 0) { + this.pop().remove(); + } +}; + +// Axis has to be appended to the svg, so it won't affect the viewport. +svg.append([svgAxisX, svgAxisY, svgBBox]); + +function fitToContent() { + + svgContainer.removeAll(); + + var padding = parseInt($ftcPadding.val(), 10); + var gridW = parseInt($ftcGridW.val(), 10); + var gridH = parseInt($ftcGridH.val(), 10); + var allowNewOrigin = $ftcNewOrigin.val(); + + paper.fitToContent({ + padding: padding, + gridWidth: gridW, + gridHeight: gridH, + allowNewOrigin: allowNewOrigin + }); + + var bbox = paper.getContentBBox(); + + var translatedX = allowNewOrigin == 'any' || (allowNewOrigin == 'positive' && bbox.x - paper.options.origin.x >= 0) || (allowNewOrigin == 'negative' && bbox.x - paper.options.origin.x < 0); + var translatedY = allowNewOrigin == 'any' || (allowNewOrigin == 'positive' && bbox.y - paper.options.origin.y >= 0) || (allowNewOrigin == 'negative' && bbox.y - paper.options.origin.y < 0); + + if (padding) { + + var svgPaddingRight = svgHorizontal.clone().addClass('padding') + .translate(paper.options.width - padding / 2, 0, { absolute: true }) + .attr('stroke-width', padding); + + var svgPaddingBottom = svgVertical.clone().addClass('padding') + .translate(0, paper.options.height - padding / 2, { absolute: true }) + .attr('stroke-width', padding); + + svg.append([svgPaddingBottom, svgPaddingRight]); + svgContainer.push(svgPaddingBottom, svgPaddingRight); + } + + if (padding && (translatedX || translatedY)) { + + var paddings = []; + + if (translatedY) { + + var svgPaddingTop = svgVertical.clone().addClass('padding') + .translate(0, padding / 2, { absolute: true }) + .attr('stroke-width', padding); + + paddings.push(svgPaddingTop); + } + + if (translatedX) { + + var svgPaddingLeft = svgHorizontal.clone().addClass('padding') + .translate(padding / 2, 0, { absolute: true }) + .attr('stroke-width', padding); + + paddings.push(svgPaddingLeft); + } + + if (paddings.length) { + svg.append(paddings); + svgContainer.push.apply(svgContainer, paddings); + } + } + + if (gridW > 2) { + + var x = gridW; + + if (translatedX) x += padding; + + do { + + var svgGridX = svgHorizontal.clone().translate(x, 0, { absolute: true }).addClass('grid'); + svg.append(svgGridX); + svgContainer.push(svgGridX); + + x += gridW; + + } while (x < paper.options.width - padding); + } + + if (gridH > 2) { + + var y = gridH; + + if (translatedY) y += padding; + + do { + + var svgGridY = svgVertical.clone().translate(0, y, { absolute: true }).addClass('grid'); + svg.append(svgGridY); + svgContainer.push(svgGridY); + y += gridH; + + } while (y < paper.options.height - padding); + } + + svgContainer.showAll(); +} + +function scaleToFit() { + + svgContainer.removeAll(); + + var padding = parseInt($stfPadding.val(), 10); + + paper.scaleContentToFit({ + padding: padding, + minScale: parseFloat($stfMinScale.val()), + maxScale: parseFloat($stfMaxScale.val()), + scaleGrid: parseFloat($stfScaleGrid.val()), + preserveAspectRatio: $stfRatio.is(':checked') + }); + + if (padding) { + + var svgPaddingRight = svgHorizontal.clone().addClass('padding') + .translate(paper.options.width - padding / 2, 0, { absolute: true }) + .attr('stroke-width', padding); + + var svgPaddingBottom = svgVertical.clone().addClass('padding') + .translate(0, paper.options.height - padding / 2, { absolute: true }) + .attr('stroke-width', padding); + + var svgPaddingLeft = svgVertical.clone().addClass('padding') + .translate(0, padding / 2, { absolute: true }) + .attr('stroke-width', padding); + + var svgPaddingTop = svgHorizontal.clone().addClass('padding') + .translate(padding / 2, 0, { absolute: true }) + .attr('stroke-width', padding); + + svg.append([svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft]); + svgContainer.push(svgPaddingBottom, svgPaddingRight, svgPaddingTop, svgPaddingLeft); + } + + svgContainer.showAll(); +} + +function getGridBackgroundImage(gridX, gridY) { + + var canvas = document.createElement('canvas'); + canvas.width = gridX; + canvas.height = gridY; + + if (gridX > 5 && gridY > 5) { + + var ox = $ox.val(); + var oy = $oy.val(); + + gridX = ox >= 0 ? ox % gridX : gridX + ox % gridX - 1; + gridY = oy >= 0 ? oy % gridY : gridY + oy % gridY - 1; + + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(gridX, gridY, 1, 1); + context.fillStyle = 'black'; + context.fill(); + } + + return canvas.toDataURL('image/png'); +} + +function updateBBox() { + + var bbox = paper.getContentBBox(); + + $bboxX.text(Math.round(bbox.x - paper.options.origin.x)); + $bboxY.text(Math.round(bbox.y - paper.options.origin.y)); + $bboxW.text(Math.round(bbox.width)); + $bboxH.text(Math.round(bbox.height)); + + svgBBox.attr(bbox).addClass('active').hide(); +} + +/* events */ + +$('#fit-to-content input, #fit-to-content select').on('input change', fitToContent); +$('#scale-to-fit input').on('input change', scaleToFit); + +$ox.on('input change', function() { + paper.setOrigin(parseInt(this.value, 10), parseInt($oy.val(), 10)); +}); +$oy.on('input change', function() { + paper.setOrigin(parseInt($ox.val(), 10), parseInt(this.value, 10)); +}); +$sx.on('input change', function() { + paper.scale(parseFloat(this.value), parseFloat($sy.val())); +}); +$sy.on('input change', function() { + paper.scale(parseFloat($sx.val()), parseFloat(this.value)); +}); +$w.on('input change', function() { + paper.setDimensions(parseInt(this.value, 10), parseInt($h.val(),10)); +}); +$h.on('input change', function() { + paper.setDimensions(parseInt($w.val(), 10), parseInt(this.value, 10)); +}); +$grid.on('input change', function() { + paper.options.gridSize = this.value; + paper.$el.css('background-image', 'url("' + getGridBackgroundImage(this.value * $sx.val(), this.value * $sy.val()) + '")'); +}); +$('.range').on('input change', function() { + $(this).next().text(this.value); +}); + +paper.on({ + + scale: function(sx, sy) { + + $sx.val(sx).next().text(sx.toFixed(2)); + $sy.val(sy).next().text(sy.toFixed(2)); + + var grid = $grid.val(); + paper.$el.css('background-image', 'url("' + getGridBackgroundImage(grid * sx, grid * sy) + '")'); + + svgContainer.hideAll(); + }, + + translate: function(ox, oy) { + + $ox.val(ox).next().text(Math.round(ox)); + $oy.val(oy).next().text(Math.round(oy)); + + // translate axis + svgAxisX.translate(0, oy, { absolute: true }); + svgAxisY.translate(ox, 0, { absolute: true }); + + var grid = $grid.val(); + paper.$el.css('background-image', 'url("' + getGridBackgroundImage(grid * $sx.val(), grid * $sy.val()) + '")'); + + svgContainer.hideAll(); + }, + + resize: function(width, height) { + + $w.val(width).next().text(Math.round(width)); + $h.val(height).next().text(Math.round(height)); + + svgContainer.hideAll(); + } +}); + +graph.on('change', function() { + svgContainer.hideAll(); + updateBBox(); +}); + +updateBBox(); diff --git a/demo/petri nets/index.html b/demo/petri nets/index.html new file mode 100644 index 0000000000..a2ec934a86 --- /dev/null +++ b/demo/petri nets/index.html @@ -0,0 +1,28 @@ + + + + + + Petri Nets + + + + + + +
+

Petri Nets

+
+ +
+ + + + + + + + + + + diff --git a/demo/petri nets/src/pn.js b/demo/petri nets/src/pn.js new file mode 100644 index 0000000000..5fa868fa8c --- /dev/null +++ b/demo/petri nets/src/pn.js @@ -0,0 +1,167 @@ +var graph = new joint.dia.Graph(); +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 800, + height: 350, + gridSize: 10, + perpendicularLinks: true, + model: graph +}); + +var pn = joint.shapes.pn; + +var pReady = new pn.Place({ + position: {x: 140, y: 50}, + attrs: { + '.label': {text: 'ready', fill: '#7c68fc'}, + '.root': {stroke: '#9586fd', 'stroke-width': 3}, + '.tokens > circle': {fill: '#7a7e9b'} + }, + tokens: 1 +}); + +var pIdle = pReady.clone().attr({ + '.label': {text: 'idle'} +}).position(140, 260).set('tokens', 2); + +var buffer = pReady.clone().attr({ + '.label': {text: 'buffer'}, + '.alot > text': { + fill: '#fe854c', + 'font-family': 'Courier New', + 'font-size': 20, + 'font-weight': 'bold', + 'ref-x': 0.5, + 'ref-y': 0.5, + 'y-alignment': -0.5 + } +}).position(350, 160).set('tokens', 12); + +var cAccepted = pReady.clone().attr({ + '.label': {text: 'accepted'} +}).position(550, 50).set('tokens', 1); + +var cReady = pReady.clone().attr({ + '.label': {text: 'accepted'} +}).position(560, 260).set('ready', 3); + + +var pProduce = new pn.Transition({ + position: {x: 50, y: 160}, + attrs: { + '.label': {text: 'produce', fill: '#fe854f'}, + '.root': {fill: '#9586fd', stroke: '#9586fd'} + } +}); + +var pSend = pProduce.clone().attr({ + '.label': {text: 'send'} +}).position(270, 160); + +var cAccept = pProduce.clone().attr({ + '.label': {text: 'accept'} +}).position(470, 160); + +var cConsume = pProduce.clone().attr({ + '.label': {text: 'consume'} +}).position(680, 160); + + +function link(a, b) { + + return new pn.Link({ + source: {id: a.id, selector: '.root'}, + target: {id: b.id, selector: '.root'}, + attrs: { + '.connection': { + 'fill': 'none', + 'stroke-linejoin': 'round', + 'stroke-width': '2', + 'stroke': '#4b4a67' + } + } + }); +} + +graph.addCell([pReady, pIdle, buffer, cAccepted, cReady, pProduce, pSend, cAccept, cConsume]); + +graph.addCell([ + link(pProduce, pReady), + link(pReady, pSend), + link(pSend, pIdle), + link(pIdle, pProduce), + link(pSend, buffer), + link(buffer, cAccept), + link(cAccept, cAccepted), + link(cAccepted, cConsume), + link(cConsume, cReady), + link(cReady, cAccept) +]); + + +function fireTransition(t, sec) { + + var inbound = graph.getConnectedLinks(t, {inbound: true}); + var outbound = graph.getConnectedLinks(t, {outbound: true}); + + var placesBefore = _.map(inbound, function (link) { + return graph.getCell(link.get('source').id); + }); + var placesAfter = _.map(outbound, function (link) { + return graph.getCell(link.get('target').id); + }); + + var isFirable = true; + _.each(placesBefore, function (p) { + if (p.get('tokens') === 0) + isFirable = false; + }); + + if (isFirable) { + + _.each(placesBefore, function (p) { + // Let the execution finish before adjusting the value of tokens. So that we can loop over all transitions + // and call fireTransition() on the original number of tokens. + _.defer(function () { + p.set('tokens', p.get('tokens') - 1); + }); + + var link = _.find(inbound, function (l) { + return l.get('source').id === p.id; + }); + paper.findViewByModel(link).sendToken(V('circle', {r: 5, fill: '#feb662'}).node, sec * 1000); + + }); + + _.each(placesAfter, function (p) { + var link = _.find(outbound, function (l) { + return l.get('target').id === p.id; + }); + paper.findViewByModel(link).sendToken(V('circle', {r: 5, fill: '#feb662'}).node, sec * 1000, function () { + p.set('tokens', p.get('tokens') + 1); + }); + + }); + } +} + +function simulate() { + var transitions = [pProduce, pSend, cAccept, cConsume]; + _.each(transitions, function (t) { + if (Math.random() < 0.7) + fireTransition(t, 1); + }); + + return setInterval(function () { + _.each(transitions, function (t) { + if (Math.random() < 0.7) + fireTransition(t, 1); + }); + }, 2000); +} + +function stopSimulation(simulationId) { + clearInterval(simulationId); +} + +var simulationId = simulate(); diff --git a/demo/petri nets/src/smil.user.js b/demo/petri nets/src/smil.user.js new file mode 100644 index 0000000000..4bfa03bba8 --- /dev/null +++ b/demo/petri nets/src/smil.user.js @@ -0,0 +1,1482 @@ +/* +@id {7eeff186-cfb4-f7c3-21f2-a15f210dca49} +@name FakeSmile +@version 0.1.52 +@description SMIL implementation in ECMAScript +@creator David Leunen (leunen.d@gmail.com) +@homepageURL http://leunen.me/fakesmile/ +@ff_min_version 2.0 +@ff_max_version 3.* +*/ +// ==UserScript== +// @name smil +// @namespace svg.smil +// ==/UserScript== + +/* MIT and GPL Licenses */ + +/* +Copyright 2008 David Leunen +Copyright 2012 Helder Magalhaes +*/ + +/** + * Milliseconds Per Frame - relation between smoothness and CPU usage: + * 40 for 25fps ("cine"-look, low CPU usage); + * 33 for ~30fps (mild resources usage, best for most LCD displays); + * 25 for 40fps (smoother animation, higher CPU usage); + * 17 for ~60fps (high CPU usage, not recommended). + * References: + * http://en.wikipedia.org/wiki/Frame_rate#Frame_rates_in_film_and_television + * http://knol.google.com/k/refresh-rate-frames-per-second-and-response-times-in-lcd-and-crt-technologies + */ +var mpf = 33; +var splinePrecision = 25; + +var svgns="http://www.w3.org/2000/svg"; +var smilanimns="http://www.w3.org/2001/smil-animation"; +var smil2ns="http://www.w3.org/2001/SMIL20"; +var smil21ns="http://www.w3.org/2005/SMIL21"; +var smil3ns="http://www.w3.org/ns/SMIL30"; +var timesheetns="http://www.w3.org/2007/07/SMIL30/Timesheets"; +var xlinkns="http://www.w3.org/1999/xlink"; + +var animators = new Array(); // all animators +var id2anim = new Object(); // id -> animation elements (workaround a Gecko bug) +var animations = new Array(); // running animators +var timeZero; + +/** + * If declarative animations are not supported, + * the document animations are fetched and registered. + */ +function initSMIL() { + if (document.documentElement.getAttribute("smiling")=="fake") + return; + document.documentElement.setAttribute("smiling", "fake"); + smile(document); + + timeZero = new Date(); + // I schedule them (after having instanciating them, for sync-based events) + // (it doesn't work either: first 0s animation don't trigger begin event to the following -> make it asynchronous) + for (var i=0, j=animators.length; i 0) { + if (!request){ + // lazy initialization of XHR + request = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("MSXML2.XMLHTTP.3.0") : null; + if (request) { + if (request.overrideMimeType) + request.overrideMimeType('text/xml'); + request.onreadystatechange = xhrCallback; + } + } + if (request) { + request.open("GET", src, false); + request.send(null); + } else if (window.getURL && window.parseXML) { + getURL(src, getURLCallback); + } + } + continue; + } + var impl = document.implementation; + if ((namespaceURI==svgns && !impl.hasFeature("http://www.w3.org/TR/SVG11/feature#Animation", "1.1")) || + (namespaceURI==smilanimns && !impl.hasFeature(smilanimns, "1.1")) || + (namespaceURI==smil2ns && !impl.hasFeature(smil2ns, "2.0")) || + (namespaceURI==smil21ns && !impl.hasFeature(smil21ns, "2.1")) || + (namespaceURI==smil3ns && !impl.hasFeature(smil3ns, "3.0")) || + (namespaceURI==timesheetns && !impl.hasFeature(timesheetns, "1.0"))) { + if (nodeName=="set" || nodeName=="animate" || nodeName=="animateColor" || nodeName=="animateMotion" || nodeName=="animateTransform") { + var targets = getTargets(anim); + var elAnimators = new Array(); + for (var k=0; k, , , ... + * (there can be more than one Animator for each element) + */ +Animator.prototype = { + + /** + * Registers the animation. + * It schedules the beginnings and endings. + */ + register : function() { + var begin = this.anim.getAttribute("begin"); + if (!begin) + begin = "0"; + this.schedule(begin, this.begin); + var end = this.anim.getAttribute("end"); + if (end) + this.schedule(end, this.finish); + }, + + /** + * Schedules the starts or ends of the animation. + */ + schedule : function(timeValueList, func) { + var me = this; // I do that because if I use "this", the addEventListener understands the event source + var timeValues = timeValueList.split(";"); + for (var i=0; i11 && time.substring(0,10)=="wallclock(") { + var wallclock = new Date(); + wallclock.setISO8601(time.substring(10,time.length-1)); + var now = new Date(); + var diff = wallclock-now; + func.call(me, diff); + } else if (isNaN(parseInt(time))) { + var offset = 0; + var io = time.indexOf("+"); + if (io==-1) + io = time.indexOf("-"); + if (io!=-1) { + offset = toMillis(time.substring(io).replace(/ /g, "")); + time = time.substring(0, io).trim(); + } + io = time.indexOf("."); + var elements = new Array(); + if (io==-1) { + elements = [this.target]; + } else { + var id = time.substring(0, io); + if (id.indexOf("index(")==0) + id = id.substring(6,id.length-1)+this.index; + elements = getEventTargetsById(id, this.anim); + } + var event = time.substring(io+1); + var call = funk(func, me, offset); + for (var j=0; j=0) { + var me = this; + var myself = this.begin; + var call = function() {myself.call(me)}; + window.setTimeout(call, offset); + return; + } + this.startTime = new Date(); + if (offset && offset<0) { + this.startTime.setTime(this.startTime.getTime()+offset); + if (this.startTime5 && this.animVals[0].trim().substring(0,4)=="rgb(")) && + (this.freezed.substring(0,1)=="#" || colors[this.freezed] || (this.freezed.length>5 && this.freezed.trim().substring(0,4)=="rgb(")) ) + this.color(); + else { + var cp = new Array(); + var oneVal = this.animVals[0]; + var qualified = getUnit(oneVal); + cp[0] = qualified[0]; + this.unit = qualified[1]; + for (var i=1; i=1) + return this.end(); + + var iteration = parseFloat(this.iteration); + if (this.repeatCount && this.repeatCount!="indefinite" && (iteration+percent)>=this.repeatCount) { + if (this.fill=="freeze") + this.freezed = this.valueAt(this.repeatCount-iteration); + return this.end(); + } + if (this.repeatDur && this.repeatDur!="indefinite" && (curTime-this.startTime)>=toMillis(this.repeatDur)) { + if (this.fill=="freeze") { + var div = toMillis(this.repeatDur)/dur; + this.freezed = this.valueAt(div-Math.floor(div)); + } + return this.end(); + } + + if (anim.localName=="set") + return true; + + var curVal = this.valueAt(percent); + + this.step(curVal); + return true; + }, + + isInterpolable : function(from, to) { + var areN = (!isNaN(from) && !isNaN(to)); + if (!areN && from.trim().indexOf(" ")!=-1 && to.trim().indexOf(" ")!=-1) { + var tfrom = from.trim().split(" "); + var tto = to.trim().split(" "); + areN = true; + if (tfrom.length==tto.length) + for (var i=0; ipercent) + return tValues[i-1]; + return tValues[tValues.length-1]; + } + var parts = tValues.length; + var div = Math.floor(percent*parts); + return tValues[div]; + } else { + var index; + if (this.keyTimes) { + for (var i=1; ipercent) { + index = i-1; + var t1 = this.keyTimes[index]; + percent = (percent-t1)/(this.keyTimes[i]-t1); + break; + } + } else { + var parts = tValues.length-1; + index = Math.floor(percent*parts); + percent = (percent%(1/parts))*parts; + } + if (this.calcMode=="spline") + percent = this.spline(percent, index); + return this.interpolate(this.normalize(tValues[index]), this.normalize(tValues[index+1]), percent); + } + }, + + spline : function(percent, index) { + var path = this.keySplines[index]; + var tot = path.getTotalLength(); + var step = tot/splinePrecision; + for (var i=0; i<=tot; i+=step) { + var pt = path.getPointAtLength(i); + if (pt.x>percent) { + var pt1 = path.getPointAtLength(i-step); + percent -= pt1.x; + percent /= pt.x-pt1.x; + return pt1.y+((pt.y-pt1.y)*percent); + } + } + var pt = path.getPointAtLength(tot); + var pt1 = path.getPointAtLength(tot-step); + percent -= pt1.x; + percent /= pt.x-pt1.x; + return pt1.y+((pt.y-pt1.y)*percent); + }, + + /** + * Does the interpolation. + * This function is overriden. + */ + interpolate : function(from, to, percent) { + if (!this.isInterpolable(from, to)) { + if (percent<.5) + return from; + else + return to; + } + if (from.trim().indexOf(" ")!=-1) { + var tfrom = from.split(" "); + var tto = to.split(" "); + var ret = new Array(); + for (var i=0; i=this.repeatCount) + return this.finish(); + else if (this.repeatDur && this.repeatDur!="indefinite" && (now-this.startTime)>=toMillis(this.repeatDur)) + return this.finish(); + else { + if (this.accumulate=="sum") { + var curVal = this.getCurVal(); + if (!curVal && propDefaults[this.attributeName] ) + curVal = propDefaults[this.attributeName]; + + if (this.by && !this.from) { + this.animVals[0] = curVal; + this.animVals[1] = this.add(this.normalize(curVal), this.normalize(this.by)); + } else { + for (var i=0; i=toMillis(this.min)) + return true; + } + if (offset && offset>0) { + var me = this; + var myself = this.finish; + var call = function() {myself.call(me)}; + window.setTimeout(call, offset); + return true; + } + if (offset && offset<0) { + var now = new Date(); + now.setTime(now.getTime()+offset); + if (now7 && event.substring(0,6)=="repeat") { + var iteration = event.substring(7,event.length-1); + this.repeatListeners.push(func); + this.repeatIterations.push(iteration); + } + }, + + /** + * Returns the path linked to this animateMotion. + */ + getPath : function() { + var mpath = this.anim.getElementsByTagNameNS(svgns,"mpath")[0]; + if (mpath) { + var pathHref = mpath.getAttributeNS(xlinkns, "href"); + return document.getElementById(pathHref.substring(1)); + } else { + var d = this.anim.getAttribute("path"); + if (d) { + var pathEl = createPath(d); + //pathEl.setAttribute("display", "none"); + //this.anim.parentNode.appendChild(pathEl); + return pathEl; + } + } + return null; + }, + + /** + * Initializes this animator as a translation (x,y): + * or + * without a path + */ + translation : function() { + if (this.by && this.by.indexOf(",")==-1) + this.by = this.by+",0"; + this.normalize = function(value) { + var coords = value.replace(/,/g," ").replace(/ +/," ").split(/ /); + if (coords.length==1) + coords[1] = "0"; + //coords[1] = this.initVal.split(",")[1]; + coords[0] = parseFloat(coords[0]); + coords[1] = parseFloat(coords[1]); + return coords; + }; + this.add = function(a, b) { + var x = a[0]+b[0]; + var y = a[1]+b[1]; + return x+","+y; + }; + this.isInterpolable = function(from, to) { return true; }; + this.interpolate = function(from, to, percent) { + var x = from[0]+((to[0]-from[0])*percent); + var y = from[1]+((to[1]-from[1])*percent); + return x+","+y; + }; + }, + + /** + * Initializes this animator as a color animation: + * or + * on a color attribute + */ + color : function() { + this.isInterpolable = function(from, to) { return true; }; + this.interpolate = function(from, to, percent) { + var r = Math.round(from[0]+((to[0]-from[0])*percent)); + var g = Math.round(from[1]+((to[1]-from[1])*percent)); + var b = Math.round(from[2]+((to[2]-from[2])*percent)); + var val = "rgb("+r+","+g+","+b+")"; + return val; + }; + this.normalize = function(value) { + var rgb = toRGB(value); + if (rgb==null) + return toRGB(propDefaults[this.attributeName]); + return rgb; + } + this.add = function(a, b) { + var ret = new Array(); + for (var i=0; i=0; --i) + if (ar[i]=="") + ar.splice(i,1); + return ar; + }; + } + this.from = anim.getAttribute("from"); + this.to = anim.getAttribute("to"); + this.by = anim.getAttribute("by"); + this.values = anim.getAttribute("values"); + if (this.values) { + this.values = this.values.trim(); + if (this.values.substring(this.values.length-1)==";") + this.values = this.values.substring(0, this.values.length-1); + } + this.calcMode = anim.getAttribute("calcMode"); + this.keyTimes = anim.getAttribute("keyTimes"); + if (this.keyTimes) { + this.keyTimes = this.keyTimes.split(";"); + for (var i=0; i0 && (!this.computedDur || this.computedDur>this.computedMax)) + this.computedDur = this.computedMax; + } + this.min = anim.getAttribute("min"); + if (this.min) { + this.computedMin = toMillis(this.min); + if (!this.computedDur || this.computedDur0) { + var transList = curTrans.animVal; + return decompose(transList.getItem(0).matrix, "translate"); + } else + return "0,0"; + }; + this.path = this.getPath(); + if (this.path) { + this.valueAt = function(percent) { + var length = this.path.getTotalLength(); + var point = this.path.getPointAtLength(percent*length); + return point.x+","+point.y; + }; + } else { + this.translation(); + } + this.freeze = function() { + var val = this.valueAt(1); + this.step(val); + }; + if (this.keyPoints && this.keyTimes) { + this.pathKeyTimes = this.keyTimes; + this.keyTimes = null; + this.superValueAt = this.valueAt; + this.valueAt = function(percent) { + for (var i=1; ipercent) { + var pt = this.keyPoints[i-1]; + if (this.calcMode=="discrete") + fakePC = pt; + else { + var t1 = this.pathKeyTimes[i-1]; + percent = (percent-t1)/(this.pathKeyTimes[i]-t1); + fakePC = pt+((this.keyPoints[i]-pt)*percent) + } + break; + } + } + return this.superValueAt(fakePC); + }; + } + this.step = function(value) { + var attributeName = this.attributeName; + value = "translate("+value+")"; + this.target.setAttribute("transform", value); + }; + + } else if (nodeName=="animateTransform") { + + this.isInterpolable = function(from, to) { return true; }; + this.getCurVal = function() { + var type = this.type; + var curTrans = this.target.transform; + if (curTrans && curTrans.animVal.numberOfItems>0) { + var transList = curTrans.animVal; + return decompose(transList.getItem(0).matrix, type); + } else { + if (type=="scale") + return "1,1"; + else if (type=="translate") + return "0,0"; + else if (type=="rotate") + return "0,0,0"; + else + return 0; + } + }; + + if (this.type=="scale") { + this.normalize = function(value) { + value = value.replace(/,/g," "); + var coords = value.split(" "); + if (coords.length==1) + coords[1] = coords[0]; + coords[0] = parseFloat(coords[0]); + coords[1] = parseFloat(coords[1]); + return coords; + }; + this.add = function(a, b) { + var ret = new Array(); + for (var i=0; i value + // then f(t) cannot return false when autostoping -> we must find another mechanism +} + + +/** + * Converts a clock-value to milliseconds. + * Supported: "s" | "ms" | "min" | "h" | no-units + */ +function toMillis(time) { + time = time.trim(); + var len = time.length; + var io = time.indexOf(":"); + + if (io!=-1) { + var clockVal = time.split(":"); + var len = clockVal.length; + time = 0; + if (clockVal.length==3) + time += parseInt(clockVal[0])*3600000; + time += parseInt(clockVal[len-2])*60000; + time += parseFloat(clockVal[len-1])*1000; + } else if (len>2 && time.substring(len-2)=="ms") { + time = time.substring(0, time.length-2); + } else if (len>1 && time.substring(len-1)=="s") { + time = time.substring(0, time.length-1); + time = time*1000; + } else if (len>3 && time.substring(len-3)=="min") { + time = time.substring(0, time.length-3); + time = time*60000; + } else if (len>1 && time.substring(len-1)=="h") { + time = time.substring(0, time.length-1); + time = time*3600000; + } else { + time = time*1000; + } + return parseFloat(time); +} + + +/** + * Decompose a matrix into its scale, translate, rotate or skew. + */ +function decompose(matrix, type) { + if (type=="translate") + return matrix.e+","+matrix.f; + + var a = matrix.a; + var b = matrix.b; + var c = matrix.c; + var d = matrix.d; + + if (type=="rotate") + return Math.atan2(c,a)+",0,0"; + + var ModA = Math.sqrt(a*a+c*c); + var ModB = Math.sqrt(b*b+d*d); + + if (type=="scale") { + var AxB = a*d-b*c; + var scaleX = AxB==0?0:(AxB/ModA); + var scaleY = ModB; + return scaleX+","+scaleY; + } + var AdotB = a*b+c*d; + var shear = Math.PI/2-Math.acos(AdotB==0?0:(AdotB/(ModB*ModA))); + return (shear*180)/Math.PI; +} + + +/** + * Convert an rgb(), #XXX, #XXXXXX or named color + * into an [r,g,b] array. + */ +function toRGB(color) { + if (color.substring(0, 3)=="rgb") { + color = color.replace(/ /g, ""); + color = color.replace("rgb(", ""); + color = color.replace(")", ""); + var rgb = color.split(","); + for (var i=0; i0 && str.substring(vlen)==units[i]) { + var val = str.substring(0, vlen); + if (!isNaN(val)) + return [val,units[i]]; + } + } + } + return [str,null]; +} + +var colors = { + aliceblue : [240, 248, 255], + antiquewhite : [250, 235, 215], + aqua : [0, 255, 255], + aquamarine : [127, 255, 212], + azure : [240, 255, 255], + beige : [245, 245, 220], + bisque : [255, 228, 196], + black : [0, 0, 0], + blanchedalmond : [255, 235, 205], + blue : [0, 0, 255], + blueviolet : [138, 43, 226], + brown : [165, 42, 42], + burlywood : [222, 184, 135], + cadetblue : [95, 158, 160], + chartreuse : [127, 255, 0], + chocolate : [210, 105, 30], + coral : [255, 127, 80], + cornflowerblue : [100, 149, 237], + cornsilk : [255, 248, 220], + crimson : [220, 20, 60], + cyan : [0, 255, 255], + darkblue : [0, 0, 139], + darkcyan : [0, 139, 139], + darkgoldenrod : [184, 134, 11], + darkgray : [169, 169, 169], + darkgreen : [0, 100, 0], + darkgrey : [169, 169, 169], + darkkhaki : [189, 183, 107], + darkmagenta : [139, 0, 139], + darkolivegreen : [85, 107, 47], + darkorange : [255, 140, 0], + darkorchid : [153, 50, 204], + darkred : [139, 0, 0], + darksalmon : [233, 150, 122], + darkseagreen : [143, 188, 143], + darkslateblue : [72, 61, 139], + darkslategray : [47, 79, 79], + darkslategrey : [47, 79, 79], + darkturquoise : [0, 206, 209], + darkviolet : [148, 0, 211], + deeppink : [255, 20, 147], + deepskyblue : [0, 191, 255], + dimgray : [105, 105, 105], + dimgrey : [105, 105, 105], + dodgerblue : [30, 144, 255], + firebrick : [178, 34, 34], + floralwhite : [255, 250, 240], + forestgreen : [34, 139, 34], + fuchsia : [255, 0, 255], + gainsboro : [220, 220, 220], + ghostwhite : [248, 248, 255], + gold : [255, 215, 0], + goldenrod : [218, 165, 32], + gray : [128, 128, 128], + grey : [128, 128, 128], + green : [0, 128, 0], + greenyellow : [173, 255, 47], + honeydew : [240, 255, 240], + hotpink : [255, 105, 180], + indianred : [205, 92, 92], + indigo : [75, 0, 130], + ivory : [255, 255, 240], + khaki : [240, 230, 140], + lavender : [230, 230, 250], + lavenderblush : [255, 240, 245], + lawngreen : [124, 252, 0], + lemonchiffon : [255, 250, 205], + lightblue : [173, 216, 230], + lightcoral : [240, 128, 128], + lightcyan : [224, 255, 255], + lightgoldenrodyellow : [250, 250, 210], + lightgray : [211, 211, 211], + lightgreen : [144, 238, 144], + lightgrey : [211, 211, 211], + lightpink : [255, 182, 193], + lightsalmon : [255, 160, 122], + lightseagreen : [32, 178, 170], + lightskyblue : [135, 206, 250], + lightslategray : [119, 136, 153], + lightslategrey : [119, 136, 153], + lightsteelblue : [176, 196, 222], + lightyellow : [255, 255, 224], + lime : [0, 255, 0], + limegreen : [50, 205, 50], + linen : [250, 240, 230], + magenta : [255, 0, 255], + maroon : [128, 0, 0], + mediumaquamarine : [102, 205, 170], + mediumblue : [0, 0, 205], + mediumorchid : [186, 85, 211], + mediumpurple : [147, 112, 219], + mediumseagreen : [60, 179, 113], + mediumslateblue : [123, 104, 238], + mediumspringgreen : [0, 250, 154], + mediumturquoise : [72, 209, 204], + mediumvioletred : [199, 21, 133], + midnightblue : [25, 25, 112], + mintcream : [245, 255, 250], + mistyrose : [255, 228, 225], + moccasin : [255, 228, 181], + navajowhite : [255, 222, 173], + navy : [0, 0, 128], + oldlace : [253, 245, 230], + olive : [128, 128, 0], + olivedrab : [107, 142, 35], + orange : [255, 165, 0], + orangered : [255, 69, 0], + orchid : [218, 112, 214], + palegoldenrod : [238, 232, 170], + palegreen : [152, 251, 152], + paleturquoise : [175, 238, 238], + palevioletred : [219, 112, 147], + papayawhip : [255, 239, 213], + peachpuff : [255, 218, 185], + peru : [205, 133, 63], + pink : [255, 192, 203], + plum : [221, 160, 221], + powderblue : [176, 224, 230], + purple : [128, 0, 128], + red : [255, 0, 0], + rosybrown : [188, 143, 143], + royalblue : [65, 105, 225], + saddlebrown : [139, 69, 19], + salmon : [250, 128, 114], + sandybrown : [244, 164, 96], + seagreen : [46, 139, 87], + seashell : [255, 245, 238], + sienna : [160, 82, 45], + silver : [192, 192, 192], + skyblue : [135, 206, 235], + slateblue : [106, 90, 205], + slategray : [112, 128, 144], + slategrey : [112, 128, 144], + snow : [255, 250, 250], + springgreen : [0, 255, 127], + steelblue : [70, 130, 180], + tan : [210, 180, 140], + teal : [0, 128, 128], + thistle : [216, 191, 216], + tomato : [255, 99, 71], + turquoise : [64, 224, 208], + violet : [238, 130, 238], + wheat : [245, 222, 179], + white : [255, 255, 255], + whitesmoke : [245, 245, 245], + yellow : [255, 255, 0], + yellowgreen : [154, 205, 50] +}; + +var propDefaults = { + "font" : "see individual properties", + "font-family" : "Arial", + "font-size" : "medium", + "font-size-adjust" : "none", + "font-stretch" : "normal", + "font-style" : "normal", + "font-variant" : "normal", + "font-weight" : "normal", + "direction" : "ltr", + "letter-spacing" : "normal", + "text-decoration" : "none", + "unicode-bidi" : "normal", + "word-spacing" : "normal", + "clip" : "auto", + "color" : "depends on user agent", + "cursor" : "auto", + "display" : "inline", + "overflow" : "hidden", + "visibility" : "visible", + "clip-path" : "none", + "clip-rule" : "nonzero", + "mask" : "none", + "opacity" : "1", + "enable-background" : "accumulate", + "filter" : "none", + "flood-color" : "black", + "flood-opacity" : "1", + "lighting-color" : "white", + "stop-color" : "black", + "stop-opacity" : "1", + "pointer-events" : "visiblePainted", + "color-interpolation" : "sRGB", + "color-interpolation-filters" : "linearRGB", + "color-profile" : "auto", + "color-rendering" : "auto", + "fill" : "black", + "fill-opacity" : "1", + "fill-rule" : "nonzero", + "image-rendering" : "auto", + "marker-end" : "none", + "marker-mid" : "none", + "marker-start" : "none", + "shape-rendering" : "auto", + "stroke" : "none", + "stroke-dasharray" : "none", + "stroke-dashoffset" : "0", + "stroke-linecap" : "butt", + "stroke-linejoin" : "miter", + "stroke-miterlimit" : "4", + "stroke-opacity" : "1", + "stroke-width" : "1", + "text-rendering" : "auto", + "alignment-baseline" : "0", + "baseline-shift" : "baseline", + "dominant-baseline" : "auto", + "glyph-orientation-horizontal" : "0", + "glyph-orientation-vertical" : "auto", + "kerning" : "auto", + "text-anchor" : "start", + "writing-mode" : "lr-tb" +}; + +function funk(func, obj, arg) { + return function() {func.call(obj, arg);}; +} + +/** + * Removes the leading and trailing spaces chars from the string. + * NOTE: part of ES5, so use feature detection + * http://stackoverflow.com/questions/2308134/trim-in-javascript-not-working-in-ie/#2308157 + * NOTE: the regular expression used in fallback is placed in global namespace for performance + * (as it's far better having a "singleton" than bloating every string instance) + */ +if (typeof String.prototype.trim !== "function") { + window._trimRegExp = new RegExp("^\\s+|\\s+$", "g"); + String.prototype.trim = function() { + return this.replace(window._trimRegExp, ""); + }; +} + +/** + * Set an ISO 8601 timestamp to a Date object. + * NOTE: as ES5 doesn't define precisely what "parse" should do, we run a sample to test for feasibility + * http://stackoverflow.com/questions/2479714/does-javascript-ecmascript3-support-iso8601-date-parsing/#2481375 + * NOTE: the regular expression used in fallback is placed in global namespace for performance + * (as it's far better having a "singleton" than bloating every date instance) + */ +if (!isNaN(Date.parse("2012-04-22T19:53:32Z"))){ + // parse did well, use the native implementation + Date.prototype.setISO8601 = function (string) { + this.setTime(Date.parse(string)); + }; +}else{ + window._setISO8601RegExp = new RegExp( + "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" + ); + Date.prototype.setISO8601 = function (string) { + var d = window._setISO8601RegExp.exec(string); + + var offset = 0; + var date = new Date(d[1], 0, 1); + + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { date.setHours(d[7]); } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + offset -= date.getTimezoneOffset(); + time = (Number(date) + (offset * 60 * 1000)); + this.setTime(Number(time)); + }; +} + +try { + window.addEventListener("load", initSMIL, false); +} catch(exc) {} diff --git a/demo/routing/css/routing.css b/demo/routing/css/routing.css new file mode 100644 index 0000000000..39762767a9 --- /dev/null +++ b/demo/routing/css/routing.css @@ -0,0 +1,16 @@ +.router-switch { + width: 100px; + margin: 4px; + background: #68ddd5; + color: #484e68; + outline: none; + font-size: 12px; + border: none; + padding: 4px; + border-radius: 5px; + cursor: pointer; +} +.router-switch:hover { + background: #fdc685; + color: #fff; +} \ No newline at end of file diff --git a/demo/routing/index.html b/demo/routing/index.html new file mode 100644 index 0000000000..11ca395369 --- /dev/null +++ b/demo/routing/index.html @@ -0,0 +1,41 @@ + + + + + + Smart Routing + + + + + + + +
+

Smart Routing

+

Try to drag elements and see how the link avoids the obstacles.

+
+ +
+ + + + + + + + + +
+ +
+ + + + + + + + + + diff --git a/demo/routing/src/routing.js b/demo/routing/src/routing.js new file mode 100644 index 0000000000..35f8d5513b --- /dev/null +++ b/demo/routing/src/routing.js @@ -0,0 +1,99 @@ +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 1000, + height: 600, + gridSize: 10, + model: graph +}); + + +var source = new joint.shapes.basic.Rect({ + position: { x: 50, y: 50 }, + size: { width: 140, height: 70 }, + attrs: { + rect: { + fill: { + type: 'linearGradient', + stops: [ + { offset: '0%', color: '#f7a07b' }, + { offset: '100%', color: '#fe8550' } + ], + attrs: { x1: '0%', y1: '0%', x2: '0%', y2: '100%' } + }, + stroke: '#ed8661', + 'stroke-width': 2 + }, + text: { + text: 'Source', + fill: '#f0f0f0', + 'font-size': 18, + 'font-weight': 'lighter', + 'font-variant': 'small-caps' + } + } +}); + +var target = source.clone().translate(750, 400).attr('text/text', 'Target'); + +var link = new joint.dia.Link({ + source: { id: source.id }, + target: { id: target.id }, + router: { name: 'manhattan' }, + connector: { name: 'rounded' }, + attrs: { + '.connection': { + stroke: '#333333', + 'stroke-width': 3 + }, + '.marker-target': { + fill: '#333333', + d: 'M 10 0 L 0 5 L 10 10 z' + } + } +}); + +var obstacle = source.clone().translate(300, 100).attr({ + text: { + text: 'Obstacle', + fill: '#eee' + }, + rect: { + fill: { + stops: [{ color: '#b5acf9' }, { color: '#9687fe' }] + }, + stroke: '#8e89e5', + 'stroke-width': 2 + } +}); + +var obstacles = [ + obstacle, + obstacle.clone().translate(200, 100), + obstacle.clone().translate(-200, 150) +]; + +graph.addCells(obstacles).addCells([source, target, link]); + +link.toBack(); + +graph.on('change:position', function(cell) { + + // has an obstacle been moved? Then reroute the link. + if (_.contains(obstacles, cell)) paper.findViewByModel(link).update(); +}); + +$('.router-switch').on('click', function(evt) { + + var router = $(evt.target).data('router'); + var connector = $(evt.target).data('connector'); + + if (router) { + link.set('router', { name: router }); + } else { + link.unset('router'); + } + + link.set('connector', { name: connector }); +}); \ No newline at end of file diff --git a/demo/umlcd/css/umlcd.css b/demo/umlcd/css/umlcd.css new file mode 100644 index 0000000000..738758f683 --- /dev/null +++ b/demo/umlcd/css/umlcd.css @@ -0,0 +1,4 @@ +.joint-type-uml-composition .marker-target { + fill: #4a4e69; + stroke: #4a4e69; +} diff --git a/demo/umlcd/index.html b/demo/umlcd/index.html new file mode 100644 index 0000000000..2e22e581fc --- /dev/null +++ b/demo/umlcd/index.html @@ -0,0 +1,29 @@ + + + + + + Unified Modeling Language + + + + + + + +
+

Unified Modeling Language

+

The Class Diagram

+
+ +
+ + + + + + + + + + diff --git a/demo/umlcd/src/umlcd.js b/demo/umlcd/src/umlcd.js new file mode 100644 index 0000000000..740ff2c659 --- /dev/null +++ b/demo/umlcd/src/umlcd.js @@ -0,0 +1,179 @@ +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 800, + height: 600, + gridSize: 1, + model: graph +}); + + +var uml = joint.shapes.uml; + +var classes = { + + mammal: new uml.Interface({ + position: { x:300 , y: 50 }, + size: { width: 240, height: 100 }, + name: 'Mammal', + attributes: ['dob: Date'], + methods: ['+ setDateOfBirth(dob: Date): Void','+ getAgeAsDays(): Numeric'], + attrs: { + '.uml-class-name-rect': { + fill: '#feb662', + stroke: '#ffffff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#fdc886', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-text': { + ref: '.uml-class-attrs-rect', + 'ref-y': 0.5, + 'y-alignment': 'middle' + }, + '.uml-class-methods-text': { + ref: '.uml-class-methods-rect', + 'ref-y': 0.5, + 'y-alignment': 'middle' + } + + } + }), + + person: new uml.Abstract({ + position: { x:300 , y: 300 }, + size: { width: 260, height: 100 }, + name: 'Person', + attributes: ['firstName: String','lastName: String'], + methods: ['+ setName(first: String, last: String): Void','+ getName(): String'], + attrs: { + '.uml-class-name-rect': { + fill: '#68ddd5', + stroke: '#ffffff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#9687fe', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-methods-text, .uml-class-attrs-text': { + fill: '#fff' + } + } + }), + + bloodgroup: new uml.Class({ + position: { x:20 , y: 190 }, + size: { width: 220, height: 100 }, + name: 'BloodGroup', + attributes: ['bloodGroup: String'], + methods: ['+ isCompatible(bG: String): Boolean'], + attrs: { + '.uml-class-name-rect': { + fill: '#ff8450', + stroke: '#fff', + 'stroke-width': 0.5, + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#fe976a', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-text': { + ref: '.uml-class-attrs-rect', + 'ref-y': 0.5, + 'y-alignment': 'middle' + }, + '.uml-class-methods-text': { + ref: '.uml-class-methods-rect', + 'ref-y': 0.5, + 'y-alignment': 'middle' + } + } + }), + + address: new uml.Class({ + position: { x:630 , y: 190 }, + size: { width: 160, height: 100 }, + name: 'Address', + attributes: ['houseNumber: Integer','streetName: String','town: String','postcode: String'], + methods: [], + attrs: { + '.uml-class-name-rect': { + fill: '#ff8450', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#fe976a', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-text': { + 'ref-y': 0.5, + 'y-alignment': 'middle' + } + } + + }), + + man: new uml.Class({ + position: { x:200 , y: 500 }, + size: { width: 180, height: 50 }, + name: 'Man', + attrs: { + '.uml-class-name-rect': { + fill: '#ff8450', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#fe976a', + stroke: '#fff', + 'stroke-width': 0.5 + } + } + }), + + woman: new uml.Class({ + position: { x:450 , y: 500 }, + size: { width: 180, height: 50 }, + name: 'Woman', + methods: ['+ giveABrith(): Person []'], + attrs: { + '.uml-class-name-rect': { + fill: '#ff8450', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-attrs-rect, .uml-class-methods-rect': { + fill: '#fe976a', + stroke: '#fff', + 'stroke-width': 0.5 + }, + '.uml-class-methods-text': { + 'ref-y': 0.5, + 'y-alignment': 'middle' + } + } + }) + + +}; + +_.each(classes, function(c) { graph.addCell(c); }); + +var relations = [ + new uml.Generalization({ source: { id: classes.man.id }, target: { id: classes.person.id }}), + new uml.Generalization({ source: { id: classes.woman.id }, target: { id: classes.person.id }}), + new uml.Implementation({ source: { id: classes.person.id }, target: { id: classes.mammal.id }}), + new uml.Aggregation({ source: { id: classes.person.id }, target: { id: classes.address.id }}), + new uml.Composition({ source: { id: classes.person.id }, target: { id: classes.bloodgroup.id }}) +]; + +_.each(relations, function(r) { graph.addCell(r); }); diff --git a/demo/umlsc/index.html b/demo/umlsc/index.html new file mode 100644 index 0000000000..344e36ca30 --- /dev/null +++ b/demo/umlsc/index.html @@ -0,0 +1,28 @@ + + + + + + Unified Modeling Language + + + + + + +
+

Unified Modeling Language

+

The Statechart Diagram

+
+ +
+ + + + + + + + + + diff --git a/demo/umlsc/src/umlsc.js b/demo/umlsc/src/umlsc.js new file mode 100644 index 0000000000..723942a247 --- /dev/null +++ b/demo/umlsc/src/umlsc.js @@ -0,0 +1,149 @@ +var graph = new joint.dia.Graph(); + +var paper = new joint.dia.Paper({ + el: $('#paper'), + width: 800, + height: 600, + gridSize: 1, + model: graph +}); + + +var uml = joint.shapes.uml; + +var states = { + + s0: new uml.StartState({ + position: { x:20 , y: 20 }, + size: { width: 30, height: 30 }, + attrs: { + 'circle': { + fill: '#4b4a67', + stroke: 'none' + } + } + }), + + s1: new uml.State({ + position: { x:100 , y: 100 }, + size: { width: 200, height: 100 }, + name: "state 1", + events: ["entry / init()","exit / destroy()"], + attrs: { + '.uml-state-body': { + fill: 'rgba(48, 208, 198, 0.1)', + stroke: 'rgba(48, 208, 198, 0.5)', + 'stroke-width': 1.5 + }, + '.uml-state-separator': { + stroke: 'rgba(48, 208, 198, 0.4)' + } + } + }), + + s2: new uml.State({ + position: { x:400 , y: 200 }, + size: { width: 300, height: 300 }, + name: "state 2", + events: ["entry / create()","exit / kill()","A / foo()","B / bar()"], + attrs: { + '.uml-state-body': { + fill: 'rgba(48, 208, 198, 0.1)', + stroke: 'rgba(48, 208, 198, 0.5)', + 'stroke-width': 1.5 + }, + '.uml-state-separator': { + stroke: 'rgba(48, 208, 198, 0.4)' + } + } + }), + + s3: new uml.State({ + position: { x:130 , y: 400 }, + size: { width: 160, height: 60 }, + name: "state 3", + events: ["entry / create()","exit / kill()"], + attrs: { + '.uml-state-body': { + fill: 'rgba(48, 208, 198, 0.1)', + stroke: 'rgba(48, 208, 198, 0.5)', + 'stroke-width': 1.5 + }, + '.uml-state-separator': { + stroke: 'rgba(48, 208, 198, 0.4)' + } + } + }), + + s4: new uml.State({ + position: { x:530 , y: 400 }, + size: { width: 160, height: 50 }, + name: "sub state 4", + events: ["entry / create()"], + attrs: { + '.uml-state-body': { + fill: 'rgba(48, 208, 198, 0.1)', + stroke: 'rgba(48, 208, 198, 0.5)', + 'stroke-width': 1.5 + }, + '.uml-state-separator': { + stroke: 'rgba(48, 208, 198, 0.4)' + } + } + }), + + se: new uml.EndState({ + position: { x:750 , y: 550 }, + size: { width: 30, height: 30 }, + attrs: { + '.outer': { + stroke: "#4b4a67", + 'stroke-width': 2 + }, + '.inner': { + fill: '#4b4a67' + } + } + }) + +}; +_.each(states, function(c) { graph.addCell(c); }); + +states.s2.embed(states.s4); + +var linkAttrs = { + 'fill': 'none', + 'stroke-linejoin': 'round', + 'stroke-width': '2', + 'stroke': '#4b4a67' +}; + +var transitons = [ + new uml.Transition({ + source: { id: states.s0.id }, + target: { id: states.s1.id }, + attrs: {'.connection': linkAttrs } + }), + new uml.Transition({ + source: { id: states.s1.id }, + target: { id: states.s2.id }, + attrs: {'.connection': linkAttrs } + }), + new uml.Transition({ + source: { id: states.s1.id }, + target: { id: states.s3.id }, + attrs: {'.connection': linkAttrs } + }), + new uml.Transition({ + source: { id: states.s3.id }, + target: { id: states.s4.id }, + attrs: {'.connection': linkAttrs } + }), + new uml.Transition({ + source: { id: states.s2.id }, + target: { id: states.se.id }, + attrs: {'.connection': linkAttrs } + }) +]; + +graph.addCells(transitons); diff --git a/package.json b/package.json index c0527a20cf..8a1807317b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "devDependencies": { "async": "2.0.1", + "chai": "3.5.0", "cheerio": "0.20.0", "express": "4.14.0", "grunt": "1.0.1", @@ -67,11 +68,14 @@ "load-grunt-tasks": "3.5.0", "mocha": "2.5.3", "open-sans-fontface": "https://github.com/clientIO/open-sans/archive/1.4.2.tar.gz", + "phantomjs-prebuilt": "2.1.7", "prismjs": "1.5.1", + "selenium-standalone": "5.1.0", "serve-static": "1.11.1", "should": "11.0.0", "sinon": "1.17.5", "time-grunt": "1.4.0", + "webdriverio": "4.0.8", "webpack": "1.13.1", "webpack-dev-server": "1.14.1" } diff --git a/test/e2e/chess.js b/test/e2e/chess.js new file mode 100644 index 0000000000..0426cacf7b --- /dev/null +++ b/test/e2e/chess.js @@ -0,0 +1,68 @@ +/* + * e2e test for CHESS demo: http://jointjs.com/demos/chess + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Chess', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/chess/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#board .joint-type-chess-rookwhite') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be movable', function (done) { + + client.url(url) + .getAttribute('#board g[transform="translate(150,300)"]', 'model-id') // PawnWhite d2 + .then(function (modelId) { + this + .moveElement('#board g[model-id="' + modelId + '"]', 150 + 30, 200 + 20) // d4 + .then(function (transform) { + expect(transform).to.equal("translate(150,200)"); + done(); + }); + }); + + }); + + it('should be playable', function (done) { + + client.url(url) + .getAttribute('#board g[transform="translate(150,300)"]', 'model-id') // PawnWhite d2 + .then(function (modelId) { + this + .moveElement('#board g[model-id="' + modelId + '"]', 150 + 30, 200 + 20) // d4 + .pause(100) + .getText('#message') + .then(function (msg) { + // msg return: 1. d4 ? + var msgMoves = msg.split(' ').length; + expect(msgMoves).to.equal(3); + done(); + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/devs.js b/test/e2e/devs.js new file mode 100644 index 0000000000..2391598da1 --- /dev/null +++ b/test/e2e/devs.js @@ -0,0 +1,83 @@ +/* + * e2e test for DISCRETE EVENT SYSTEM SPECIFICATION demo: http://jointjs.com/demos/devs + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Discrete Event System Specification', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/devs/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-devs.joint-type-devs-atomic') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Coupled', function () { + + it('should be movable', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-devs.joint-type-devs-coupled') + .moveToObject('#paper .joint-type-devs.joint-type-devs-coupled', 50/* x-offset */, 50/* y-offset */) + .buttonDown() + .moveToObject('#paper svg', 40 /* x-offset */, 40 /* y-offset */) + .buttonUp() + .getAttribute('#paper .joint-type-devs.joint-type-devs-coupled', 'transform') + .then(function (transform) { + expect(transform).to.equal("translate(21,-10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-devs.joint-type-devs-coupled') + .moveToObject('#j_10 .tool-remove', 5/* x-offset */, 5/* y-offset */) + .click('#j_10 .tool-remove') + .waitForNotExist('#j_10') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/erd.js b/test/e2e/erd.js new file mode 100644 index 0000000000..c76ebe1d6b --- /dev/null +++ b/test/e2e/erd.js @@ -0,0 +1,75 @@ +/* + * e2e test for ER DIAGRAMS demo: http://jointjs.com/demos/erd + */ + +'use strict'; + +var expect = require('chai').expect; +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('ER Diagrams', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/erd/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-erd.joint-type-erd-identifyingrelationship') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Entity', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-erd.joint-type-erd-entity') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#j_14 .tool-remove') + .waitForNotExist('#j_14') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/finite-state-machines.js b/test/e2e/finite-state-machines.js new file mode 100644 index 0000000000..8df5e8601d --- /dev/null +++ b/test/e2e/finite-state-machines.js @@ -0,0 +1,84 @@ +/* + * e2e test for finite state machines demo: http://jointjs.com/demos/fsa + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Finite State Machines', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/fsa/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-fsa-state') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('State', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-fsa-state') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + var model_id; + client.url(url) + .getAttribute('#paper .joint-link', 'model-id') + .then(function (attr) { + model_id = attr[0]; + + this + .click('#paper g[model-id="' + model_id + '"] .tool-remove') + .waitForNotExist('#paper g[model-id="' + model_id + '"]') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/links.js b/test/e2e/links.js new file mode 100644 index 0000000000..874b05f563 --- /dev/null +++ b/test/e2e/links.js @@ -0,0 +1,89 @@ +/* + * e2e test for LINKS demo: http://jointjs.com/demos/links + */ + +'use strict'; + +var expect = require('chai').expect; +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Links demo', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/links/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Link', function () { + + it('should be removable', function (done) { + + client.url(url) + .click('#j_1 .tool-remove') + .waitForNotExist('#j_1') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be changeable', function (done) { + + client.url(url) + .getAttribute('#paper #j_1 path.connection', 'd') + .then(function (orig_connection_data) { + + this + .moveToObject('#paper #j_1', 100/* x-offset */, 10/* y-offset */) + .buttonDown() + .moveToObject('#paper svg', 100 /* x-offset */, 100 /* y-offset */) + .buttonUp() + .getAttribute('#paper #j_1 path.connection', 'd') + .then(function (new_connection_data) { + expect(new_connection_data).not.to.equal(orig_connection_data); + done(); + }); + + }); + }); + + it('should be stretchable', function (done) { + + client.url(url) + .getAttribute('#paper #j_1 path.connection', 'd') + .then(function (orig_connection_data) { + + this + .moveToObject('#paper #j_1 .marker-arrowhead-group-target', 10/* x-offset */, 10/* y-offset */) + .buttonDown() + .moveToObject('#paper svg', 500 /* x-offset */, 100 /* y-offset */) + .buttonUp() + .getAttribute('#paper #j_1 path.connection', 'd') + .then(function (new_connection_data) { + expect(new_connection_data).not.to.equal(orig_connection_data); + done(); + }); + + }); + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/logic-circuits.js b/test/e2e/logic-circuits.js new file mode 100644 index 0000000000..0de5eb2561 --- /dev/null +++ b/test/e2e/logic-circuits.js @@ -0,0 +1,75 @@ +/* + * e2e test for LOGIC CIRCUITS demo: http://jointjs.com/demos/joint-type-logic + */ + +'use strict'; + +var expect = require('chai').expect; +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Logic Circuits', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/logic/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-logic.joint-type-logic-repeater') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Element', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-logic.joint-type-logic-input') + .then(function (transform) { + expect(transform).to.equal("translate(10,5)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-logic.joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#j_11 .tool-remove') + .waitForNotExist('#j_11') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/organizational-charts.js b/test/e2e/organizational-charts.js new file mode 100644 index 0000000000..a977ca91ad --- /dev/null +++ b/test/e2e/organizational-charts.js @@ -0,0 +1,76 @@ +/* + * e2e test for ORGANIZATIONAL CHARTS demo: http://jointjs.com/demos/org + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Organizational Charts', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/org/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-org-member') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Member', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-org-member') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#j_12 .tool-remove') + .waitForNotExist('#j_12') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/paper-attributes.js b/test/e2e/paper-attributes.js new file mode 100644 index 0000000000..980b5147cf --- /dev/null +++ b/test/e2e/paper-attributes.js @@ -0,0 +1,161 @@ +/* + * e2e test for PAPER ATTRIBUTES demo: http://jointjs.com/demos/paper + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Paper attributes', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/paper/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-basic-path') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Attributes', function () { + + it('origin x should be changable', function (done) { + + client.url(url) + .changeRange('#ox', 60/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("translate(101,0)"); + done(); + }); + + }); + + it('origin y should be changable', function (done) { + + client.url(url) + .changeRange('#oy', 60/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("translate(0,101)"); + done(); + }); + + }); + + it('scale x should be changable', function (done) { + + client.url(url) + .changeRange('#sx', 35/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("scale(2.3,1)"); + done(); + }); + + }); + + it('scale y should be changable', function (done) { + + client.url(url) + .changeRange('#sy', 35/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("scale(1,2.3)"); + done(); + }); + + }); + + it('width should be changable', function (done) { + + client.url(url) + .changeRange('#width', 55/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.width).to.equal("928"); + done(); + }); + + }); + + it('height should be changable', function (done) { + + client.url(url) + .changeRange('#height', 35/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.height).to.equal("928"); + done(); + }); + + }); + + }); + + describe('Fit to content', function () { + + it('padding should be changable', function (done) { + + client.url(url) + .changeRange('#ftc-padding', 10/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("translate(0,25)"); + expect(data.width).to.equal("625"); + expect(data.height).to.equal("440"); + done(); + }); + + }); + + it('grid width should be changable', function (done) { + + client.url(url) + .changeRange('#ftc-grid-width', 10/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("translate(-75,-50)"); + expect(data.width).to.equal("525"); + expect(data.height).to.equal("290"); + done(); + }); + + }); + + it('grid height should be changable', function (done) { + + client.url(url) + .changeRange('#ftc-grid-height', 10/* orig range */, 80/* new range */) + .then(function (data) { + expect(data.transform).to.equal("translate(-75,0)"); + expect(data.width).to.equal("475"); + expect(data.height).to.equal("375"); + done(); + }); + + }); + + }); + + describe('Scale content to fit', function () { + + it('padding should be changable', function (done) { + + client.url(url) + .changeRange('#stf-padding', 10/* orig range */, 50/* new range */) + .then(function (data) { + expect(data.transform).not.to.equal(null); + done(); + }); + + }); + + }); +}); \ No newline at end of file diff --git a/test/e2e/petri-nets.js b/test/e2e/petri-nets.js new file mode 100644 index 0000000000..c892f0d2f6 --- /dev/null +++ b/test/e2e/petri-nets.js @@ -0,0 +1,80 @@ +/* + * e2e test for PETRI NETS demo: http://jointjs.com/demos/pn + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Petri Nets', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/petri nets/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-pn.joint-type-pn-transition') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Element', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveToObject('#paper .joint-type-pn.joint-type-pn-place', 30/* x-offset */, 30/* y-offset */) + .buttonDown() + .moveToObject('#paper svg', 40 /* x-offset */, 30 /* y-offset */) + .buttonUp() + .getAttribute('#paper .joint-type-pn.joint-type-pn-place', 'transform') + .then(function (transform) { + expect(transform[0]).to.equal("translate(10,20)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#j_10 .tool-remove') + .waitForNotExist('#j_10') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/smart-routing.js b/test/e2e/smart-routing.js new file mode 100644 index 0000000000..728c4506c7 --- /dev/null +++ b/test/e2e/smart-routing.js @@ -0,0 +1,130 @@ +/* + * e2e test for SMART ROUTING demo: http://jointjs.com/demos/routing + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Smart Routing', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/routing/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-basic-rect') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Element', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-basic-rect') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Route', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#paper .joint-link .tool-remove') + .waitForNotExist('#paper .joint-link') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be smart', function (done) { + + client.url(url) + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (orig_path) { + this + .moveElement('#paper #j_1', 250, 100) + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (new_path) { + expect(new_path).not.to.equal(orig_path); + done(); + }); + + }); + + }); + + }); + + describe('Button', function () { + + it('Normal/none should be clickable', function (done) { + + client.url(url) + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (orig_path) { + this + .click('button[data-connector="normal"]') + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (new_path) { + expect(new_path).not.to.equal(orig_path); + done(); + }); + + }); + + }); + + it('Normal/orthogonal should be clickable', function (done) { + + client.url(url) + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (orig_path) { + this + .click('button[data-router="orthogonal"]') + .getAttribute('#paper .joint-link path.connection', 'd') + .then(function (new_path) { + expect(new_path).not.to.equal(orig_path); + done(); + }); + + }); + + }); + + }); +}); \ No newline at end of file diff --git a/test/e2e/umlcd.js b/test/e2e/umlcd.js new file mode 100644 index 0000000000..e98e66d5b8 --- /dev/null +++ b/test/e2e/umlcd.js @@ -0,0 +1,76 @@ +/* + * e2e test for UNIFIED MODELING LANGUAGE demo: http://jointjs.com/demos/umlcd + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Unified Modeling Language - The Class Diagram', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/umlcd/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-uml.joint-type-uml-interface') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('Element', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-uml.joint-type-uml-class') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-uml.joint-type-uml-implementation.joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#paper .joint-type-uml.joint-type-uml-implementation.joint-link .tool-remove') + .waitForNotExist('#paper .joint-type-uml.joint-type-uml-implementation.joint-link') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2e/umlsc.js b/test/e2e/umlsc.js new file mode 100644 index 0000000000..f5f40efcfa --- /dev/null +++ b/test/e2e/umlsc.js @@ -0,0 +1,80 @@ +/* + * e2e test for UNIFIED MODELING LANGUAGE demo: http://jointjs.com/demos/umlsc + */ + +'use strict'; + +var expect = require('chai').expect; + +var e2eHelpers = require('../e2eHelpers'); +var config = e2eHelpers.config; + +describe('Unified Modeling Language - The Statechart Diagram', function () { + + var client; + var url; + + before(function (done) { + + url = e2eHelpers.staticUrl('/demo/umlsc/index.html'); + client = e2eHelpers.client(done); + }); + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-uml.joint-type-uml-endstate') + .then(function (exists) { + expect(exists).to.equal(true); + }) + .waitForExist('#paper .joint-type-uml.joint-type-uml-startstate') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + describe('State', function () { + + it('should be movable', function (done) { + + client.url(url) + .moveElement('#paper .joint-type-uml.joint-type-uml-state') + .then(function (transform) { + expect(transform[0]).to.equal("translate(20,10)"); + done(); + }); + + }); + + }); + + describe('Link', function () { + + it('should be visible', function (done) { + + client.url(url) + .waitForExist('#paper .joint-type-uml.joint-type-uml-transition.joint-link path.connection') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + it('should be removable', function (done) { + + client.url(url) + .click('#j_9 .tool-remove') + .waitForNotExist('#j_9') + .then(function (exists) { + expect(exists).to.equal(true); + done(); + }); + + }); + + }); + +}); \ No newline at end of file diff --git a/test/e2eHelpers.js b/test/e2eHelpers.js new file mode 100644 index 0000000000..3811a1f72f --- /dev/null +++ b/test/e2eHelpers.js @@ -0,0 +1,187 @@ +'use strict'; + +var _ = require('lodash'); +var express = require('express'); +var serveStatic = require('serve-static'); +var webdriverio = require('webdriverio'); + +var config = { + timeouts: { + 'script': 15000, + 'implicit': 5000, + 'page load': 30000 + }, + // Uncomment the following line to enable verbose logging for webdriverio. + // logLevel: 'verbose' +}; + +var app; +var client; +var host = 'localhost'; +var port = 3000; + +var e2eHelpers = module.exports = { + config: config, + staticUrl: function (uri) { + + return 'http://' + host + ':' + port + uri; + }, + setUp: function (cb) { + + e2eHelpers.createStaticServer(cb); + }, + createStaticServer: function (cb) { + + app = express(); + app.use(serveStatic(__dirname + '/..')); + app.server = app.listen(port, host); + cb(); + }, + tearDown: function (cb) { + + e2eHelpers.destroyClient(function (error) { + if (error) + return cb(error); + e2eHelpers.destroyStaticServer(cb); + }); + }, + destroyClient: function (cb) { + + if (!client) + return cb(); + + client.end().then(function () { + client = null; + cb(); + }); + }, + destroyStaticServer: function (cb) { + + app.server.close(); + app = null; + cb(); + }, + client: function (cb) { + + if (client) { + setTimeout(cb, 0); + return client; + } + + var options = { + // https://code.google.com/p/selenium/wiki/DesiredCapabilities + desiredCapabilities: { + browserName: process.env.E2E_BROWSER || 'chrome' + } + }; + + if (process.env.E2E_DESIRED) { + options.desiredCapabilities = JSON.parse(process.env.E2E_DESIRED); + } + + if (process.env.SELENIUM_HOST) { + options.host = process.env.SELENIUM_HOST; + } + + if (process.env.SELENIUM_PORT) { + options.port = process.env.SELENIUM_PORT; + } + + if (process.env.SELENIUM_USER) { + options.user = process.env.SELENIUM_USER; + } + + if (process.env.SELENIUM_KEY) { + options.key = process.env.SELENIUM_KEY; + } + + if (config.logLevel) { + options.logLevel = config.logLevel; + } + + client = webdriverio.remote(options); + + _.each(e2eHelpers.customCommands, function (fn, name) { + client.addCommand(name, fn, true); + }); + + return client.init() + .setViewportSize({width: 1024, height: 768}, false) + .timeouts('script', config.timeouts['script']) + + /* + Cannot set 'implicit' timeout because of a bug in webdriverio [1]. + [1] https://github.com/webdriverio/webdriverio/issues/974 + */ + // .timeouts('implicit', config.timeouts['implicit']) + .timeouts('page load', config.timeouts['page load']) + .then(function () { + cb(); + }).catch(cb); + }, + customCommands: { + waitForNotExist: function (selector, waitTime) { + + if (_.isUndefined(waitTime)) { + waitTime = config.timeouts['implicit']; + } + + return this.waitForExist(selector, waitTime, true/* reverse */).then(function (notExists) { + return notExists; + }); + }, + moveElement: function (selector, posX, posY) { + + var selectorId = selector.split(' ')[0]; + + if (!posX) { + posX = 40; + } + if (!posY) { + posY = 30; + } + + return this + .moveToObject(selector, 20/* x-offset */, 20/* y-offset */) + .buttonDown() + .moveToObject(selectorId + ' svg', posX /* x-offset */, posY /* y-offset */) + .buttonUp() + .getAttribute(selector, 'transform') + .then(function (transform) { + return transform; + }); + }, + changeRange: function (selector, posRangeX, newPosRangeX) { + + var retObj = { + transform: '', + width: '', + height: '' + }; + + return this + .waitForExist(selector) + .moveToObject(selector, posRangeX/* x-offset */, 10/* y-offset */) + .buttonDown() + .moveToObject(selector, newPosRangeX/* x-offset */, 10/* y-offset */) + .buttonUp() + .getAttribute('#paper .joint-viewport', 'transform') + .then(function (transform) { + retObj.transform = transform; + }) + .getAttribute('#paper svg', 'width') + .then(function (width) { + retObj.width = width; + }) + .getAttribute('#paper svg', 'height') + .then(function (height) { + retObj.height = height; + return retObj; + }); + } + } +}; + +// Set global hooks. +before(e2eHelpers.setUp); +after(e2eHelpers.tearDown);