HTML5 Canvas - sample gauge for smart home monitoring systems
Wrapper over HTML5 canvas for polar drawing (coordinates origin are in the centre of the canvas, the angle coordinate goes anti-clockwise, and the unitary magniture is the minimum of width and height of canvas) is attached.
Instantiation is:
var canvas=document.getElementById(dialName+"_z");
var drawing=new Drawing(canvas);
.
Sample usage (partial code) is in
Sample.js.
Result is:
function Drawing(canvas){
this.canvas=canvas;
this.context=canvas.getContext('2d');
this.resolution=Math.min(this.canvas.width,this.canvas.height);
this.offset={x:(this.canvas.width-this.resolution)/2,y:(this.canvas.height-this.resolution)/2};
this.clear=function(){
this.context.beginPath();
this.context.fillStyle='#fff';
this.context.fillRect(0,0,this.canvas.width,this.canvas.height);
this.context.fill();
}
this.open=function(params){
this.context.beginPath();
this.context.strokeStyle=params.fg?params.fg:'#000';
this.context.fillStyle=params.bg?params.bg:'#fff';
this.context.lineWidth=params.w?params.w:1;
}
this.close=function(params){
if(params.fg)this.context.stroke();
if(params.bg)this.context.fill();
}
this.convert=function(coords){
if(coords.ax||coords.ay)return coords;
if(coords.x||coords.y)
return {ax:this.resolution/2*(1+coords.x)+this.offset.x,ay:this.resolution/2*(1-coords.y)+this.offset.y};
if(coords.a||coords.m)
return{ax:this.resolution/2*(1+coords.m*Math.cos(coords.a))+this.offset.x,ay:this.resolution/2*(1-coords.m*Math.sin(coords.a))+this.offset.y};
return {ax:this.resolution/2+this.offset.x,ay:this.resolution/2+this.offset.y};
}
this.circle=function(params){
this.open(params);
var centre=this.convert({x:0,y:0});
this.context.arc(centre.ax,centre.ay,params.r*this.resolution/2,0,2*Math.PI);
this.close(params);
}
this.line=function(params){
this.open(params);
var to=this.convert(params.to);
var from=this.convert(params.from);
this.context.moveTo(from.ax,from.ay);
this.context.lineTo(to.ax,to.ay);
var a=this.convert({a:0,m:0.9});
this.close(params);
}
this.text=function(params){
this.open(params);
this.context.textAlign=params.align?params.align:'center';
this.context.textBaseline='middle';
this.context.font = this.resolution/2*params.size+'px sans-serif';
var coords=this.convert(params);
if(params.fg)
this.context.strokeText(params.text,coords.ax,coords.ay);
if(params.bg)
this.context.fillText(params.text,coords.ax,coords.ay);
this.close(params);
}
return this;
}
:for(var sensorIdx=0;sensorIdx<sensors.length;sensorIdx++){
var dialName=sensors[sensorIdx];
var canvas=document.getElementById(dialName+"_z");
var drawing=new Drawing(canvas);
drawing.clear();
drawing.circle({w:0.5,r:1,fg:'#000',bg:'#448'});
drawing.circle({r:0.85,fg:'#111',bg:'#eee'});
drawing.line({w:4,fg:'#f00',from:{a:Math.PI/2,m:0},to:{a:Math.PI/2,m:0.98}});
drawing.line({w:2,fg:'#ff0',from:{a:Math.PI/2,m:0},to:{a:Math.PI/2,m:0.97}});
for(var hour=1;hour<=12;hour++){
drawing.line({w:0.25,fg:'#777',from:{x:0,y:0},to:{a:offsetA-Math.PI*2*(hour)/(12),m:0.85}});
drawing.text({a:offsetA-Math.PI*2*(hour)/(12),m:0.925,text:hour,size:0.125,fg:'#bbb'});
}
:
if(MOUSE_OVER){
if(dialName==MOUSE_OVER.sensor){
MOUSE_OVER.at=Math.atan2(-MOUSE_OVER.y+canvas.height/2,MOUSE_OVER.x-canvas.width/2);
while(MOUSE_OVER.at<0)
MOUSE_OVER.at+=2*Math.PI;
while(MOUSE_OVER.at>=2*Math.PI)
MOUSE_OVER.at-=2*Math.PI;
drawing.line({from:{a:0,m:0},to:{a:MOUSE_OVER.at,m:0.85},w:2,fg:'#f70'});
}
}
:
drawing.circle({w:0.5,r:0.4,fg:'#000',bg:'#dde'});
var params={};
drawing.text({x:0,y:1.2,text:allSensors[sensorProperties.name].label,size:0.2,align:'center',bg:'#000',fg:'#ff0'});
var lastPoint=null;
var tooltips=[];
for(var dataIdx=0;dataIdx<data.length;dataIdx++){
var x=data[dataIdx][0];
if(lastTS-x>1000000)continue;
if(!minTSIdx)minTSIdx=dataIdx;
if(data[minTSIdx][0]>x)
minTSIdx=dataIdx;
var y=data[dataIdx][1+json.sensor2column[sensorProperties.name]];
if(!data[dataIdx][1+json.sensor2column[sensorProperties.name]])continue;
if((x==null)||(y==null))continue;
x/=100;
var min=x%100;
var hour=((x-min)/100)%100;
var params={};
params.w=0.5+3*dataIdx/data.length*dataIdx/data.length;
params.from=lastPoint;
var chan=2.5+2.5*Math.sin(Math.PI*2*((12+6+hour-1)/24));
chan=Math.round(chan);
params.fg='rgba('+chan*255/5+',0,'+(5-chan)*255/5+','+(1)+')';
params.to={};
params.to.a=offsetA-2*Math.PI*(hour/12+min/12/60);
while(params.to.a<0)
params.to.a+=2*Math.PI;
while(params.to.a>=2*Math.PI)
params.to.a-=2*Math.PI;
params.to.m=(y-minVal)/(maxVal-minVal)*0.4+0.4;
params.y=y;
if(lastPoint){
drawing.line(params);
if(MOUSE_OVER)
if(dialName==MOUSE_OVER.sensor){
if(between(params.from.a,MOUSE_OVER.at,params.to.a)){
if(tooltips.length==2)
alert([params.from.a,MOUSE_OVER.at,params.to.a]);
tooltips.push(dataIdx);
}
}
}
lastPoint=params.to;
}
var rezolutie=allSensors[sensorProperties.name].resolution;
var actualValue=Math.round(params.y/rezolutie)*rezolutie;
var yesterdaysValue=Math.round(data[minTSIdx][colIdx]/rezolutie)*rezolutie;
actualValue=1*actualValue.toFixed(6);
yesterdaysValue=1*yesterdaysValue.toFixed(6);
var delta=actualValue-yesterdaysValue;
delta=1*delta.toFixed(6);
if(delta>0)
delta='+'+delta;
delta=delta+' ';
drawing.text({x:0,y:-0.34,text:minVal,size:0.1,bg:"#bbb"});
drawing.text({x:0,y:+0.34,text:maxVal,size:0.1,bg:"#bbb"});
if(actualValue>yesterdaysValue){
drawing.text({x:0,y:0.2,text:actualValue,size:0.25,align:'center',fg:params.fg,bg:params.fg});
drawing.text({x:0,y:0,text:delta+"↗",size:0.1,bg:"#f00",align:"center"});
drawing.text({x:0,y:-0.15,text:allSensors[sensorProperties.name].um,size:0.2,align:'center',fg:params.fg,bg:params.fg});
}else if(actualValue<yesterdaysValue){
drawing.text({x:0,y:0.2,text:actualValue,size:0.25,align:'center',fg:params.fg,bg:params.fg});
drawing.text({x:0,y:0,text:delta+"↘",size:0.1,bg:"#00f",align:"center"});
drawing.text({x:0,y:-0.15,text:allSensors[sensorProperties.name].um,size:0.2,align:'center',fg:params.fg,bg:params.fg});
}else{
drawing.text({x:0,y:0,text:actualValue+allSensors[sensorProperties.name].um,size:0.2,align:'center',fg:params.fg,bg:params.fg});
}