Real Time Analytics Display in JavaScript

One niche of computing I have always enjoyed is digital art and displays. I found an opportunity to make a new display to track incoming data into an API . Below I have prepared a short write up of how I took intermittent API calls and transformed them into an analytics display that mirrors real time data.

This program is simple enough to be written within a single html page with inline javascript. Not the best form, but it will get the job done with no overhead.

The general structure of the program is as follows:

  • Retrieve the current data total from the analytics API
  • Calculate how much higher this total is from a previous total and then calculate the change per second.
  • Use JavaScript’s setInterval() to update the onscreen counter several times per second to mirror the calculated change per second.

The only complication to this plan is that the setInterval() function does not offer time guarantees. This means that if you set an interval of 10 times per second, Javascript will try to put the function in the stack 10 times per second, but the function may not be executed 10 times per second. This results in the counter becoming wildly off track if left running long enough.

In order to combat this issue I added an additional “speedUp” variable used to adjust the counting velocity both up and down as needed during the program execution. A simpler way may have been to simply recalculate the exact amount of increase 10 times per second, but I wanted a challenge as well as a very smooth looking counter. The velocity adjustment code can be found in the stepClock() method.

In the end we have a very simple and reliable counter that can be run on a Raspberry Pi or any other microcomputer. A potential good use for any old monitors lying around the office.

Code below. Single HTML page with inline script and stylings. I highly recommend using an ENV file for your tokens and api urls.

<!DOCTYPE html>
<html>
<head>
<title>ticker</title>
</head>
<body>
<div class="title">Total Data</div>
<div id="count" class="count">50,000</div>
<div id="velocity" class="velocity">10,000 per Hour</div>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script type="text/javascript">
var currentData = 0;
var oldTarget = 0;
var targetData = 0;
var increment = 0;
var perSec = 10;
var intervalDate = new Date();
var speedUp = 1;
var speedUpBase = 1.1;
//distance between api calls in seconds
var checkDistance = (10 * 60);
//calculating the velocity of data
function setPerHr() {
var change = targetData - oldTarget;
var checkToHour = (60 * 60) / checkDistance ;
var perHr = change * checkToHour;
document.getElementById('velocity').innerHTML = perHr.toLocaleString() + ' per Hour';
}
//parsing the api response
function examineData(data) {
var total = data;
if (currentData == 0) {
currentData = total - 10000;
oldTarget = total - 10000;
} else {
console.log('skipped amount');
console.log(targetData - currentData);
currentData = targetData;
oldTarget = targetData;
}
console.log('new target set at:')
console.log(new Date().toLocaleString());
console.log(total);

intervalDate = new Date();
targetData = total;
increment = (targetData - currentData) / checkDistance;
setPerHr();
}
function updateTotals() {
//token in gitignore file.
return $.ajax({
'url': [API URL HERE],
'contentType': 'application/json; charset=utf-8',
'data': [AUTH TOKEN HERE],
'type': 'POST',
success: examineData
})
}
function stepClock(element) {
var useIncrement = increment;
var time = new Date();
var distanceCovered = (time - intervalDate) / (checkDistance * 1000);
//if ground covered less than what should be covered
var toBeCovered = (targetData - oldTarget) * (1 - distanceCovered);
if ((targetData - currentData) > toBeCovered) {
speedUp += 0.01;
useIncrement *= speedUp;
console.log('faster');
//if counter is too far ahead ~ 1% buffer
} else if ((targetData - currentData) < (toBeCovered * 0.99)) {
if (speedUp > speedUpBase) {
speedUp = speedUpBase;
}
if (speedUp > 0.01) {
speedUp -= 0.01;
}
useIncrement *= speedUp;
console.log('slower');
} else {
if (speedUp > (speedUpBase)) {
speedUp -= 0.01;
} else {
speedUp = speedUpBase;
}
}
element.innerHTML = (Math.round(currentData += (useIncrement / perSec))).toLocaleString();
//check both high and low to avoid a blast of api requests
if (((time - intervalDate) > checkDistance * 1000) && ((time - intervalDate) < (checkDistance * 1000) + 10000)) {
//setting this to try to reduce multiple triggers of this condition
intervalDate = time;
updateTotals();
}
}
function onStart() {
var element = document.getElementById('count');
//checking the api every "checkDistance" num of seconds apart.
updateTotals();
function incrementer() {
stepClock(element);
}

//updating the on screen counter
//updates the counter and checks if api needs to be rechecked.
setInterval(incrementer, (1000 / perSec));
}
onStart();
</script>
<link href='https://fonts.googleapis.com/css?family=Orbitron' rel='stylesheet' type='text/css'>
<style type="text/css">
body {
background-color: black;
}
.count {
color: #00E500;
font-size: 150px;
font-family: 'Orbitron', sans-serif;
border-bottom: solid 5px #00E500;
border-top: solid 5px #00E500;
padding: 25px 30px 0px 50px;
}
.title {
color: #00E500;
font-size: 150px;
font-family: 'Orbitron', sans-serif;
padding: 10px 30px 10px 50px;
}
.velocity {
color: #00E500;
font-size: 40px;
font-family: 'Orbitron', sans-serif;
padding: 15px 30px 0px 50px;
}
</style>
</body>
</html>