<html>
<head>
<meta name="description" content="[A Markov Model Simulator]" />
<meta charset=utf-8 />
<title>JS Bin</title>
<script type="text/javascript" src="http://dat-gui.googlecode.com/git/build/dat.gui.min.js"></script>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.0-beta2.js"></script>
<script src="https://raw.github.com/anomal/RainbowVis-JS/master/rainbowvis.js"></script>
<script src="http://numericjs.com/numeric/lib/numeric-1.2.6.min.js"></script>
</head>
<body>
<div id="container"></div>
</body>
</html>
#container {
border: 2px solid gray;
width: 500px;
height: 800px;
}
//generate a transition matrix that is stickier as sigma is lower.
function randomBandedTransMatrix(n, sigma) {
var A = [];
for(i = 0; i < n; i++) {
var row = [];
var sum = 0;
for(j = 0; j < n; j++) {
var dist = j - i;
dist = dist - Math.round(dist / n) * n;
row[j] = Math.exp(-dist * dist / ( 2 * sigma * sigma));
sum += row[j];
}
A[i] = numeric.div(row, sum);
}
return(A);
}
//generate a path given an initial distribution, a transition probability matrix and number of elements.
function genPath(pi, A, n) {
var r = Math.random();
var x = pi[0];
var path = [0];
while(x < r && path[0] < pi.length - 1) {
path[0] += 1;
x += pi[ path[0] ];
}
for(i = 1; i < n; i++) {
r = Math.random();
x = A[ path[ i - 1 ] ][ 0 ];
path.push(0);
while(x < r && path[i] < A.length - 1) {
path[i] += 1;
x += A[ path[i - 1] ][ path[i] ];
}
}
return(path);
}
//sample and then move a path forward
function updatePath(path, A) {
var i;
for(i = 0; i < path.length - 2; i++) {
path[i] = path[i + 1];
}
//sample last section
//i = path.length - 1;
var r = Math.random();
var x = A[ path[ i - 1 ] ][ 0 ];
path[i] = 0;
while(x < r && path[i] < A.length - 1) {
path[i] += 1;
x += A[ path[i - 1] ][ path[i] ];
}
return(path);
}
var Grid = function (nx, ny, dx, dy) {
this.nx = nx;
this.ny = ny;
this.dx = dx;
this.dy = dy;
this.colorSpec = new Rainbow();
this.colorSpec.setSpectrum('blue', 'red');
this.colorSpec.setNumberRange(0, 125 / ny);
this.radius = 10;
this.nodes = [];
this.lines = [];
for(i = 0; i < nx; i++) {
for(j = 0; j < ny; j++) {
this.nodes.push(new Kinetic.Circle({
x: i * dx + this.radius*1.5,
y: j * dy + this.radius*1.5,
radius: this.radius,
fill: 'white',
stroke: 'black',
strokeWidth: 2
}));
}
}
};
Grid.prototype = {
render: function(nodeLayer, pathLayer) {
for(i = 0; i < this.lines.length; i++) {
pathLayer.add(this.lines[i]);
}
for(i = 0; i < this.nodes.length; i++) {
nodeLayer.add(this.nodes[i]);
}
},
cleanup: function() {
for(i = 0; i < this.lines.length; i++) {
this.lines[i].remove();
}
for(i = 0; i < this.nodes.length; i++) {
this.nodes[i].remove();
}
},
setNodeValue: function(i,j,v) {
this.nodes[i * this.ny + j].setFill(this.colorSpec.colourAt(v));
},
setNodeVector: function(i, v) {
for(j = 0; j < v.length; j++) {
this.nodes[i * this.ny + j].setFill(this.colorSpec.colourAt(v[j]));
}
},
setColourRange: function(lo, hi) {
this.colorSpec.setNumberRange(lo, hi);
},
enablePath: function(i, j, k, layer) {
this.lines.push(new Kinetic.Line({
points: [i * this.dx + this.radius *2, j * this.dy + this.radius *1.5,(i + 1) * this.dx + this.radius, k * this.dy + this.radius*1.5],
stroke: 'black',
strokeWidth: 2
}));
layer.add(this.lines[this.lines.length - 1]);
},
clearPaths: function() {
for(i = 0; i < this.lines.length; i++) {
this.lines[i].remove();
}
this.lines = [];
},
clearNodes: function() {
for(i = 0; i < this.nodes.length; i++) {
this.nodes[i].setFill('white');
}
},
enablePathVector: function(path, layer) {
for(i = 0; i < path.length - 1; i++) {
this.lines.push(new Kinetic.Line({
points: [i * this.dx + this.radius *2, path[i] * this.dy + this.radius *1.5,(i + 1) * this.dx + this.radius, path[i + 1] * this.dy + this.radius*1.5],
stroke: 'black',
strokeWidth: 2
}));
layer.add(this.lines[this.lines.length - 1]);
}
}
};
var Model = function(nx, ny, sigma, nodeLayer, pathLayer) {
this.nx = nx;
this.dx = stage.getWidth() / (nx - 2);
this.ny = ny;
this.dy = stage.getHeight() / ny;
this.grid = new Grid(nx, ny, this.dx, this.dy);
this.nodeLayer = nodeLayer;
this.pathLayer = pathLayer;
this.grid.render(this.nodeLayer, this.pathLayer);
this.A = randomBandedTransMatrix(this.ny, sigma);
this.pi = numeric.random([this.ny]);
this.pi = numeric.div(this.pi, numeric.sum(this.pi));
this.path = genPath(this.pi, this.A, this.nx);
var temp = this.pi;
for(i = 0; i < nx; i++) {
this.grid.setNodeVector(i, numeric.mul(temp, 100));
temp = numeric.dotVM(temp, this.A);
}
};
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 800
});
Model.prototype = {
cleanup: function() {
this.grid.cleanup();
}
};
var nodeLayer = new Kinetic.Layer();
var pathLayer = new Kinetic.Layer();
stage.add(pathLayer);
stage.add(nodeLayer);
function makeAnimation(speed, model, marginals) {
var nx_index = 0;
var anim = new Kinetic.Animation(function(frame) {
var time = frame.time,
timeDiff = frame.timeDiff,
frameRate = frame.frameRate;
var trans = (stage.getX() - (timeDiff) * speed) % model.dx >= stage.getX();
stage.setX((stage.getX()- timeDiff * speed) % model.dx);
if(trans) {
if(marginals) {
var temp = model.pi;
for(i = 0; i < model.nx; i++) {
model.grid.setNodeVector(i, numeric.mul(temp, 100));
temp = numeric.dotVM(temp, model.A);
}
model.pi = numeric.dotVM(model.pi, model.A);
}
nx_index++;
model.grid.clearPaths();
model.path = updatePath(model.path, model.A);
model.grid.enablePathVector(model.path, model.pathLayer);
}
}, stage);
return anim;
}
function setupModel(data) {
data.model.cleanup();
data.anim.stop();
var new_model = new Model(data.xDensity + 1, data.yDensity, data.variance / 5, nodeLayer, pathLayer);
//copy path if possible
if(data.xDensity + 1 == data.model.nx && data.yDensity == data.model.ny) {
new_model.path = data.model.path;
new_model.grid.enablePathVector(new_model.path, new_model.pathLayer);
}
delete data.model;
data.model = new_model;
if(!data.marginals) {
data.model.grid.clearNodes();
}
stage.draw();
data.anim = makeAnimation(data.speed/100, data.model, data.marginals);
if(data.running) {
data.anim.start();
}
}
var GUIData = function() {
this.model = new Model(9,5, 1, nodeLayer, pathLayer);
this.model.grid.clearNodes();
stage.draw();
this.xDensity = 8;
this.yDensity = 5;
this.variance = 5;
this.speed = 10;
this.marginals = false;
this.running = false;
this.anim = makeAnimation(this.speed/100, this.model, this.marginals);
this.start = function() {
this.running = true;
this.anim.start();};
this.stop = function() {
this.running = false;
this.anim.stop();};
};
window.onload = function() {
var data = new GUIData();
var gui = new dat.GUI();
gui.add(data, "start");
gui.add(data, "stop");
var nxcon = gui.add(data, "xDensity").min(3).max(20).step(1);
nxcon.onChange(function(value) {
data.xDensity = value;
setupModel(data);
});
var nycon = gui.add(data, "yDensity").min(2).max(20).step(1);
nycon.onChange(function(value) {
data.yDensity = value;
setupModel(data);
});
var speedcon = gui.add(data, "speed").min(5).max(100).step(5);
speedcon.onChange(function(value) {
data.speed = value;
setupModel(data);
});
var stickycon = gui.add(data, "variance").min(1).max(25).step(1);
stickycon.onChange(function(value) {
data.variance = value;
setupModel(data);
});
var marcon = gui.add(data, "marginals");
marcon.onChange(function(value) {
data.marginals = value;
setupModel(data);
});
data.anim.start();
data.running = true;
gui.close();
};
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |