...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,cg2k // 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,null,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)%60==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",repeat:true});
if (!b2w) b2w=setWatch(halt,BTN2,{edge:"rising",repeat:true});
if (!b3w) b3w=setWatch(cont,BTN3,{edge:"rising",repeat: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-cg2.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*30),[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,begDeg,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(_.degs); // 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-this.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,this.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(vs,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(vs,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.slice(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.round(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),this.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);
Espruino is a JavaScript interpreter for low-power Microcontrollers. This site is both a support community for Espruino and a place to share what you are working on.
...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.
5 Attachments