javascript - Simulate a discrete markov chain with given transition matrix using d3.js -


i want simulate discrete markov chain given transition matrix input , histogram showing frequencies of visiting states. here : markov chain simulation

i inspired work :

http://setosa.io/markov/index.html#%7b%22tm%22%3a%5b%5b0.5%2c0.5%5d%2c%5b0.5%2c0.5%5d%5d%7d

here html/javascript code:

<!doctype html>  <html>    <head>      <title>markov chains</title>      <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">           <link href="style.css" type="text/css" rel="stylesheet">           <script src="vector.js"></script>      <script src="d3.min.js"></script>      <script src="angular.min.js" charset="utf-8"></script>      <style>  body {    background-color: #222;    color: white;  }  .controls {    position: absolute;    top: 100px;    right: 10px;  }  .st-diagram {    pointer-events: none;    position: absolute;    left: 0;    width: 100%;    z-index: 1;  }  .st-diagram .nodes {    pointer-events: all;  }  .matrixinput {    display: block;    height: 100%;    width: 40%;    top: 50px;    right: 50px;    position: absolute;      }  .matrixinput textarea{    border: none;    background-color: transparent;    color: red;    width: 100%;    height: 100%;    font-size: 20px;    outline: none;  }  .matrixinput textarea.valid {    color: white;  }  .matrix table {    width: 100%;    height: 100%;    text-align: center;    table-layout: fixed;  }  .matrix table td {    width: 33.33%;  }  .matrix table td input {    pointer-events: all;    width: 80%;  }  </style>  </head>  <body>  <div class="title-area">      <h1 <center> markov chains </center> </h2>         <title rows="4" cols="50">  </title>       <body ng-app="myapp" ng-controller="mainctrl">    <st-diagram center="diagramcenter" states="states"      transition-matrix="transitionmatrix" duration="duration"       state="state"      selected-transition="selected.transition"></st-diagram>        <div class="sequence" step-1="70%" step-2="60%"></div>    <div class="matrixinput">      <p> matrice de transition </p>      <textarea ng-class="{ 'valid' : validtransitionmatrix }"        ng-model="transitionmatrixjson">{{transitionmatrix | json}}</textarea>    </div>       <div class="controls">      <input class="speedrange" type="range" ng-model="speedrange"        min="1" max="10" step="1">      <label> vitesse </label>    </div>        <script>    var myapp = angular.module('myapp', []);    myapp.controller('mainctrl', function($scope, utils, $window) {    angular.element($window).on('resize', function() { $scope.$apply(); });    $scope.diagramcenter = [0.8, 0.5];        $scope.isselectedtransition = function(i, j) {      return !!$scope.selectedtransition;      if (!$scope.selectedtransition) return false;      return $scope.selectedtransition[0] ===        && $scope.selectedtransition[1] === j;    };    $scope.speedrange = 2;    $scope.$watch('speedrange', function(speed) {      $scope.duration = 2000 / +speed;    });    $scope.updatetransitionmatrix = function(matrix) {      var prev = $scope.transitionmatrix;      $scope.transitionmatrix = matrix;      if($scope.states && matrix.length === prev.length) return;      $scope.states = matrix.map(function(d, i) {        return { label: string.fromcharcode(65 + i), index: };      });      utils.sethash({ tm: matrix });    };    var hash = utils.gethash();    if(hash && hash.tm) $scope.updatetransitionmatrix(hash.tm);      else $scope.updatetransitionmatrix([[0.3,0.3,0.4],  [0.3,0.5,0.2],[0.4,0.4,0.2]]);      $scope.transitionmatrixjson = json.stringify($scope.transitionmatrix)      .replace(',[', ',\n[');    $scope.$watch('transitionmatrixjson', function(str) {      var valid = false;      try{         var matrix = json.parse(str);        valid = matrix[0].length === matrix.length;        var sum = matrix.reduce(function(c, row) {          return c + row.reduce(function(p, c){ return p + c; }, 0);        }, 0);        var r = sum / matrix.length;        valid = valid && r < (1 + 1e-9) && r > (1 - 1e-9);        if (valid) {          $scope.updatetransitionmatrix(matrix);        }      }catch(e) {}    $scope.validtransitionmatrix = valid;    });    $scope.state = { current: null };    $scope.selected = { transition: null };   <!-- var labels = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];    var labels = ['a','b','c'];    var color = d3.scale.category10();    $scope.states = labels.map(function(label, i) {      return {text: label, label: label, index: i, color: color(label) };          });                   });  </script>  <script src="common.js" charset="utf-8"></script>  </html>

