• Optimized the CGauge to draw by default only delta-value of the arc, in other words, the part of the arc that changes color, from value color to filler color or background color, when the value changes (and options omitted). To always draw the full arc(s), set draw arc delta only .dado=0 (or fals) after construction.

    Optimized drawings are still almost perfect. A few missing pixels because of overlaps:

    • when CGauge's full arc > 270 degrees and 'smaller' radiuses
    • when drawing a single line of a polygon because it shares points of previous and next line when new value is less then previously drawn value. (could be fixed by clearing one more value segment and redrawing it, requiring 12 trigonometric calculations... and some additional funky looking code. :[ So I did let it go... at least for now... ;) ~~~

    Options allow to force a complete drawing, including customizable/overridable .drawXtras(); (extras include by default the begin, end and optional 0-tick. Setting the tick length .tikL=0 after construction spresses tick drawing).

    Below the test code and CGauge prototype/'class'. Main change in the CGauge is in .draw(...) method - lines 133..142- where it is determined whether the the:

    • complete arc has to be drawn (because of options or .dado (draw arc delta only) values)
    • value increased and value arc has to be extended (draw value arc over begin of filler arc)
    • value decreased and value arc has to be shrinked (draw filler arc over end of value arc)

    Furthermore, the calculation of the filler arc vertices and with that the its drawing begin and end are reversed in order to handle the traverse:

    • The filler arc begins at the highest possible value point on the outer radius and goes 'down' to the value position, then - when inner radius is defined - switches to inner radius and goes back up to maximum possible value position.
    • The value arc begins at the lowest value position on the outer radius and goes 'up' to the value position, then - when inner radius defined - switches to inner radius and goes back 'down' to lowest value position. This creates the interesting case for the optimized drawing when the changes to a lower value: The traversing value - or a dot - has to be drawn with the value color at the value position after the filler expansion just draw it with the filler color.

    PS: Buttons have been redefined:

    • BTN1 advances small, bottom left (yellow) CGauge by one change value step (cg1c)
    • BTN2 halts execution of timers / intervals
    • BTN3 continues - resumes - execution

      // Swiss Federal Railway Clock implemented with
      // cgauge.js - circular gauge prototype / 'class'
      // allObjects - forum.espruino.com
      // 2020-11-29
      
      // 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 rng=0 // running
      , cg0,cg0c,cg0f,cg0t,cg0h // Circular Gauge 0 variables...
      , cg1,cg1c,cg1f,cg1t,cg1h
      , cg2,cg2c,cg2f,cg2t,cg2h,cg2i,cg2j,cg2u,c­g2k
      , cg3,cg3c,cg3f,cg3t,cg3h
      , cg4,cg4c,cg4f,cg4t,cg4h
      , cg5
      , cg6
      , b1w,b2w,b3w // button watch handles
      ;
      function run() { if (rng) halt(); rng=1;
      cg0=new CGauge("cg0",0,0,300,[0,  1,  0],[1  ,0  ,0  ],135,270,null,120,140,100,96);
      cg1=new CGauge("cg1",0,0, 30,[1,  1,  0],[0  ,0  ,0  ],180,180,null, 80,212, 16,11);
      cg2=new CGauge("cg2",0,0, 60,[1,  1,  1],[0.5,0.5,0.5],270,360,null,120,142, 18, 1); // sec
      cg3=new CGauge("cg3",0,0, 30,[1,  0,  1],[0  ,0  ,1  ],180,180,null,160,212, 16, 0);
      cg4=new CGauge("cg4",0,0, 40,[1,  0,  1],[0.6,0.6,0.6],120,-60,null,120,  8, 70,64);
      cg5=new CGauge("cg5",0,0, 60,[0.4,0.7,1],[0.5,0.5,0.5],270,360,nul­l,120,142, 53,42); // min
      cg6=new CGauge("cg6",0,0, 60,[1,  1,0.5],[0  ,0  ,0  ],270,360,null,120,142, 28,22); // hour
      cg0.setVal(100,-1);
      cg1.setVal( 20,-1);
      cg2.setVal(  0,-1); cg5.setVal(  0,-1); cg6.setVal(  0,-1); // sec, min, hour
      cg2.tikL=0;         cg5.tikL=0;         cg6.tikL=0;         // no ticks when opt2
      cg3.setVal( 15,-1);
      cg4.setVal( 30,-1);
      drawAll();
      cg0c= 12; cg0f=function(){ if (!cg0.setVal(cg0.val+cg0c)) cg0c=-cg0c; };
      cg0t=1200;
      cg1c=  3; cg1f=function(){ if (!cg1.setVal(cg1.val+cg1c)) cg1c=-cg1c; };
      cg1t=1900;
      cg2c=  1; cg2f=function(){ var v=cg2.val+cg2c; // +1 - seconds
                     if (v>=60 && cg2h) cg2h=clearInterval(cg2h);
                     cg2.setVal(v); };
      cg2i=function(){ cg2j(); cg2k=setInterval(cg2j,cg2u); }; // finished min
      cg2j=function(){ cg2h=setInterval(cg2f,cg2t); cg2.setVal(0); // minutes
                     cg5.setVal((cg5.val+1)%60); 
                     if (cg5.val%12==0) cg6.setVal((cg6.val+1)%60); };
      cg2t= 984; cg2u=60000;
      cg3c=5  ; cg3f=function(){ if (!cg3.setVal(cg3.val+cg3c)) cg3c=-cg3c; };
      cg3t=1700;
      cg4c=5  ; cg4f=function(){ if (!cg4.setVal(cg4.val+cg4c,1)) cg4c=-cg4c; };
      cg4t=3000;
      if (!b1w) b1w=setWatch(cg1f,BTN1,{edge:"rising",re­peat:true});
      if (!b2w) b2w=setWatch(halt,BTN2,{edge:"rising",re­peat:true});
      if (!b3w) b3w=setWatch(cont,BTN3,{edge:"rising",re­peat:true});
      cont();
      }
      function cont() { var d; halt();
      cg0h=setInterval(cg0f,cg0t);
      cg1h=setInterval(cg1f,cg1t);
      d=new Date();
      cg2.setVal(d.getSeconds());
      cg5.setVal(d.getMinutes());
      cg6.setVal(d.getHours()%12*5+Math.floor(­cg5.val/12));
      cg2k=(cg2.val!=0)?setTimeout(cg2i,cg2u-c­g2.val*cg2t)
                      :setInterval(cg2j,cg2u);
      if (cg2.val<cg2.maxV) cg2h=setInterval(cg2f,cg2t);
      cg3h=setInterval(cg3f,cg3t);
      cg4h=setInterval(cg4f,cg4t);
      }
      function halt() {
      if (cg0h) cg0h=clearInterval(cg0h);
      if (cg1h) cg1h=clearInterval(cg1h);
      if (cg2h) cg2h=clearInterval(cg2h);
      if (cg2k) cg2k=clearInterval(cg2k);
      cg2.vLD=null; cg5.vLD=null; cg6.vLD=null;
      if (cg3h) cg3h=clearInterval(cg3h);
      if (cg4h) cg4h=clearInterval(cg4h);
      }
      
      function drawAll() {
      g.clear(); setTimeout(function() {
        cg0.draw(1,1);
        cg1.draw(1);
        for (var i=1;i<=12;i++) { cg2.drawTick(cg2.x,cg2.y,cg5.rIn-5,
          (cg6.rOut+5)/(cg5.rIn-5),cg2.rad(270+i*3­0),[1,0.8,0.8]); }
        cg2.draw(1); cg5.draw(1); cg6.draw(1);
        cg3.draw(1,1);
        cg4.draw(1,1); }
      , 1000); }
      
      var p; // temp for prototype references
      function CGauge(id,val,minV,maxV,color,fColor,beg­Deg,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
      _.tikL=6;       // tick-length (set to 0 post construction for no ticks drawing)
      _.dado=1;       // draw arc delta only  
      _.bClr=[0,0,0]; // background color
      _.id=id;        // id of the circular gauge
      _.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 rad used for sCnt only
      _.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 
      _.vLD=null;       // (display/draw) value (v) last displayed/drawn
      _.setVal(val,-1); // set value only
      } p=CGauge.prototype;
      p.setVal=function(v,o1,o2) { // --- set min/max adj'd val, draw != && o1=0 || o1>0; 
      var chd = (v=(v<this.minV)?this.minV:(v>this.maxV)­?this.maxV:v)!=this.val; // ret
      if (o1<0) { this.val=v; this.vLD=null; // update value only, NO drawing & never draw
      } else if (v!=this.val||o1>0||o2) { this.val=v; this.draw(o1,o2); }
      return chd; };
      p.draw=function(o1,o2) { // --- draw circular gauge (otp1:value, 2:ticks+extras)
      var s=this.sCnt,v=Math.round(s/(this.maxV-th­is.minV)*this.val),h=(this.rIn)?1:0
      , vL,vs,m;
      if (o2) { this.drawXtras(s,v,h,o1); } // console.log(this.id,this.val,v,s,o1,o2,t­his.cUp);
      if (o1!=-1) { if (h) { g.setColor.apply(g,this.bClr);
           while (this.cUp.length) g.drawLine.apply(g,this.cUp.pop()); }
      if (o1==1||!this.dado||(vL=this.vLD)==null)­ {
        if (v<s) g.setColor.apply(g,this.fClr).drawPoly(t­his._pvs(v,s,-1),h);
        g.setColor.apply(g,this.clr).drawPoly(th­is._pvs(0,v,1),h);
      } else if (v>vL) {
        g.setColor.apply(g,this.clr).drawPoly(th­is._pvs(vL,v,1),h&&vL==0);
      } else if (v<vL) {
        g.setColor.apply(g,this.fClr).drawPoly(v­s=this._pvs(v,vL,-1),h&&vL==s);
        vs=(h)?vs.slice((m=vs.length/2-2),m+4):(­m=vs.slice(-2)).concat(m);
        g.setColor.apply(g,this.clr);g.drawLine.­apply(g,vs);
      } } this.vLD=v; };
      p.drawXtras=function(s,v,h,o1) { // --- draw extras... place holder for custom override
      if (this.tikL) this.drawTicks(h); }; // incl drawTicks() in custom override if needed 
      p.drawTicks=function(h) { // --- draw ticks, begin and end and 0-tick
      var x=this.x,y=this.y,rTO=(h)?this.rIn:this.­rOut,rTI=rTO-this.tikL,bR=this.begR
      , eR=bR+this.sCnt*this.segR,rTS=((rTI<0)?0­:rTI)/rTO; // console.log(this.id,rTO,rTI,rTS);
      this.drawTick(x,y,rTO,rTS,eR,this.fClr);­this.drawTick(x,y,rTO,rTS,bR,this.clr);
      if (this.deg0!=this.begD) this.drawTick(x,y,rTO,rTS,bR,this.clr); };
      p.drawTick=function(x,y,t,s,r,c) { // --- draw tick x,y,radius,scale,radian,color
      var vX=t*Math.cos(r),vY=t*Math.sin(r); g.setColor.apply(g,c); g.drawLine(
      Math.round(x+vX),Math.round(y+vY),Math.r­ound(x+vX*s),Math.round(y+vY*s)); };
      p._pvs=function(f,t,d) { // --- calc polygon vertices from..to in direction
      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,j // vertices array index (running and 'turn around'/last outer)
      , m=(d>0)?f:t,r // segmentRadian 'multiplier' (starting w/ f or t+1), radian
      ; // console.log(this.id,f,t,d,"|",x,y,rO,rI,­bR,sR,m,s,i);
      while (++s<=t) { r=bR+m*sR; m+=d;
      v[++i]=Math.round(x+rO*Math.cos(r));
      v[++i]=Math.round(y+rO*Math.sin(r)); } // console.log(this.id,s,r,v[i-1],v[i]); }
      if (rI) { j=i;
      while (--s>=f) { m-=d; r=bR+m*sR;
        v[++i]=Math.round(x+rI*Math.cos(r));
        v[++i]=Math.round(y+rI*Math.sin(r)); }
      this.cUp.push(v.slice(j-1,j+3));
      } // console.log(this.id,d,j,i,v.slice(0,4),t­his.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);
      
About

Avatar for allObjects @allObjects started