Updating Processing.js Graphics via Ajax
Another possible Processing.js technique I have been wanting to experiment with is using Ajax to create dynamic content. This entry will look at a Javascript application, relying on Process.js for graphics, that displays an always up-to-date graph. It'll use php to build a trvial example backend (it will return random data).
Overview
The app displays the current trend of a currency's exchange rate. It updates that exchange rate every ten seconds, and maintains a graph of the past ten data points. Data points are generated randomly by a php script (php was chosen because using anything more complex would be akin to resurecting Albert Einstein to tie your shoelaces).
The example consists of three files:
- The php script to generate random data.
- The Javascript program to retrieve and display data.
- The html page to contain the Javascript program.
The PHP Script: data.php
First lets look at the PHP script. All it does is return a random number, so it is about as simple as it can get.
<?php echo rand(50,250); ?>
In a real application you would probably be querying a database or running a calculation of some kind, but this example isn't really about creating an interesting backend, but about using Ajax to create dynamic graphics by leveraging Processing.js... so the backend is one line of php1.
The JS: exchangechart.js
As I mentioned above, during the original brainstorm of this example I imagined the chart representing the exchange rate between two currencies2, which is why the file and example are named exchangechart. In the end its really just a simple chart, with no connection to currencies.
It is prepopulated with five data points, and the additional data points are asychronously requested from the php script. Up to eleven data points will be represented at once. Once you have eleven data points, when an additional point is retrieved, then the oldest existing point is dropped from the chart. Also, unlike my slightly misguided bubbles example, the graphic is only redrawn when new data is added, so it doesn't assault your processor in the least (runs fine even in Camino, which promptly goes to hell when exposed to Processed Tower Defense).
It uses the Processing.js api, and a simple function to implement crossbrowser Ajax. The asychronous request has a callback that passes the returned data to an instance of the class Chart, which controls drawing the graphic.
The callback looks like this:
http_request.onreadystatechange = function() {
if (http_request.readyState == 4 && http_request.status == 200)
eval("chart.update(http_request.responseText);");
}
There are a hundred things wrong with this piece of code, and it shouldn't be copied verbatim (start cursing at the global variable abuse and go from there). One thing it does do correctly, though, is that it doesn't get upset about failed requests. This is because you'll be requesting data at a constant interval while during the window's lifespan, so it shouldn't be a big deal if some of the data doesn't transmit successfully (if the application is designed thoughtfully).
The update method that is getting called by the callback is the next step in the chain.
this.update = function(val) {
var maxLen = 10;
this.p.data.push(val);
// Retain a maximum of ten pieces of data.
var len = this.p.data.length;
if (len > maxLen) {
this.p.data = this.p.data.slice(len-1-maxLen);
}
this.p.draw();
}
It takes one value, and pushes it onto an array that contains all the data points that are being drawn. If there are more than eleven pieces of data, then the most recent eleven pieces are retained and the rest are discarded. After that, it calls the draw method to update the display graphic. Other than the initial draw, this is the only place that draw is being called (perhaps we could call this "lazy drawing" if we wanted to create a fancy name for an obvious concept).
The last piece of code we'll look at is the draw function, which is slightly long winded, but not too hard to understand once broken apart.
First there is a little setup:
var l = this.data.length;
var h = this.height;
// Find maximum value.
var max = this.data[0];
this.data.forEach(function(x) {
if (x > max) max = x;
});
// Prevent max value from being on
// absolute top of canvas.
max = max * 1.05;
The variables l and h are simply to keep the code short and readable. After that the value of the highest data point is assigned to max3. Then I slightly increase the value of max, because it will be used for scaling relative sizes of values, and I don't want the max value to be at the very top of the canvas.
Next, the code scales all the data points to fit into the available screen real-estate and still have the correct relative sizes.
// Scale vertical position based on maximum value.
var scaled = this.data.map(function(x) {
var ratio = (x * 1.0) / max;
return h - (h * ratio);
});
At first this may not make intuitive sense, but its important to recall that a Y value of 0 corresponds to the top of the canvas, and a Y value of h corresponds to the bottom of the canvas. (I definitely confused myself here for a bit before remembering that.)
Then we draw the red-colored shape.
this.fill(this.color(255,50,50));
this.beginShape();
this.vertex(0,h);
for (var i=0;i<l;i++) {
var x = this.width * 1.0 * (i/(l-1));
var y = scaled[i];
this.vertex(x,y);
}
this.vertex(this.width, h);
this.endShape();
The shape starts at the left bottom, and ends at the right bottom (with a line running across the bototm connecting the two). Between those two points we uniformly spread out the data points in the x dimension.
Next grid lines and ellipses are drawn on top of the red chart shape.
this.fill(this.color(255,0,0));
for (var i=0;i<l;i++) {
var x = this.width * 1.0 * (i/(l-1));
var y = scaled[i];
this.line(x,0,x,h);
this.ellipse(x,y,10,10);
}
The grid lines are drawn first, and then the ellipses are drawn last. The ellipses are drawn at the exact location of the data point.
Thats pretty much all there is to the code. I'll be the first to acknowledge that the drawing occasionally goes slightly hayware, but hopefully this can be overlooked since it isn't related to the Ajax/Processing.js interaction, which was the interesting aspect of the example (its appearance won't be winning any awards anytime soon).
Which I still had to look up in a reference manual, since I have never spent enough time with php.↩
More specifically, Japanese Yen to US Dollar, not that the numbers are particularly representive of that.↩
Since a reduce is really just a forEach operation with a persistent variable, I am creating the lazy man's reduce with the local variable max. ↩