Search code examples
javascriptjsonpubnub

Rendering PubNub / Eon Chart LiveStream using Multiple JSON Data Objects per Every PubNub Message


PubNub's Eon Charts looks for one JSON Object data set per each individual PubNub message. See Eon-Chart pubnub-c3.js on git hub.

To get a sense of what sort of data might come in you can see some of PubNub's examples. The PubNub Sensor Network example shows expected data stream format, with one Object per message: https://www.pubnub.com/developers/realtime-data-streams/sensor-network/

The example incomming single message JSON looks like this (see above URL for live data):

{"timestamp":1466007459,"radiation_level":"202","humidity":"79.3726","ambient_temperature":"19.48","sensor_uuid":"probe-180ea910","photosensor":"857.83"}

This works great when you are publishing data every 100ms or so, but when you are dealing with a faster sample rate (sampling every 10 to 20ms) it is sometimes more advantageous to form an array of objects and then send many data objects within one single PubNub message.

My question centers around the ability to send an Array of multiple JSON objects, inside a single PubNub message, to be parsed and then rendered by eon-charts, each JSON Array Object containing it's own data key / value pair and their own time stamp.

For those who haven't used the PubNub Eon-Chart library before, here is the example client side code that PubNub suggests to consume the above mentioned live data stream:

<html>
  <head>

    <script type="text/javascript" src="http://pubnub.github.io/eon/v/eon/0.0.11/eon.js"></script>
    <link type="text/css" rel="stylesheet" href="http://pubnub.github.io/eon/v/eon/0.0.11/eon.css" />

  </head>
  <body>
    <div id="chart"></div>
    <script>
    // using the example stream from 
    // http://www.pubnub.com/developers/data-streams/sensor-network
    var pubnub = PUBNUB.init({
        subscribe_key: 'sub-c-5f1b7c8e-fbee-11e3-aa40-02ee2ddab7fe',
        ssl: true
    });
    eon.chart({
        pubnub: pubnub,
        history: false,
        channel: 'pubnub-sensor-network',
        limit: 100,
        rate: 10,
        ssl: true,
        debug: true,
        generate: {
            transition: {
                duration: 3
            },
            bindto: '#chart',
            data: {
                x: 'x'
            },
            axis: {
                x: {
                    type: 'timeseries',
                    tick: {
                        format: '%H:%m:%S'
                    }
                }
            }
        },
        transform: function(m) {
            return {
                eon: {
                    'Humidy': m.humidity,
                    'Temperature': m.ambient_temperature,
                    'Light': m.photosensor
                }
            }
        }
    });
    </script>
  </body>
</html>

In the above example calling eon.chart (with the listed options) subscribes to the channel listed ('pubnub-sensor-network') using the supplied subscribe key. Upon receiving a new message on that pubnub subscription, it then transforms the data using the transform function:

transform: function(m) {
            return {
                eon: {
                    'Humidy': m.humidity,
                    'Temperature': m.ambient_temperature,
                    'Light': m.photosensor
                }
            }
        }

This transforms any JSON that might not be what Eon-chart is expecting into a format that it can understand and is called before the data is processed / used to update the chart.

My feeling / guess is that the iteration code that I would like to use to loop through each JSON object (of which multiple will be contained within every PubNub message) should probably be added within the transform function which is called every time a new PubNub message is received.

The eon-chart primary functionality included within pubnub-c3.js also includes the option to retrieve an array of historically logged pubnub messages (assuming you have history enabled for your pubnub keys / channel).

The reason this is significant is because enabling the history option triggers functionality that results in PubNub sending an array of objects, and then the eon-chart client iterates through them populating the chart as it goes.

This is almost exactly what we are trying to do. The only difference being that we are trying to iterate through results of each PubNub message, as opposed to iterating exclusively through the historically logged messages and then (when complete) swapping to rendering data from new incoming messages.

The goal is to combine the two concepts (transform function and history iteration concept).

The meat of code related to the history functionality (inside of pubnub_c3.js) looks like this:

self.pubnub.history({
          count: options.limit,
          channel: options.channel,
          end: timetoken,
          include_token: true,
          callback: function(payload) {

            var msgs = payload[0]; //data is here
            var start = payload[1]; 
            var end = payload[2];

            clog('History:', msgs.length + ' messages found');

            clog('History:', 'Complete... Rendering');

            i = 0;
            while (i < msgs.length) {

              var a = msgs[i];
              a.message = options.transform(a.message);

              if(a.message && a.message.eon) {
                a = appendDate(a.message.eon, a.timetoken);
                storeData(a, true); 
              } else {
                clog('Rejecting history message as improper format supplied.');
              }

              i++;

            }

            if (msgs.length > 1 && object.json.length < options.limit - 1) {
              page(end);
            } else {
              loadData(object);
              done();
            }

So. With all of that background (sorry!) I see this question as two problems:

  1. Correct formatting of outgoing PubNub JSON data.
  2. Correct placement of parsing / iteration code within the current pubnub_c3.js functions.

For the purposes of answering this question I have created an instance on hyperdev.space which you can access over here (has all of my code built out): https://hyperdev.com/#!/project/coal-weaver

My transform code currently looks like this:

transform : function(data) { //I believe any code to itterate through data will happen here
      jsonTimeStampTest.innerText = data[0].vibeTimeStamp; //we properly get the time stamp
      jsonTimeStampTest.innerText = data[0].vibeValue; //we properly get the reading: 12
      jsonLength.innerText = data.length;  //we properly get 10

        var z = 0;
        for(z=0; z<10; z++)
          {
          return { columns : [
              ['x', data[z].vibeTimeStamp,
              'Value', data[z].vibeValue]
            ]};
          }
    }

... But I end up with errors in the console: eon.js:8015 Eon messages must be in proper format. For example: Object {eon: Array[3]} eon:Array[3]

Tried many different varieties and can't seem to get the transform code to properly add values to eon-chart. Here's hoping you guys can help.


Solution

  • Multiple Points Per Payload

    It is possible to publish multiple plot points per payload. Rather than using the object name eon use the name eons and supply an Array. Because you use the eons property name, the library will know to loop through the array and plot each point.

    Note that if publishing multiple points per payload, you must use x_type: "custom" and supply an x_id.

    eons: [
      {
        x: new Date().getTime(),
        value: 1
      },
      {
        x: new Date().getTime(),
        value: 2
      }
    ] 
    

    Here's an example of data collected at 100ms increments, but only publishes every 1,000ms. Every payload includes 10 points with 100ms resolution. See a full example here.

    setInterval(function(){
    
      var data = [];
      var date = new Date().getTime();
    
      for(var i = 0; i < 10; i++) {
          data.push({
              'pub_time': date + (100 * i),
              'Austin': Math.floor(Math.random() * 99)
          });
      }
    
      pubnub.publish({
        channel: channel,
        message: {
          eons: data
        }
      });
    
    }, 1000);