Canvas based radar chart.
A Pen by Wesley DEGLISE on CodePen.
| <canvas id="canvas" width="500" height="500"> </canvas> |
Canvas based radar chart.
A Pen by Wesley DEGLISE on CodePen.
| /* init globals */ | |
| var canvas = document.getElementById('canvas'); | |
| var ctx = canvas.getContext('2d'); | |
| // limit fps | |
| var fps = 15; | |
| var now; | |
| var then = Date.now(); | |
| var interval = 1000/fps; | |
| var delta; | |
| var speed = 1; | |
| var draggable = false; | |
| var mouseX,mouseY; | |
| var width=canvas.width; | |
| var height=canvas.height; | |
| //Browser support touch or mouse event ? | |
| var isTouchSupported = 'ontouchstart' in window; | |
| var startEvent = isTouchSupported ? 'touchstart' : 'mousedown'; | |
| var moveEvent = isTouchSupported ? 'touchmove' : 'mousemove'; | |
| var endEvent = isTouchSupported ? 'touchend' : 'mouseup'; | |
| //////////////////////////////////////// | |
| /* Loop */ | |
| ;(function () { | |
| function main() { | |
| window.requestAnimationFrame( main ); | |
| now = Date.now(); | |
| delta = now - then; | |
| if (delta > interval || speed.value>0) { | |
| then = now - (delta % interval); | |
| activities.radar.loop(); | |
| } | |
| } | |
| main(); // Start the cycle | |
| })(); | |
| /////////////////////////////////////////// | |
| /* RADAR Class | |
| * data: array of base/minimal value for each axis | |
| * x: horizontal center of the radar on the canvas | |
| * y: vertical center of the radar on the canvas | |
| * radius: radius of the radar | |
| */ | |
| var Radar=function(data,x,y,radius){ | |
| this.dataset = data.slice(0); | |
| this.centerX = x || Math.floor(canvas.width / 2); | |
| this.centerY = y || Math.floor(canvas.height / 2); | |
| this.radius = radius || Math.floor(canvas.width / 2)-50;//added padding | |
| this.handles = new Array(); | |
| this.handleRadius=10; | |
| this.step=360/this.dataset.length; | |
| ctx.lineCap = 'round'; | |
| this.radgrad = ctx.createRadialGradient(this.centerX,this.centerY,this.radius/50,this.centerX,this.centerY,this.radius); | |
| this.radgrad.addColorStop(0, 'rgba(0,255,0,0)'); | |
| this.radgrad.addColorStop(0.5, 'rgb(255,200,0)'); | |
| this.radgrad.addColorStop(1, 'rgba(255,0,0,0)'); | |
| }; | |
| Radar.prototype={ | |
| //draw Radar | |
| drawBackground: function() { | |
| var centerX = this.centerX; | |
| var centerY = this.centerY; | |
| var radius = this.radius; | |
| var step = this.step; | |
| ctx.beginPath(); | |
| for (i=0;i<this.dataset.length;i++){ | |
| ctx.lineWidth = 1; | |
| ctx.strokeStyle = this.radgrad; | |
| var pos = this.lineToAngle(ctx, centerX, centerY, radius, i*step-90); | |
| ctx.stroke(); | |
| } | |
| ctx.closePath(); | |
| ctx.stroke(); | |
| }, | |
| drawArea: function() { | |
| var centerX = this.centerX; | |
| var centerY = this.centerY; | |
| var radius = this.radius; | |
| var step = this.step; | |
| var handles = this.handles; | |
| ctx.beginPath(); | |
| for (i=0;i<this.dataset.length;i++){ | |
| var pos = this.pointToAngle(ctx, centerX, centerY, radius*this.dataset[i], i*step-90); | |
| handles.splice(i,1,pos); | |
| if(i==0){ | |
| ctx.moveTo(pos.x, pos.y); | |
| }else{ | |
| ctx.lineTo(pos.x, pos.y); | |
| } | |
| } | |
| ctx.closePath(); | |
| ctx.strokeStyle="rgba(255,255,255,0.7)"; | |
| ctx.fillStyle="rgba(255,255,255,0.3)"; | |
| ctx.lineWidth = 3; | |
| ctx.fill(); | |
| ctx.stroke(); | |
| }, | |
| drawHandles: function(){ | |
| var centerX = this.centerX; | |
| var centerY = this.centerY; | |
| var radius = this.radius; | |
| var step = this.step; | |
| var handles = this.handles; | |
| var handleRadius = this.handleRadius; | |
| ctx.strokeStyle="rgba(180,200,255,0.7)"; | |
| ctx.fillStyle="rgba(180,200,255,0.5)"; | |
| ctx.lineWidth = 3; | |
| for (i=0;i<this.dataset.length;i++){ | |
| ctx.beginPath(); | |
| ctx.arc(handles[i].x,handles[i].y,handleRadius,0,2*Math.PI,true); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| } | |
| }, | |
| drawLabel: function(){ | |
| var centerX = this.centerX; | |
| var centerY = this.centerY; | |
| var radius = this.radius; | |
| var step = this.step; | |
| var handles = this.handles; | |
| ctx.fillStyle="rgba(255,255,255,1)"; | |
| ctx.font = "12pt Arial,sans-serif"; | |
| ctx.textAlign= "center"; | |
| for (i=0;i<this.dataset.length;i++){ | |
| var pos = this.lineToAngle(ctx, centerX, centerY, radius+20, i*step-90); | |
| ctx.fillText(dataLabel[i], pos.x, pos.y,200); | |
| } | |
| }, | |
| lineToAngle: function (ctx, x1, y1, length, angle) { | |
| ctx.moveTo(x1, y1); | |
| var target= this.pointToAngle(ctx, x1, y1, length, angle); | |
| ctx.lineTo(target.x,target.y); | |
| return target; | |
| }, | |
| pointToAngle: function(ctx, x1, y1, dist, angle){ | |
| angle *= Math.PI / 180; | |
| var x2 = x1 + dist * Math.cos(angle), | |
| y2 = y1 + dist * Math.sin(angle); | |
| return {x: x2, y: y2}; | |
| }, | |
| getDistance:function(pos){ | |
| var centerX = this.centerX; | |
| var centerY = this.centerY; | |
| //get the distance | |
| var xs = 0; | |
| var ys = 0; | |
| xs = pos.x - centerX; | |
| xs = xs * xs; | |
| ys = pos.y - centerY; | |
| ys = ys * ys; | |
| //return distance | |
| return Math.sqrt( xs + ys ); | |
| }, | |
| getClosestHandle:function(x,y){ | |
| var previousDist=999999999; | |
| var handle=0; | |
| var handles = this.handles; | |
| for(i=0;i<handles.length;i++){ | |
| dx = mouseX - handles[i].x; | |
| dy = mouseY - handles[i].y; | |
| dist = Math.sqrt((dx*dx) + (dy*dy)); | |
| if(dist<previousDist){ | |
| previousDist=dist; | |
| handle=i; | |
| } | |
| } | |
| return handle; | |
| }, | |
| moveHandle:function(handle){ | |
| if(draggable){ | |
| var dist = this.getDistance({x:mouseX,y:mouseY}); | |
| var maxValue=this.checkMaxValue(handle); | |
| var minValue=data[handle]; | |
| var value=Math.max(Math.min(dist/this.radius,maxValue),minValue); | |
| this.dataset[handle]=value; | |
| } | |
| }, | |
| checkMaxValue:function(handle){ | |
| var axisValue=1/this.dataset.length; | |
| var sum=0; | |
| for(i=0;i<this.dataset.length;i++){ | |
| if(i!=handle){ | |
| var value=this.dataset[i]*axisValue; | |
| sum=sum+value | |
| } | |
| } | |
| var maxValue= (perks-sum)/axisValue | |
| return Math.min(maxValue,1); | |
| }, | |
| // Export the current values | |
| getDataset:function(){ | |
| return this.dataset; | |
| } | |
| } | |
| ///////////////////////////////////////// | |
| /* Configuration of the radar */ | |
| var activities={ | |
| radar:{ | |
| init:function(){ | |
| // Define the labels for each axis | |
| dataLabel= ["Apple","Peach","Melon","Banana","Bacon ?"]; | |
| // Set the values for each axis (and the number of axis) | |
| data= [0.5,0.25,0.1,0.335,0.3]; | |
| perks= 1; | |
| radar=new Radar(data,null,null,null); | |
| //Make it interactive | |
| //add the canvas listeners and functions | |
| canvas.addEventListener(startEvent, function mousedown(e){ draggable=true;}, false); | |
| canvas.addEventListener(endEvent, function mouseup(e){ draggable=false;}, false); | |
| canvas.addEventListener(moveEvent, function mousemove(e){ | |
| mouseX = (e.targetTouches)? e.targetTouches[0].layerX - canvas.offsetLeft : e.layerX - canvas.offsetLeft ; | |
| mouseY = (e.targetTouches)? e.targetTouches[0].layerY - canvas.offsetTop : e.layerY - canvas.offsetTop ; | |
| var handle= radar.getClosestHandle(mouseY,mouseY); | |
| radar.moveHandle(handle); | |
| },false); | |
| state="radar"; | |
| }, | |
| loop:function(){ | |
| ctx.fillStyle = "black"; | |
| ctx.fillRect(0,0,ctx.canvas.clientWidth,ctx.canvas.clientHeight); | |
| radar.drawBackground(); | |
| radar.drawArea(); | |
| radar.drawLabel(); | |
| radar.drawHandles(); | |
| } | |
| } | |
| } | |
| ////////////////////// | |
| activities.radar.init(); |
| *{ | |
| margin:0; | |
| padding:0; | |
| background:#000; | |
| } | |
| canvas{ | |
| display:block; | |
| margin:auto; | |
| } |