Separation and Clustering of Views: the App

After discussing my trials and tribulations writing the program, here it is, as of the this moment. First, this is how it looks on the web page (I cannot embed the actual live site because WordPress.com does not allow JavaScript without fancy plugins you have to pay WP for the privilege of installing them):

Clustering and Alienation Demo

You can play with the live site here.

And here is the code inside it:

<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

'use strict';

function potential(gain, width, start, end, step) {
  var p = [];
  // interaction potential
  var x;
  var i;
  for ( x=start, i=0; x < end + step; x=x+step, i++) {
    var row = [];
    row[0] = i;
    row[1] = gain/1000*(x*x*(Math.exp(-x*x/width/width/100)+0.01));
    p[i] = row;
  }
  return p;
}

function force(potential, step) {
  var f = [];
  // estimate force=-dU/dx as a difference
  for (var i = 0; i < potential.length-1; i++) {
    var row = [];
    row[0] = i;
    row[1] = (potential[i][1]-potential[i+1][1])/step;
    f[i] = row;
  }
  return f;
}
  

function initialPosition(numPoints) {
  var position = [];
  var row = [];
  row[0] = 0;
  for (var i = 0; i < numPoints; i++) {
    row[i+1] = (i - numPoints/2)*spread*20;
  }
  position[0] = row;
  return position;
}

function simulate(position, force, count)
{
  var pos = [];
  var pointCount = position[0].length-1;
  var iter = position.length;
  for (var i = 0; i < pointCount; i++) {
    pos[i] = position[iter-1][i+1];
  }
  for (var i = 0; i < count; i++) {
    // calculate force from all particles on each one
    for (var j = 0; j < pointCount; j++) {
      var f = 0;
      for (var k = 0; k < pointCount; k++) {
        var delta = pos[k]-pos[j];  // position difference
        var abs = Math.abs(delta);
        var sign = Math.sign(delta);
        var index = Math.floor(abs);
        if (index >= force.length)
          index = force.length - 1;
        f = f + force[index][1]*sign;
        if (isNaN(f)) {
          console.info("i="+i+", j="+j+", k="+k);
          return;
        }
      }
      pos[j] -= f;
      // include a bit of random noise
      pos[j] += Math.random() - 0.4998;
    }
  }
  var newPos = [];
  newPos[0] = position.length;
  for (var j = 0; j < position[0].length-1; j++) {
    newPos[j+1] = pos[j];
  }
  position[iter] = newPos;
}

function keyListener() {
  event.preventDefault();
  // if (event.keyCode === 13) {
    getParams(null);
    u = potential(gain, width, start, end, step);
    f = force(u, step);
    
    chart1 = drawChart(null, u, 'potential_curve', "Interaction potential", "Separation", "Height");
    chart3 = drawChart(null, f, 'force_curve', "Interaction force", "Separation", "Strength");
  // }
}

function getParams(listener) {
  var element = document.getElementById('gain');
  gain = 1;
  if (element != null) {
    gain = Number(element.value);
  }
  if (listener != null) {
    element.addEventListener("keyup", listener);
  }
  
  element = document.getElementById('width');
  width = 1;
  if (element != null) {
    width = Number(element.value);
  }
  if (listener != null) {
    element.addEventListener("keyup", listener);
  }
  
  element = document.getElementById('spread');
  spread = 1;
  if (element != null) {
    spread = Number(element.value);
  }
  if (listener != null) {
    element.addEventListener("keyup", listener);
  }
}

function updateProgress(step, steps) {
  var progress = document.getElementById('progress');
  if (progress != null)
    progress.innerHTML = step+"/"+steps;
}


function drawChart(chartData, rows, id, title, xAxis, yAxis) {
  
  var data;
  var chart;

  if (chartData == null) {
    chart = new google.visualization.LineChart(document.getElementById(id));
    data = new google.visualization.DataTable();
    chartData = {chart: chart, data: data};
    for (var i = 0; i < rows[0].length; i++) {
      data.addColumn('number', i);
    }
    data.addRows(rows);
  } else {
    chart = chartData.chart;
    data = chartData.data;
    data.addRow(rows[rows.length-1]);
  }

  var options = {
    title: title,
    curveType: 'function',
    legend: { position: 'none' },
    vAxes: {
      // Adds titles to each axis.
      0: {title: yAxis}
    }
  };

  chart.draw(data, options);
  
  return chartData;
}

var gain;
var width;
var spread;
var numSteps = 10000;
var chart1;
var chart2;
var chart3;
var stepsPerInteration = 100;
var iteration;
var results;
var u;
var f;
var step = 0.1;
var start = 0;
var end = 100;
var listenerAdded = false;
var numPoints = 50;
var stepsSoFar;
var stopSimulation;
var position;

google.charts.load('current', {'packages':['corechart', "line"]});

google.charts.setOnLoadCallback(update);

function restartSimulation() {
  simulate(position, f, stepsPerInteration);
  iteration++;
  
  // document.getElementById('simulation_chart').style.width = 10*iteration;
  chart2 = drawChart(chart2, position, 'simulation_chart', "Separation and Clustering", "Iteration","Separation");

  if (stepsSoFar < numSteps && !stopSimulation) {
    stepsSoFar += stepsPerInteration;
    setTimeout(restartSimulation, 1);
  } else {
    stop();
  }
}

function stop() {
  stopSimulation = true;
  stepsSoFar = 0;
  var element = document.getElementById('simulate');
  element.innerHTML = "Run";
  chart2 = null;
}

function startSimulation() {
  stopSimulation = false;
  results = [[]];
  if (!listenerAdded) {
    listenerAdded = true;
    getParams(keyListener);
  }
  u = potential(gain, width, start, end, step);
  f = force(u, step);
  
  position = initialPosition(numPoints);
  stepsSoFar = 0;
  
  chart1 = drawChart(null, u, 'potential_curve', "Interaction potential", "Separation", "Height");
  chart3 = drawChart(null, f, 'force_curve', "Interaction force", "Separation", "Strength");
  chart2 = null;

  iteration = 0;
  restartSimulation();
}

function update() {
  if (stepsSoFar > 0 && stepsSoFar <= numSteps) {
    stop();
  } else {
    startSimulation();
    var element = document.getElementById('simulate');
    element.innerHTML = "Stop";
  }
}


  </script>
  </head>
  <body>
    <button type="button" id="simulate" onclick="update()">Run</button>
    <span>Gain</span>
    <input id="gain" value="1" style="width:50px;">
    <span>Width</span>
    <input id="width" value="1" style="width:50px;">
    <span>Spread</span>
    <input id="spread" value="1" style="width:50px;">
    <span id="progress"></span>
    <table>
      <tr>
    <td id="potential_curve" style="width: 500px; height: 400px"></td>
    <td id="force_curve" style="width: 500px; height: 400px"></td>
    </tr></table>
    <div id="simulation_chart" style="width: 1000px; height: 400px"></div>
  </body>
</html>

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s