<div id="container"></div>
html, body
{
    height: 100%;
    min-height: 100%;
    margin: 0;
    padding: 0;
}

#container {
  width: 100%;
  height: 100%;
}
function nextDouble(maxValue, minValue) {
    var max = maxValue || 1,
        min = minValue || 0,
        randomValue = Math.random();
    if (!min) {
        return randomValue * max;
    }
    return (0.5 - randomValue) * (max - min);
}

function uniformDistribution(maxValue) {
    this.maxValue = maxValue;
}

uniformDistribution.prototype.generate = function () {
    return nextDouble(this.maxValue);
}

function featureParticle(state, weight, variance) {
    var defaultVariance = 10;

    this.variance = variance || defaultVariance;
    this.weight = weight || 0;
    this.state = state.slice();
};

featureParticle.prototype.diffuse = function () {
    var state = this.state,
        variance = this.variance;

    for (var i = 0, l = state.length; i < l; i++) {
        state[i] += nextDouble(variance, -variance);
    }
};

featureParticle.prototype.clone = function () {
    return new featureParticle(this.state.slice(), this.weight, this.variance);
};

function particleFilter(particlesNumber, variablesNumber, distributionSize, particleVariance) {

    var particlesNumber = particlesNumber || 100,
        variablesNumber = variablesNumber || 1,
        distributionSize = distributionSize || 10;

    this.particles = [];
    this.epsilon = Number.MIN_VALUE;
    this.variance = 0;
    this.distributions = [];
    this.particleVariance = particleVariance;

    for (var i = 0; i < variablesNumber; i++) {
        this.distributions.push(new uniformDistribution(distributionSize));
    }

    this.generateParticles(particlesNumber, this.distributions);
};

particleFilter.prototype.generateParticles = function (numberOfParticles, distributions) {
    var nDim = distributions.length,
        particles = this.particles;
    for (var i = 0; i < numberOfParticles; i++) {
        var randomParam = [];
        for (var dim = 0; dim < nDim; dim++) {
            randomParam[dim] = distributions[dim].generate();
        }
        particles.push(new featureParticle(randomParam, 1 / numberOfParticles, this.particleVariance));
    }
};

particleFilter.prototype.resample = function (sampleCount) {
    var resampledParticles = [],
        filteredParticles = this.filter(this.particles.length);

    for (var i = 0, l = filteredParticles.length; i < l; i++) {
        var newPart = filteredParticles[i].clone();
        newPart.weight = 1 / this.particles.length;
        resampledParticles.push(newPart);
    }

    return resampledParticles;
};

particleFilter.prototype.filter = function (sampleCount) {
    var cumulativeWeights = [],
        cumSumInd = 0,
        cumSum = 0,
        particles = this.particles;

    for (var i = 0, l = particles.length; i < l; i++) {
        var p = particles[i];
        cumSum += p.weight;
        cumulativeWeights[cumSumInd++] = cumSum;
    }

    var maxCumWeight = cumulativeWeights[particles.length - 1],
        minCumWeight = cumulativeWeights[0];

    var filteredParticles = [];

    for (var i = 0; i < sampleCount; i++) {
        var randWeight = minCumWeight + nextDouble(1) * (maxCumWeight - minCumWeight),
            particleInd = 0;
        while (cumulativeWeights[particleInd] < randWeight) {
            particleInd++;
        }

        var p = particles[particleInd];
        filteredParticles.push(p);
    }

    return filteredParticles;
};

particleFilter.prototype.predict = function (effectiveMinRatio) {
    var newParticles,
        particles = this.particles,
        effectiveRatio = this.effectiveLength(this.normalWeights()) / this.particles.length;
    if (effectiveRatio > this.epsilon &&
        effectiveRatio < effectiveMinRatio) {
        newParticles = this.resample(particles.length);
    }
    else {
        newParticles = [];
        for (var i = 0, l = particles.length; i < l; i++) {
            var cloned = particles[i].clone();
            cloned.diffuse();
            newParticles.push(cloned);
        }
    }

    this.particles = newParticles;
};

particleFilter.prototype.effectiveLength = function (weights) {
    var sumSqr = this.epsilon,
        sum = 0;
    for (var i = 0, l = weights.length; i < l; i++) {
        var w = weights[i];
        sumSqr += w * w;
        sum += w;
    }

    return sum / sumSqr;
};

particleFilter.prototype.normalWeights = function () {
    var particles = this.particles,
        normalizedWeights = [],
        weightSum = this.epsilon;
    for (var i = 0, l = particles.length; i < l; i++) {
        weightSum += particles[i].weight;
    }

    for (var i = 0, l = particles.length; i < l; i++) {
        normalizedWeights.push(particles[i].weight / weightSum);
    }

    return normalizedWeights;
};

particleFilter.prototype.update = function (measure, effectiveMinRatio) {

    this.predict(effectiveMinRatio || 0.9);

    var particles = this.particles,
        maxWeightParticle;

    for (var i = 0, l = particles.length; i < l; i++) {
        var particle = particles[i],
            state = particle.state,
            sumSqr = 0;

        for (var j = 0, sl = state.length; j < sl; j++) {
            var diff = measure[j] - state[j];
            sumSqr += diff * diff;
        }

        var weight = 1 / Math.sqrt(sumSqr);
        particle.weight = weight;

        if (!maxWeightParticle || maxWeightParticle.weight < weight) {
            maxWeightParticle = particle;
        }
    }

    this.result = maxWeightParticle;
    return maxWeightParticle;
};

var particlesNumber = 200;
var filter = new particleFilter(particlesNumber, 2, 5);
var container = document.getElementById('container');
var x = -1;
var y = -1;
var predictionFactor = 10;

function setSize(el, size){
    el.style.width = size + 'px';
    el.style.height = size + 'px';
    el.style.borderRadius = size + 'px';
}

var particlesElements = [];
for(var i = 0; i < particlesNumber; i++){
  var el = document.createElement('div');
  el.style.position = 'absolute';
  el.style.backgroundColor = 'black';
  container.appendChild(el);
  particlesElements.push(el);
}

function drawPredictions(){
  var filterParticles = filter.particles;
  for(var i = 0; i < particlesNumber; i++){
    var el = particlesElements[i];
    var coord = filterParticles[i].state;
    el.style.left = (x + predictionFactor*coord[0]) + 'px';
    el.style.top = (y + predictionFactor*coord[1]) + 'px';
    setSize(el, Math.min(1, filterParticles[i].weight) * 10)
  }
}

container.onmousemove = function(event){
  if(x > -1){
    filter.update([
      event.clientX - x, 
      event.clientY - y
    ]);
    drawPredictions();
  }
  x = event.clientX;
  y = event.clientY;
};

Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.