Graphing

Posted on
  • Hi,

    I started work on a library to make graphing a bit easier. The code below runs on a Pixl.js, or any other device as long as you create a Graphics instance called g.

    Currently it does:

    • Line graphs
    • Auto-scaling for Y, with a 'grid marks'
    • Y axis labels

    What kind of functionality is needed to you think? Bearing in mind the display is quite low-res...

    Thoughts so far:

    • Title
    • Bar graph
    • another module to handle keeping track of a 'history' array

    var history = new Float32Array(64);
    
    /*
    
    Graph the given array of data.
    
    options = {
      miny // optional - minimum y value
      maxy // optional - maximum y value
      gridy // optional - grid value for y. Also enables labels
      x // optional - pixel x offset on screen
      y // optional - pixel y offset on screen
      width // optional - pixel width on screen
      height // optional - pixel height on screen
    };
    
    */
    function graph(g, data, options) {
      options = options||{};
      var miny = (options.miny!==undefined) ? options.miny :
        options.miny=data.reduce((a,b)=>Math.min­(a,b), data[0]);
      var maxy = (options.maxy!==undefined) ? options.maxy :
        options.maxy=data.reduce((a,b)=>Math.max­(a,b), data[0]);
      if (options.gridy) {
        var gy = options.gridy;
        miny = gy*Math.floor(miny/gy);
        maxy = gy*Math.ceil(maxy/gy);
      }
      var ox = options.x||0;
      var oy = options.y||0;
      var ow = options.width||g.getWidth();
      var oh = options.height||g.getHeight();
      // draw axes
      if (options.axes) {
        var o = 6; // size of axes
        ox += o;
        ow -= o;
        oh -= o;
        g.drawLine(ox,oy,ox,oy+oh/*+o*/);
        g.drawLine(ox/*-o*/,oy+oh,ox+ow,oy+oh);
      }  
      var dy = maxy-miny;
      function getx(x) { return ox+ow*x/data.length; }
      function gety(y) { return oy+oh*(maxy-y)/dy; }
      // Draw grid pips and labels
      if (options.gridy) {
        g.setFontAlign(0,0,1); // rotate 90
        for (var i=miny;i<=maxy;i+=options.gridy) {
          var y = gety(i);
          var t = i;
          g.drawLine(ox-1, y, ox+1, y);
          if (y>g.stringWidth(t)/2) // does it fit?
            g.drawString(t, ox-5, y+2);
        }    
      }
      // Draw actual data
      g.moveTo(getx(0), gety(data[0]));
      for (var i=1;i<data.length;i++) {
        g.lineTo(getx(i), gety(data[i]));
      }
      // back to defaults
      g.setFontAlign(0,0,0); 
    }
    
    function onTimer() {
      // quickly move all elements of history back one
      history.set(new Float32Array(history.buffer,4));
      // add new history element at the end
      var temp = E.getTemperature();
      history[history.length-1] = temp;
      // 
      g.clear();
      graph(g, history, {
        miny: 0, 
        axes : true,
        gridy : 10
      });
      // Update the screen
      g.flip();
    }
    
    // Update temperature every 2 seconds
    setInterval(onTimer,2000);
    // Update temperature immediately
    onTimer();
    

    1 Attachment

    • graph.png
    1. Cursors? (for a given X value / for a MAX or MIN value , ...)
    2. Automatic X scrolling (when defining a window width)?
    3. Gauge?

    Nice work, by the way !

  • What about grid with dots in the graph section


    1 Attachment

    • grid with dots.JPG
  • min/max/ave
    -for graph on display
    -for total measurement (history module)

  • Thanks for the suggestions!

    Having the ability to mark a point and display its value would be great, and as you say @Jean-Philippe_Rey being able to move it around to find a value would be really handy.

    Gauges would be good too - but probably best for another module?

    I'm not convinced that a grid would be that useful on the Pixl display - on something with grayscale it'd be really good though.

    @Spocki do you have an example of how you'd want those things shown on the graph? Having stats in the history module would be extremely handy though as you say.

  • I am trying to get the graph to work with realtime data using the tinydash widget. The graph in the example file works but it is just plotting a curve on start without any updates. Whenever I try to push live sensor values to the graph I get no line being drawn. Is it possible to get an example of the tinydash graph widget that updates with new incoming values. Thanks

  • Sure - I think the RSSI grapher in EspruinoHub does it: https://github.com/espruino/EspruinoHub/­blob/master/www/rssi.html#L106

    You just need to call setData on the graph itself

  • You beat me to it. I was excited because a friend already pointed me in the right direction and I got it working earlier today. Thanks for the quick response and I still am looking forward to looking over the example you shared. Here is the code I used

    
    <html>
     <head>
       <meta name="viewport" content="width=640, initial-scale=1">
     </head>
     <body>
       <link href="/static/css/tinydash.css" rel="stylesheet">
       <script src="/static/tinydash.js"></script>
       <script src="/static/puck.js"></script>
       <script>
    var t;
    var j_light;
    t = [];
      function onLine(line) {
        try {
        
      
          var j = JSON.parse(line);
          console.log("Received JSON: ",j);
          j_light = j.light*100;
          t.push(j_light);
          elements.light.setValue(j_light);
          elements.logg.log(j_light);
          elements.gr.setData(t);
        
        } catch(e) {
          console.log("Received: ",line);
        }
      }
      var connection;
      function connectDevice() {
        Puck.connect(function(c) {
          if (!c) {
            alert("Couldn't connect!");
            return;
          }
          connection = c;
          // remove modal window
          elements.modal.remove();
          // Handle the data we get back, and call 'onLine'
          // whenever we get a line
          var buf = "";
          connection.on("data", function(d) {
            elements.log.log(40*Math.sin(Date.now()/­1000)+50);        
            //elements.logg.log(connection.write("di­gitalRead(BTN);\n")); 
            buf += d;
            var i = buf.indexOf("\n");
            while (i>=0) {
              onLine(buf.substr(0,i));
              buf = buf.substr(i+1);
              i = buf.indexOf("\n");
            }
          });
          // First, reset Puck.js
          connection.write("reset();\n", function() {
            // Wait for it to reset itself
            setTimeout(function() {
              // Now tell it to write data on the current light level to Bluetooth
              // 10 times a second. Also ensure that when disconnected, Puck.js
              // resets so the setInterval doesn't keep draining battery.
              //TD.update({log : 40*Math.sin(Date.now()/1000)+50});
              connection.write("setInterval(function()­{Bluetooth.println(JSON.stringify({light­:Puck.light()}));},100);NRF.on('disconne­ct', function() {reset()});\n",
                function() { console.log("Ready..."); });
              }, 1500);
            });
          });
      }  
      //function getTempValue() {
       // for (var i=0;i<100;i++) t.push(i);
        //connection.write("Puck.temp();\n", function(temper){
        //for (var i=0; i<100;i++) t.push(temper);
          //    {elements.gr.setData(t);}
         //  setTimeout(function() {
          // getTempValue();     
       //  }, 250);
      //  });
     // }
    
      // Set up the controls we see on the screen    
      var elements = {
        heading : TD.label({x:10,y:10,width:200,height:50,­label:"My Dashboard"}),
        b:TD.button({x:10,y:290,width:200,height­:100,label:"Press Me",value:0,name:"button",onchange:funct­ion(e){elements.log.log("Pressed!");}}),­
        log:TD.log({x:430,y:10,width:190,height:­200,label:"A Log",text:"A\nB\nC"}),
        logg:TD.log({x:630,y:10,width:190,height­:200,label:"A Log",text:"A\nB\nC"}),
        //function() {
          //  connection.write("Puck.getBatteryPercent­age();\n");
        //elements.logg.log(connection.write("Pu­ck.getBatteryPercentage();\n"));
        //Puck.eval("{bat:Puck.getBatteryPercent­age()}"
      //  }}),
        light : TD.gauge({x:10,y:70,width:200,height:220­,label:"Light",value:0,min:0,max:100}),
        redled : TD.toggle({x:210,y:200,width:200,height:­90,label:"Red LED",value:0,onchange:function(el,v) {
          connection.write("LED1.write("+v+");\n")­;
        }}),
       flash : TD.button({x:420,y:200,width:190,height:­90,label:"Flash!",value:0,onchange:funct­ion() { 
          connection.write("digitalPulse(LED3, 1, 500);\n");
        }}),
        vs:TD.value({x:10,y:400,width:200,height­:60,label:"Steppable Value",value:"1.2",min:1,step:0.1,max:2}­),
       // gr:TD.graph({x:220,y:420,width:400,heigh­t:170,label:"A Graph",data:function(){
      //  connection.write("Puck.temp();\n");}}),
        gr:TD.graph({x:220,y:420,width:400,heigh­t:170,label:"A Graph",data:t}),
        modal: TD.modal({x:10,y:10,width:190,height:430­,label:"Click to connect",onchange:connectDevice})
      }
      
      elements.log.log("Hello");
         for (var i in elements) document.body.appendChild(elements[i]);
         
      </script>
     </body>
    </html>
    
    
  • Great! Thanks for posting up your finished code too - that should really help others :)

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Graphing

Posted by Avatar for Gordon @Gordon

Actions