• ...and here the eXtende Circular Gauges that allow mitch and match of filled and unfilled arcs... Fill is already extracted but I guess there may be more code streamlining possible. Attached is also a clip to show real behavior on the BanglJS watch. Since junk by by junk of 62 points - 31 segments - with 1 segment overlap is drawn, you can notice it in the large green-red 270 degree CGauge. Despite all gauges are animated, behavior seams time true. The clip shows the minute change.

    Drawing of gauges with mixed - filled and hallow - arcs require to first fill in background color, then outline and for the filled part lastly fill the segment - full or delta draw of the value and filler arg. Constructor has a few more argument to define the fill. Currently, the fill - other than background - has to be of the same color of the outline... adding more config on construction - in addition to the fill indicators - which could actually be the color object - and the optional background color, the constructor with flat argument becomes heavy. Providing a structured config object may be the answer here to reduce the number of arguments and get away from the constraint positional arguments pose.

    // Swiss Federal Railway Clock implemented with
    // CGaugeX.js - circular gauge prototype / 'class'
    // allObjects - forum.espruino.com
    // 2020-11-30
    
    // 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 t=0   // test mode
      , rng=0 // running        // c:change/f:funcion/t:interval time/h:inteval handle 
      , cg0,cg0c,cg0f,cg0t,cg0h // Circular Gauge 0 variables... big one
      , cg1,cg1c,cg1f,cg1t,cg1h // small one lower left
      , cg2,cg2c,cg2f,cg2t,cg2h,cg2i,cg2j,cg2u,c­g2k // center, seconds of clock
      , cg3,cg3c,cg3f,cg3t,cg3h // small on elower right
      , cg4,cg4c,cg4f,cg4t,cg4h // middle top inside bow of bit one
      , cg5                     // co-centric, outer around hr ticks, minutes of clock
      , cg6                     // co-centeric, around seconds insice hour ticks, hour of clock
      , b1w,b2w,b3w // button watch handles
      ;
    function run() { // --- run / startup application
      if (rng) halt(); rng=1; // prevent multiple timer instances for same function
      // --- construct gauges, no display/drawing yet - static setup
      cg0=new CGauge("cg0",0,0,300,[0,  1,  0],[1  ,0  ,0  ],135,270,null,120,140,100,96,0,1);
      cg1=new CGauge("cg1",0,0, 30,[1,  1,  0],[0  ,0  ,0  ],180,180,null, 80,212, 16,11,1,1);
      cg2=new CGauge("cg2",0,0, 60,[1,  1,  1],[0.5,0.5,0.5],270,360,null,120,142, 18, 1,1,0); // 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,1,1);
      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,1,0); // min
      cg6=new CGauge("cg6",0,0, 60,[1,  1,0.5],[0  ,0  ,0  ],270,360,null,120,142, 28,22,1,0); // 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 on in draw
      cg3.setVal( 15,-1);
      cg4.setVal( 30,-1);
      drawAll(); // --- initial / first draw - not animated yet - static draw
      // --- setup anumiations with change value and function on timer and value events
      cg0c= 60; 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,1); // minutes
                       cg5.setVal((cg5.val+1)%60); 
                       if (cg5.val%12==0) cg6.setVal((cg6.val+1)%60,((cg6.val+1)%6­0==0)?1:0); };
      cg2t= 984; cg2u=60000; // milliseconds for second and for minute
      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;
      // hookup buttons
      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(); // start animation
    }
    function cont() { var d; halt(); // --- continue, restart timers/...
       cg0h=setInterval(cg0f,cg0t);
       if (t) return;
       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() { // --- halt, clear timers/...
      if (cg0h) cg0h=clearInterval(cg0h);
      if (t) return;
      if (cg1h) cg1h=clearInterval(cg1h);
      if (cg2h) cg2h=clearInterval(cg2h);
      if (cg2k) cg2k=clearInterval(cg2k);
      // will enforce full draw on resume/continue after halt
      // to ensure correct watch display after break
      cg2.vLD=null; cg5.vLD=null; cg6.vLD=null;
      if (cg3h) cg3h=clearInterval(cg3h);
      if (cg4h) cg4h=clearInterval(cg4h);
    }
    
    function drawAll() { // --- draw all for initial draw
      g.clear(); setTimeout(function() {
          // g.setColor(0.3,0.3,0.3).fillRect(0,0,239­,239);
          cg0.draw(1,1);
          if (t) return;
          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,fill,fFill,bgColor) {
      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 (used as default)
      _.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==null)?deg0:begDeg; // for 0/center value mark; null defaults to begDeg
      _.x=x;          // center x
      _.y=y;          // center y
      _.rOut=rOuter;  // radius outer
      _.rIn=rInner;   // radius inner (optional)
      _.fV=fill;      // fill value arc w/ color
      _.fF=fFill;     // fill filler arc w/ fColor
      _.bClr=(bgColor)?bgColor:this.bClr;                // opt bg color, defaults blk 
      _.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,fV=!!this.fV,fF=!!this.­fF,bC=this.bClr
        , vL,vs,m; // console.log(this.id,this.val,v,s,o1,o2,t­his.cUp);
      if (o2) { this.drawXtras(s,v,h,o1); }
      if (o1!=-1) { if (h&&(fV==fF)) { g.setColor.apply(g,bC);
             while (this.cUp.length) g.drawLine.apply(g,this.cUp.pop()); 
          } else { this.cUp=[]; }
        if (o1==1||!this.dado||(vL=this.vLD)==null)­ {
          if (v<s) { vs=this._pvs(v,s,-1,0);
            if (h&&!fF&&fV) { g.setColor.apply(g,bC); this.fSs(vs,vs.length); }
            g.setColor.apply(g,this.fClr).drawPoly(v­s,h);
            if (h&&fF) this.fSs(vs,vs.length); vs=null; }
          vs=this._pvs(0,v,1,1);
          if (h&&!fV&&fF) { g.setColor.apply(g,bC); this.fSs(vs,vs.length); }
          g.setColor.apply(g,this.clr).drawPoly(vs­,h);
          if (h&&fV) this.fSs(vs,vs.length); vs=null;
        } else if (v>vL) { vs=this._pvs(vL,v,1,1);
          if (h&&!fV&&fF) { g.setColor.apply(g,bC); this.fSs(vs,vs.length); }
          g.setColor.apply(g,this.clr).drawPoly(vs­,h&&vL==0);
          if (h&&fV) this.fSs(vs,vs.length); vs=null;
        } else if (v<vL) { vs=this._pvs(v,vL,-1,1);
          if (h&&!fF&&fV) { g.setColor.apply(g,bC); this.fSs(vs,vs.length); }
          g.setColor.apply(g,this.fClr).drawPoly(v­s,h&&vL==s);
          if (h&&fF) this.fSs(vs,vs.length);
          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.fSs=function(vs,vsL) { // --- fill
      if (vsL<64) { g.fillPoly(vs,1); // console.log("------",vsL/2);
      } else { var k=30,l=vsL/2,i=0,j=vsL-k,n; // console.log("start:",k,":",i,i+k,l,j,j+k­);
        while (i<l) { // console.log(":",k,":",i,i+k,l,j,j+k);
          g.fillPoly(vs.slice(i,i+=k).concat(vs.sl­ice(j,j+k)),1);
          if (i<l) { i-=2; if ((n=l-i)<30) k=n; j-=k-2;
            if (k==2) { k+=2; i-=2; j+=2; }
      } } } };
    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,c) { // --- 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)); }
        if (c) 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
    
    // convenient single char functions for control in console
    function r() { run(); }
    function h() { halt(); }
    function c() { cont(); }
    
    setTimeout(run,999);
    

    5 Attachments

About

Avatar for allObjects @allObjects started