• Updated: Most recent code of 2020-11-27 in post #26. - Code version here is kept for preserving line numbers used in posts up to #26.

    CGauge code with examples:

    // cgauge.js - circular gauge
    // allObjects - forum.espruiono.com
    // 2020-11-24
    
    // left/top = x/y = 0/0
    
    // Circular Gauge cg0:
    // - visual: 270 degree clock-wise gauge starting mid left bottom quadrant
    // - graphics: ...starting mid 2nd quadrant ending mid 1st quadrant
    // - showing values from 0..300 clockwise with initial value of 100
    
    var cg0
      , cg1,cg1c,cg1f,cg1t,cg1h
      , cg2,cg2c,cg2f,cg2t,cg2h
      , cg3,cg3c,cg3f,cg3t,cg3h
      , cg4
      , b1w,b2w,b3w
      ;
    function run() {
      halt();
      cg0=new CGauge(0,0,300,[0,1,0],[1  ,0  ,0  ],135,270,null,120,140,100,96);
      cg1=new CGauge(0,0, 30,[1,1,0],[0  ,1  ,1  ],180,180,null, 80,212, 16,14);
      cg2=new CGauge(0,0,180,[1,1,1],[0.3,0.3,0.3],100­,340,null,120,180, 22, 1);
      cg3=new CGauge(0,0, 30,[1,0,1],[0  ,0  ,1  ],180,180,null,160,212, 16, 0);
      cg4=new CGauge(0,0, 40,[1,0,1],[0.3,0.3,0.3],120,-60,null,12­0,  8, 70,64);
      cg0.setVal(100,-1);
      cg1.setVal( 20,-1);
      cg2.setVal(108,-1);
      cg3.setVal( 15,-1);
      cg4.setVal( 10,-1);
      drawAll();
      cg1c=1; cg1f=function(){ if (!cg1.setVal(cg1.val+cg1c)) cg1c=-cg1c; };
      cg1t=750;
      cg2c=9; cg2f=function(){ if (!cg2.setVal(cg2.val+cg2c)) cg2c=-cg2c; };
      cg2t=1700;
      cg3c=3; cg3f=function(){ if (!cg3.setVal(cg3.val+cg3c)) cg3c=-cg3c; };
      cg3t=1000;
      if (!b1w) b1w=setWatch(cg1f,BTN1,{edge:"rising",re­peat:true});
      if (!b2w) b2w=setWatch(cg2f,BTN2,{edge:"rising",re­peat:true});
      if (!b3w) b3w=setWatch(cg3f,BTN3,{edge:"rising",re­peat:true});
      cont();
    }
    function cont() {
       cg1h=setInterval(cg1f,cg1t);
       cg2h=setInterval(cg2f,cg2t);
       cg3h=setInterval(cg3f,cg3t);
    }
    function halt() {
      if (cg1h) cg1h=clearInterval(cg1h);
      if (cg2h) cg2h=clearInterval(cg2h);
      if (cg3h) cg3h=clearInterval(cg3h);
    }
    
    function drawAll() { 
      g.clear(); setTimeout(function() {
          cg0.draw(); cg1.draw(); cg2.draw(); cg3.draw(); cg4.draw(); }
        , 1000); }
    
    var p; // temp for prototype references
    function CGauge(val,minV,maxV,color,fColor,begDeg­,degs,deg0,x,y,rOuter,rInner) {
      var _=0||this;
      _.mxXY=239;    // x, y max graph coord - defaults for BangleJS Graphics
      _.pps=2;       // 'pixel per segment'/jaggedness/graphical precision/resolution
      _.val=null;    // temporary, set at end of construction
      _.minV=minV;   // minimum value (arc all in fColor)
      _.maxV=maxV;   // maximum value (arc all in color)
      _.clr=color;   // color - as required by Graphics - for the value arc
      _.fClr=fColor; // color - as required by Graphics - for to complete the arc
      _.begD=begDeg; // 0 degrees: +x-Axis
      _.degs=degs;   // gauge full arc in degrees -/+ = counter/clockwise
      _.deg0=(deg0)?deg0:begDeg; // for 0/center value mark; falsy defaults to begDeg
      _.x=x;         // center x
      _.y=y;         // center y
      _.rOut=rOuter; // radius outer
      _.rIn=rInner;  // radius inner (optional)
      _.begR=_.rad(_.begD);                              // begin radian
      _.arcR=(_.degs==360)?Math.PI*2:_.rad(_.d­egs);      // arc radian
      _.segR=(Math.PI/(4/_.pps)/_.rOut)*((degs­>0)?1:-1); // segment radian
      _.sCnt=Math.round(Math.abs(_.arcR/_.segR­));        // segment count in arc
      _.cUp=[];                                          // clean up vertices 
      _.setVal(val,-1); // set value only
    } p=CGauge.prototype;
    p.setVal=function(v,opt) { // --- set min/max adj'd val, draw != && opt=0 || opt>0; 
      var chd = (v=(v<this.minV)?this.minV:(v>this.maxV)­?this.maxV:v)!=this.val; // ret
      if (opt<0) { this.val=v; // update value only, NO drawing
      } else if (v!=this.val||opt>0) { this.val=v; this.draw(); }
      return chd; };
    p.draw=function(_) { // --- draw circular gauge
      var s=this.sCnt, v=Math.round(s/(this.maxV-this.minV)*thi­s.val-1)+1, h=!!this.rIn;
      g.setColor(0,0,0); while (this.cUp.length) g.drawLine.apply(g,this.cUp.pop());
      v=(v<s)?v=v:s+1; // console.log(this.val,v,s);
      if (v<s) g.setColor.apply(g,this.fClr).drawPoly(t­his._pvs(v+1,s+1,0),h);
      g.setColor.apply(g,this.clr).drawPoly(th­is._pvs(0,v,1),h); };
    p._pvs=function(f,t,c) { // --- calc polygon vertices from..to
      var x=this.x, y=this.y, rO=this.rOut, rI=this.rIn, bR=this.begR, sR=this.segR
        , l=(t-f+1)*2*((rI)?2:1) // len of array for vertices (double w/ inner radius
        , v=((this.mxXY<=355) ? new Uint8Array(l) : new Uint16Array(l)) // vertices array
        , s=f-1 // segment index 
        , i=-1  // vertices array index
        , r     // radian of vertice 
        , j     // 'turn around' // last of outer
        ; // console.log(x,y,rO,rI,bR,sR,f,t,s,i);
      while(++s<=t) { r=bR+s*sR;
        v[++i]=Math.round(x+rO*Math.cos(r));
        v[++i]=Math.round(y+rO*Math.sin(r)); } // console.log(s,r,v[i-1],v[i]); }
      if (rI) { j=i;
        while (--s>=f) { r=bR+s*sR;
          v[++i]=Math.round(x+rI*Math.cos(r));
          v[++i]=Math.round(y+rI*Math.sin(r)); }
        this.cUp.push((c)?v.slice(j-1,j+3):v.sli­ce(0,2).concat(v.slice(-2)));
      } //console.log(c,j,v.slice(0,4),this.cUp)­; }
      return v; };
    p.rad=function(degrs) { return 2*Math.PI*(degrs%360)/360; }; // radian <-- degrees
    
    function r() { run(); }
    function h() { halt(); }
    function c() { cont(); }
    
    setTimeout(run,999);
    

    For the initial work, I precalculated the vertices and then stringed together the ones I needed, but it got me quickly out of memory, because slicing (and concatenating) of Uint8Arrays creates regular arrays and wastes a lot of RAM. I could go for some crazy compiled C- as described in this conversation about Efficiently moving things around in zig-zag Graphics buffer visualized w/ 24/32 bpp displays / neopixel strings or even worse using some inline Assembler - but it would still be a memory challenge with multiple simultaneous gauges. So I calculate for every draw just what I need in graphic precision (resolution) and memory optimized way.

    The code can be sped up by increasing the .pps - pixel per segment - value after construction. Currently it is set for 2, which seems to be a good compromise. Higher values yield rougher, more jagged edges. Lower than 2 - such as 1 (the lowest, going lower is no point) - gives smoother edges close to horizontal and vertical slope. Higher values can help mitigate memory issues, but yield less accurate graphical representation of the value (enhancements are in thoughts). You can interpret this value also as 'graphical precision', because any value is mapped to a segment with that number of pixels in x and y dimension (You can see the x and y value sequences by looking at the vertices returned from ._pvs() - (private) calc polygon vertices - 'method'.

    If you want to use the code on displays where calculated vertices - x or y coordinates - will be larger than 255, set the .mxXY value after construction to 256 or more in order to force the use of an Uint16Array... (and be aware of the memory consumption).

    Outlook: As mentioned, there is still room for improvement / enhancements... last but not least, this base prototype/'class' can be wrapped and used with drawing the 0-tick at ```deg0''' position and more 'ticks'/min/max/overshoot/labels and even for a multi segment display gauge.

About

Avatar for allObjects @allObjects started