and common.js

myapp.factory('utils', function() {   var utils = {};   utils.gethash = function() {     var hash = window.location.hash;     if(!hash) return;     hash = hash.slice(1); // remove '#'     try { return json.parse(decodeuricomponent(hash)); }catch(e) {};   };   utils.sethash = function(obj) {     window.location.hash = encodeuricomponent(json.stringify(obj));   };   utils.sample = function(probs) {     var t = 0;     var r = math.random();     for(var = 0; < probs.length; i++) {       t = t + probs[i];       if (r <= t) return i;     }     throw new error('invalid distribution');   };   utils.normalizetransitionmatrix = function(matrix, idx1, idx2) {     // next states state transition to.     var states = matrix[idx1];      // convert numbers.     states.foreach(function(d, i){ states[i] = +d; });      // need re-normalize transitions each state     // add one.      // `val` - selected next state value.     var val = states[idx2];      if(val === 1) return states.foreach(function(d, i) {       if(i === idx2) return;       states[i] = 0;     });      // `r` - remaining state probability.     var r = states.reduce(function(total, state, i){       return total + (i === idx2 ? 0 : state);     }, 0);      if(r === 0) r = states.length - 1, states.foreach(function(d, i) {       if(i === idx2) return;       states[i] = 1;     });      // normalize remaining states , multiply remaining     // probability, `( 1 - val)`.     states.foreach(function(d, i) {       if(i === idx2) return;       states[i] = states[i] / r * (1 - val);     });   };   return utils; });    myapp.directive('stdiagram', function($compile) {   function link(scope, el, attr) {     el = d3.select(el[0]);     calcresize();     var svg = el.select('svg');     var aligng = svg.append('g');     var centerg = aligng.append('g');     var color = d3.scale.category10();     var links = centerg.append('g').attr('class', 'links').selectall('paths');     var nodes = centerg.append('g').attr('class', 'nodes').selectall('g');     var markers = svg.append('defs').selectall('.linkmarker');     var currentstateg = centerg.append('g').attr('class', 'currentstate')       .attr('transform', 'translate(' + [w / 2, h / 2] + ')')       .style('opacity', 0);     var w, h, r = 20;     var linkelements = {};     var force = d3.layout.force()       .linkdistance(function(d){ return w / 16 + (1 - d.value) * 200 * w / 1200 })       .charge(-4000);      currentstateg       .append('circle')       .attr('r', 10);      function calcresize() {       return w = el.node().clientwidth, h = el.node().clientheight, w + h;     }     scope.$watch(calcresize, resize);     scope.$watch('center', resize, true);     scope.$watch('states', update, true);     scope.$watch('transitionmatrix', update, true);     scope.$watch('selectedtransition', update);      function resize() {       force.size([w, h]);       svg.attr({width: w, height: h});       var center = scope.center;       var cx = (center && angular.isdefined(center[0])) ? center[0] : 0.5;       var cy = (center && angular.isdefined(center[1])) ? center[1] : 0.5;       aligng.attr('transform', 'translate(' + [ w * cx, h * cy ] + ')');       centerg.attr('transform', 'translate(' + [ - w / 2, - h / 2] + ')');     }      function update() {       var linksdata = [];       var enter;       scope.transitionmatrix.foreach(function(transitions, idx1) {         // idx1 - index of state         // transitions - array of next state probabilities         // each index in array coorisponds state in `scope.states`.         transitions.foreach(function(prob, idx2) {           if(prob === 0) return;           linksdata.push({             source: scope.states[idx1],             target: scope.states[idx2],             value: +prob           });         });       });       nodes = nodes.data(scope.states);       enter = nodes.enter().append('g')         .attr('class', 'node')         .style('fill', function(d){ return color(d.index); })         .call(force.drag);       enter.append('circle')         .attr('r', r);       enter.append('text')         .attr('transform', 'translate(' + [0, 5] + ')')       nodes.exit().remove();        var linkkey = function(d) {         return (d.source && d.source.index) + ':'           + (d.target && d.target.index);       };       links = links.data(linksdata, linkkey)       links.enter().append('path')         .attr('marker-end', function(d) {           if(!d.source || !d.target) debugger;           return 'url(#linkmarker-' + d.source.index + '-' + d.target.index + ')';         }).classed('link', true)         .style('stroke', function(d){ return color(d.source.index); })       links.exit().remove();       links.each(function(d, i) {         linkelements[d.source.index + ':' +d.target.index] = this;         var active = false, inactive = false;         if (scope.selectedtransition) {           active = scope.selectedtransition[0] === d.source.index             && scope.selectedtransition[1] === d.target.index;           inactive = !active;         }         d3.select(this)           .classed('active', active)           .classed('inactive', inactive);       });        markers = markers.data(linksdata, linkkey);       markers.enter().append('marker')         .attr('class', 'linkmarker')         .attr('id', function(d) {           return 'linkmarker-' + d.source.index + '-' + d.target.index })         .attr('orient', 'auto')         .attr({markerwidth: 2, markerheight: 4})         .attr({refx: 0, refy: 2})         .append('path')           .attr('d', 'm0,0 v4 l2,2 z')           .style('fill', function(d){ return color(d.source.index); });       markers.exit().remove();        force.nodes(scope.states)         .links(linksdata)         .start();     }      force.on('tick', function() {       var _r = r;       links         .style('stroke-width', function(d) {           return math.sqrt(100 * d.value || 2); })         .attr('d', function(d) {           var r = _r;           var p1 = vector(d.source.x, d.source.y);           var p2 = vector(d.target.x, d.target.y);           var dir = p2.sub(p1);           var u = dir.unit();           if(d.source !== d.target) {             r *= 2;             var right = dir.rot(math.pi /2).unit().scale(50);             var m = p1.add(u.scale(dir.len() / 2)).add(right);             u = p2.sub(m);             l = u.len();             u = u.unit();             p2 = m.add(u.scale(l - r));             u = p1.sub(m);             l = u.len();             u = u.unit();             p1 = m.add(u.scale(l - r));             return 'm' + p1.array() + 's' + m.array() + ' ' + p2.array();           }else{             var s = 50, rot = math.pi / 8;             r = r * 1.5;             p1 = p1.add(vector(1, -1).unit().scale(r - 10))             p2 = p2.add(vector(1, 1).unit().scale(r))             var c1 = p1.add(vector(1, 0).rot(-rot).unit().scale(s));             var c2 = p2.add(vector(1, 0).rot(rot).unit().scale(s - 10));             return 'm' + p1.array() + ' c' + c1.array() + ' '               + c2.array() + ' ' + p2.array();           }         });       nodes.attr('transform', function(d) {         return 'translate(' + [d.x, d.y] + ')';       }).select('text').text(function(d){ return d.label; })     });      var currentstate = 0;     function loop() {       var = currentstate;       var nextstates = scope.transitionmatrix[i];       var nextstate = -1;       var rand = math.random();       var total = 0;       for(var j = 0; j < nextstates.length; j++) {         total += nextstates[j];         if(rand < total) {           nextstate = j;           break;         }       }       var cur = scope.states[currentstate];       var next = scope.states[nextstate];       var path = linkelements[cur.index + ':' + next.index];       scope.$apply(function() {         scope.$emit('statechange', next);       });       currentstateg         .transition().duration(+scope.duration * 0.25)         .style('opacity', 1)         .ease('cubic-in')         .attrtween('transform', function() {           var m = d3.transform(d3.select(this).attr('transform'));           var start = vector.apply(null, m.translate);           var scale = m.scale;           var s = d3.interpolatearray(scale, [1, 1]);           return function(t) {             var end = path.getpointatlength(0);             end = vector(end.x, end.y);             var p = start.add(end.sub(start).scale(t));             return 'translate(' + p.array() + ') scale(' + s(t) + ')';           };         })         .transition().duration(+scope.duration * 0.5)         .ease('linear')         .attrtween('transform', function() {           var l = path.gettotallength();           return function(t) {             var p = path.getpointatlength(t * l);             return 'translate(' + [p.x, p.y] + ') scale(1)';           };         })         .transition().duration(+scope.duration * 0.25)         .ease('bounce-in')         .attrtween('transform', function() {           var m = d3.transform(d3.select(this).attr('transform'));           var translation = vector.apply(null, m.translate);           var scale = m.scale;           var s = d3.interpolatearray(scale, [2, 2]);           return function(t) {             var end = vector(next.x, next.y);             var p = translation.add(end.sub(translation).scale(t));             return 'translate(' + p.array() + ') scale(' + s(t) + ')';           };         })         .each('end', function() {           loop();         })       currentstate = nextstate;     }     settimeout(loop, +scope.duration);   }   return {     link: link,     restrict: 'e',     replace: true,     scope: {       states: '=',       center: '=',       transitionmatrix: '=',       duration: '=',       selectedtransition: '=',       state: '=?'     },     template: ''       + '<div class="st-diagram">'         + '<svg>' + '</svg>'       + '</div>'   }; }); myapp.directive('cell', function() {   function link(scope, el, attr) {     scope.$parent.numcells++;     scope.$on('destroy', function() {       scope.$parent.numcells--;     });     scope.$parent.$watch('width + height', function(){       scope.width = scope.$parent.width / scope.$parent.numcells;       scope.height = scope.$parent.height;     });   }   return {     link: link,     restrict: 'e',     replace: true,     transclude: true,     template: '<div class="cell"'       + "ng-style=\"{width: width + 'px', height: height + 'px'}\""       + 'ng-transclude>' + '</div>'   }; }); myapp.directive('row', function() {   function prelink(scope, el, attr) {     scope.numcells = 0;   }   function postlink(scope, el, attr) {     scope.$parent.numrows++;     scope.$on('destroy', function() {       scope.$parent.numrows--;     });     scope.$parent.$watch('width + height', function(){       scope.width = scope.$parent.width;       scope.height = scope.$parent.height / scope.$parent.numrows;     });   }   return {     link: { pre: prelink, post: postlink },     restrict: 'e',     replace: true,     transclude: true,     template: '<div class="row"'       + "ng-style=\"{width: width + 'px', height: height + 'px'}\""       + 'ng-transclude>' + '</div>'   }; }); myapp.directive('grid', function() {   function prelink(scope, el, attr, controllers) {     scope.numrows = 0;   }   function postlink(scope, el, attr, controllers) {     var sel = d3.select(el[0]);     var w, h;     scope.name = 'victor';     scope.numberofrows = 0;     scope.$watch(function() {       return w = sel.node().clientwidth, h = sel.node().clientheight, w + h;     }, function() {       scope.width = w, scope.height = h;     });   }   return {     link: { pre: prelink, post: postlink },     restrict: 'e',     transclude: true,     replace: true,     template: ''       + '<div style="overflow:hidden" class="grid">'       + '<div ng-transclude></div>'       + '</div>'   }; });     myapp.directive('sequence', function() {   function link(scope, el, attr) {     var sel = d3.select(el[0]);     var w, h;     scope.$watch(function() {       return w = sel.node().clientwidth, h = sel.node().clientheight, w + h;     }, function() {       scope.width = w, scope.height = h;       sel.style('line-height', h + 'px');     });     scope.$on('statechange', function(e, state) {       sel.append('span').text(state.text).style('position', 'absolute')         .style('color', state.color)         .style('opacity', 0)         .transition()         .duration(2000)         .ease('cubic-in')         .style('opacity', 1)         .styletween('left', function() { return d3.interpolate(attr.step1 || '90%', attr.step2 || '80%');})         .transition()         .duration(15000)         .ease('linear')         .styletween('left', function() { return d3.interpolate(attr.step2 || '80%', '10%'); })         .remove();     });   }   return {     link: link, restrict: 'c'   }; }); 


Comments