From bd5bff2cd4d782fa72a529f81a8e188fc2348d77 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Tue, 19 Mar 2013 23:12:20 +0100 Subject: [PATCH 01/47] initial pie labels commit --- Chart.js | 41 ++++++++++++++++++++++++++++++++++++++--- pie.html | 20 ++++++++++++++------ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Chart.js b/Chart.js index 08f72636353..fdeb8beda84 100755 --- a/Chart.js +++ b/Chart.js @@ -244,7 +244,11 @@ var Chart = function(context){ animationEasing : "easeOutBounce", animateRotate : true, animateScale : false, - onAnimationComplete : null + onAnimationComplete : null, + labelFontFamily : "'Arial'", + labelFontStyle : "normal", + labelFontSize : 12, + labelFontColor : "#666", }; var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; @@ -694,12 +698,23 @@ var Chart = function(context){ var segmentTotal = 0; //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var pieRadius = Min([height/2,width/2]) - 5; + var pieRadius = Min([height/2,width/2]) - 5, + labels = []; for (var i=0; i config.labelFontSize) { + ctx.fillStyle = data[i].labelColor || 'black'; + // rotate text, so it perfectly fits in segments + var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, + tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, + tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); + ctx.textAlign = 'right'; + if(textRotation < -Math.PI/2) { + textRotation -= Math.PI; + ctx.textAlign = 'left'; + tX += 20; + } + ctx.translate(tX, tY); + ctx.rotate(-textRotation); + ctx.fillText(data[i].label, 0, 0); + ctx.rotate(textRotation); + ctx.translate(-tX, -tY); + } if(config.segmentShowStroke){ ctx.lineWidth = config.segmentStrokeWidth; diff --git a/pie.html b/pie.html index dd8371671ef..43170dd3ae3 100644 --- a/pie.html +++ b/pie.html @@ -17,21 +17,29 @@ var pieData = [ { - value: 30, - color:"#F38630" + value : 30, + color : "#F38630", + label : 'Sleep', + labelColor : '#0C8ACF' }, { value : 50, - color : "#E0E4CC" + color : "#E0E4CC", + label : 'Music', + labelColor : '#1F1B33' }, { value : 100, - color : "#69D2E7" + color : "#69D2E7", + label : 'Work', + labelColor : '#A72D19' } - ]; - var myPie = new Chart(document.getElementById("canvas").getContext("2d")).Pie(pieData); + var myPie = new Chart(document.getElementById("canvas").getContext("2d")).Pie(pieData, { + animationSteps: 100, + animationEasing: 'easeInOutQuart' + }); From 8b0cf16acfb9b541620231de62046ec808b3d924 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 09:04:56 +0100 Subject: [PATCH 02/47] basic font styling and fading --- Chart.js | 33 +++++++++++++++++++++++++++++---- pie.html | 9 ++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/Chart.js b/Chart.js index fdeb8beda84..ad77afdc486 100755 --- a/Chart.js +++ b/Chart.js @@ -711,10 +711,8 @@ var Chart = function(context){ }); } } - - ctx.font = config.labelFontStyle + " " + config.labelFontSize+"px " + config.labelFontFamily; ctx.fillStyle = 'black'; - ctx.textBaseline = 'middle'; + ctx.textBaseline = 'base'; animationLoop(config,null,drawPieSegments,ctx); @@ -741,7 +739,9 @@ var Chart = function(context){ ctx.fill(); if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) { - ctx.fillStyle = data[i].labelColor || 'black'; + var fontSize = data[i].labelFontSize || config.labelFontSize; + ctx.font = config.labelFontStyle+ " " +fontSize+"px " + config.labelFontFamily; + ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); // rotate text, so it perfectly fits in segments var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, @@ -1462,6 +1462,31 @@ var Chart = function(context){ // Provide some basic currying to the user return data ? fn( data ) : fn; }; + + function getFadeColor(percent, primColor, secColor) { + var pseudoEl = document.createElement('div'), + rgbPrim, + rgbSec; + pseudoEl.style.color = primColor; + document.body.appendChild(pseudoEl); + rgbPrim = window.getComputedStyle(pseudoEl).color; + pseudoEl.style.color = secColor; + rgbSec = window.getComputedStyle(pseudoEl).color; + var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/, + valuesP = regex.exec(rgbPrim), + valuesS = regex.exec(rgbSec), + rP = Math.round(parseFloat(valuesP[1])), + gP = Math.round(parseFloat(valuesP[2])), + bP = Math.round(parseFloat(valuesP[3])), + rS = Math.round(parseFloat(valuesS[1])), + gS = Math.round(parseFloat(valuesS[2])), + bS = Math.round(parseFloat(valuesS[3])), + rCur = parseInt((rP-rS)*percent+rS), + gCur = parseInt((gP-gS)*percent+gS), + bCur = parseInt((bP-bS)*percent+bS); + console.log(rgbPrim, rgbSec, "rgb("+rCur+','+gCur+','+bCur+')'); + return "rgb("+rCur+','+gCur+','+bCur+')'; + } } diff --git a/pie.html b/pie.html index 43170dd3ae3..2584da5673a 100644 --- a/pie.html +++ b/pie.html @@ -20,19 +20,22 @@ value : 30, color : "#F38630", label : 'Sleep', - labelColor : '#0C8ACF' + labelColor : 'white', + labelFontSize : '16' }, { value : 50, color : "#E0E4CC", label : 'Music', - labelColor : '#1F1B33' + labelColor : '#444', + labelFontSize : '16' }, { value : 100, color : "#69D2E7", label : 'Work', - labelColor : '#A72D19' + labelColor : 'black', + labelFontSize : '16' } ]; From f076b16754934fc331a7d822b1404921ff33a477 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 09:20:05 +0100 Subject: [PATCH 03/47] Removed unnecessary lines --- Chart.js | 11 +---------- pie.html | 4 ++-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Chart.js b/Chart.js index ad77afdc486..3ae1ac61e61 100755 --- a/Chart.js +++ b/Chart.js @@ -698,18 +698,10 @@ var Chart = function(context){ var segmentTotal = 0; //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var pieRadius = Min([height/2,width/2]) - 5, - labels = []; + var pieRadius = Min([height/2,width/2]) - 5; for (var i=0; i From d7faabb721a1336d452131230f655b7c706f8bad Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 10:04:14 +0100 Subject: [PATCH 04/47] added em/%/px support to labelFontSize with px as fallback --- Chart.js | 7 +++++-- pie.html | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Chart.js b/Chart.js index 3ae1ac61e61..b948cc1152a 100755 --- a/Chart.js +++ b/Chart.js @@ -731,8 +731,11 @@ var Chart = function(context){ ctx.fill(); if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) { - var fontSize = data[i].labelFontSize || config.labelFontSize; - ctx.font = config.labelFontStyle+ " " +fontSize+"px " + config.labelFontFamily; + var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; + if(fontSize.match(/^[0-9]+$/g) != null) { + fontSize = fontSize+'px'; + } + ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); // rotate text, so it perfectly fits in segments var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, diff --git a/pie.html b/pie.html index 158f769859f..d841f2ec9a6 100644 --- a/pie.html +++ b/pie.html @@ -21,21 +21,21 @@ color : "#F38630", label : 'Sleep', labelColor : 'white', - labelFontSize : '16' + labelFontSize : '10px' }, { value : 50, color : "#E0E4CC", label : 'Music', labelColor : '#444', - labelFontSize : '16' + labelFontSize : '1.4em' }, { value : 100, color : "#69D2E7", label : 'Work', labelColor : 'rgb(0,0,0)', - labelFontSize : '16' + labelFontSize : '175%' } ]; From d34947ed7caa54ea6523894f0eb01b4ccf485c90 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 15:06:16 +0100 Subject: [PATCH 05/47] Basic tooltips for line and bar charts --- Chart.js | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- pie.html | 2 +- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/Chart.js b/Chart.js index b948cc1152a..d6f3e5abb3d 100755 --- a/Chart.js +++ b/Chart.js @@ -138,9 +138,59 @@ var Chart = function(context){ } }; + var tooltips = []; + + var Tooltip = function(ctx, x, y, width, height, data, type) { + this.ctx = ctx; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.data = data; + this.isRendered = false; + + this.inRange = function(x,y) { + return (x >= this.x && x <= this.x+this.width) && + (y >= this.y && y <= this.y+this.height); + } + + this.render = function() { + if(!this.isRendered) { + var posX = this.x+this.width; + if(posX + ctx.measureText(this.data).width > ctx.canvas.width) { + posX = this.x-ctx.measureText(this.data).width; + } + this.ctx.fillStyle = 'rgba(0,0,0,0.6)'; + this.ctx.fillRect(posX, this.y, this.ctx.measureText(this.data).width+10, 24); + this.ctx.fillStyle = 'white'; + this.ctx.textAlign = 'center'; + this.ctx.textBaseline = 'middle'; + this.ctx.fillText(data, posX+(this.ctx.measureText(this.data).width+10)/2, this.y+12); + this.isRendered = true; + } + } + } + //Variables global to the chart var width = context.canvas.width; var height = context.canvas.height; + var savedState = null; + + context.canvas.onmousemove = function(e) { + savedState = savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : savedState; + var rendered = 0; + for(var i in tooltips) { + if(tooltips[i].inRange(e.x, e.y)) { + tooltips[i].render(); + rendered++; + } else { + tooltips[i].isRendered = false; + } + } + if(rendered == 0) { + context.putImageData(savedState,0,0); + } + } //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. @@ -842,7 +892,7 @@ var Chart = function(context){ scaleHop = Math.floor(scaleHeight/calculatedScale.steps); calculateXAxisSize(); - animationLoop(config,drawScale,drawLines,ctx); + animationLoop(config,drawScale,drawLines,ctx); function drawLines(animPc){ for (var i=0; i Date: Wed, 20 Mar 2013 15:11:03 +0100 Subject: [PATCH 06/47] removed console logs and test rects --- Chart.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Chart.js b/Chart.js index d6f3e5abb3d..bc70b606a7f 100755 --- a/Chart.js +++ b/Chart.js @@ -923,7 +923,6 @@ var Chart = function(context){ 'Line' ) ); - console.log(tooltips); } } ctx.stroke(); @@ -1172,12 +1171,6 @@ var Chart = function(context){ height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2), y = xAxisPosY-height, width = barWidth; - var fS = ctx.fillStyle; - ctx.fillStyle = 'black'; - ctx.fillRect(barOffset, xAxisPosY-calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2), barWidth, 1); - ctx.fillStyle = fS; - - console.log(barOffset,xAxisPosY,barWidth, -calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2)); tooltips.push( new Tooltip( ctx, @@ -1189,7 +1182,6 @@ var Chart = function(context){ 'Line' ) ); - console.log(tooltips); } } } From 33b36b15c35ae5b9ed273eabdc1eeee0f9a400b8 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 17:36:24 +0100 Subject: [PATCH 07/47] fixed tooltip position --- Chart.js | 28 +++++++++++++++------------- bar.html | 1 + line.html | 1 + 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Chart.js b/Chart.js index bc70b606a7f..caa509aec86 100755 --- a/Chart.js +++ b/Chart.js @@ -177,18 +177,20 @@ var Chart = function(context){ var savedState = null; context.canvas.onmousemove = function(e) { - savedState = savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : savedState; - var rendered = 0; - for(var i in tooltips) { - if(tooltips[i].inRange(e.x, e.y)) { - tooltips[i].render(); - rendered++; - } else { - tooltips[i].isRendered = false; + if(tooltips.length > 0) { + savedState = savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : savedState; + var rendered = 0; + for(var i in tooltips) { + if(tooltips[i].inRange(e.x-context.canvas.offsetLeft, e.y-context.canvas.offsetTop)) { + tooltips[i].render(); + rendered++; + } else { + tooltips[i].isRendered = false; + } + } + if(rendered == 0) { + context.putImageData(savedState,0,0); } - } - if(rendered == 0) { - context.putImageData(savedState,0,0); } } @@ -915,8 +917,8 @@ var Chart = function(context){ tooltips.push( new Tooltip( ctx, - xPos(j), - yPos(i,j), + xPos(j)-10, + yPos(i,j)-10, 20, 20, data.labels[j]+": "+data.datasets[i].data[j], diff --git a/bar.html b/bar.html index 08a8b292622..bf513ad9041 100644 --- a/bar.html +++ b/bar.html @@ -10,6 +10,7 @@ +

Bar with lines

diff --git a/line.html b/line.html index 1dd08d48d2c..46b377e369a 100644 --- a/line.html +++ b/line.html @@ -10,6 +10,7 @@ +

Line chart with tooltips

From 156728378ec3a1a7cfa952be9b1065567036fe17 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 17:52:43 +0100 Subject: [PATCH 08/47] fixed line chart tooltip trigger area --- Chart.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Chart.js b/Chart.js index caa509aec86..d16bb8383bc 100755 --- a/Chart.js +++ b/Chart.js @@ -138,7 +138,18 @@ var Chart = function(context){ } }; - var tooltips = []; + var tooltips = [], + tooltipOptions = { + background: 'rgba(0,0,0,0.6)', + fontColor: 'white', + fontSize: '12px', + padding: { + top: 10, + right: 10, + bottom: 10, + left: 10 + } + }; var Tooltip = function(ctx, x, y, width, height, data, type) { this.ctx = ctx; @@ -156,16 +167,18 @@ var Chart = function(context){ this.render = function() { if(!this.isRendered) { - var posX = this.x+this.width; + var posX = this.x+this.width, + rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; if(posX + ctx.measureText(this.data).width > ctx.canvas.width) { posX = this.x-ctx.measureText(this.data).width; } - this.ctx.fillStyle = 'rgba(0,0,0,0.6)'; - this.ctx.fillRect(posX, this.y, this.ctx.measureText(this.data).width+10, 24); - this.ctx.fillStyle = 'white'; + console.log(tooltipOptions); + this.ctx.fillStyle = tooltipOptions.background; + this.ctx.fillRect(posX, this.y, rectWidth, 24); + this.ctx.fillStyle = tooltipOptions.fontColor; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; - this.ctx.fillText(data, posX+(this.ctx.measureText(this.data).width+10)/2, this.y+12); + this.ctx.fillText(data, posX+rectWidth/2, this.y+12); this.isRendered = true; } } @@ -911,16 +924,17 @@ var Chart = function(context){ ctx.lineTo(xPos(j),yPos(i,j)); } } + var pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10; for(var j = 0; j < data.datasets[i].data.length; j++) { if(animPc == 1) { // register tooltips tooltips.push( new Tooltip( ctx, - xPos(j)-10, - yPos(i,j)-10, - 20, - 20, + xPos(j)-pointRadius, + yPos(i,j)-pointRadius, + 2*pointRadius, + 2*pointRadius, data.labels[j]+": "+data.datasets[i].data[j], 'Line' ) From 911e0bb25ed9614ad5679e14c26ee0f57d4e1743 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 18:08:35 +0100 Subject: [PATCH 09/47] added configurable tooltip options to chart object --- Chart.js | 29 +++++++++++++++-------------- sixup.html | 5 ++++- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Chart.js b/Chart.js index d16bb8383bc..f1d354ca721 100755 --- a/Chart.js +++ b/Chart.js @@ -1,5 +1,5 @@ //Define the global Chart Variable as a class. -var Chart = function(context){ +var Chart = function(context, tooltipOptions){ var chart = this; @@ -139,7 +139,7 @@ var Chart = function(context){ }; var tooltips = [], - tooltipOptions = { + tooltipDefaults = { background: 'rgba(0,0,0,0.6)', fontColor: 'white', fontSize: '12px', @@ -149,7 +149,8 @@ var Chart = function(context){ bottom: 10, left: 10 } - }; + }, + tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; var Tooltip = function(ctx, x, y, width, height, data, type) { this.ctx = ctx; @@ -167,18 +168,21 @@ var Chart = function(context){ this.render = function() { if(!this.isRendered) { - var posX = this.x+this.width, + var posX = this.x+this.width/2, + posY = this.y+this.height/2, rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; - if(posX + ctx.measureText(this.data).width > ctx.canvas.width) { - posX = this.x-ctx.measureText(this.data).width; + if(posX + rectWidth > ctx.canvas.width) { + posX -= rectWidth; + } + if(posY + 24 > ctx.canvas.height) { + posY -= 24; } - console.log(tooltipOptions); this.ctx.fillStyle = tooltipOptions.background; - this.ctx.fillRect(posX, this.y, rectWidth, 24); + this.ctx.fillRect(posX, posY, rectWidth, 24); this.ctx.fillStyle = tooltipOptions.fontColor; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; - this.ctx.fillText(data, posX+rectWidth/2, this.y+12); + this.ctx.fillText(data, posX+rectWidth/2, posY+12); this.isRendered = true; } } @@ -928,6 +932,7 @@ var Chart = function(context){ for(var j = 0; j < data.datasets[i].data.length; j++) { if(animPc == 1) { // register tooltips + var ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; tooltips.push( new Tooltip( ctx, @@ -935,7 +940,7 @@ var Chart = function(context){ yPos(i,j)-pointRadius, 2*pointRadius, 2*pointRadius, - data.labels[j]+": "+data.datasets[i].data[j], + ttData, 'Line' ) ); @@ -1582,10 +1587,6 @@ var Chart = function(context){ bCur = parseInt((bP-bS)*percent+bS); return "rgb("+rCur+','+gCur+','+bCur+')'; } - - function initTooltip(ctx, x, y, data) { - - } } diff --git a/sixup.html b/sixup.html index 1d36f273f98..c77a662a8f3 100644 --- a/sixup.html +++ b/sixup.html @@ -144,7 +144,10 @@ }; new Chart(document.getElementById("doughnut").getContext("2d")).Doughnut(doughnutData); - new Chart(document.getElementById("line").getContext("2d")).Line(lineChartData); + new Chart(document.getElementById("line").getContext("2d"), { + background: 'rgba(100,0,0,0.7)', + fontColor: 'black' + }).Line(lineChartData); new Chart(document.getElementById("radar").getContext("2d")).Radar(radarChartData); new Chart(document.getElementById("polarArea").getContext("2d")).PolarArea(chartData); new Chart(document.getElementById("bar").getContext("2d")).Bar(barChartData); From 81a57d0f40ba81debe28a840512433a029ed0acf Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 20:01:17 +0100 Subject: [PATCH 10/47] fixed radar rotation issue --- Chart.js | 39 ++++++++++++++++++++++++++++++++------- radar.html | 2 +- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/Chart.js b/Chart.js index f1d354ca721..bf97dc33ad0 100755 --- a/Chart.js +++ b/Chart.js @@ -598,16 +598,41 @@ var Chart = function(context, tooltipOptions){ ctx.save(); //translate to the centre of the canvas. ctx.translate(width/2,height/2); - ctx.rotate(rotationDegree); //We accept multiple data sets for radar charts, so show loop through each set for (var i=0; i Date: Wed, 20 Mar 2013 20:05:57 +0100 Subject: [PATCH 11/47] fixed radar rotation issue --- Chart.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Chart.js b/Chart.js index b948cc1152a..ba9dab1887f 100755 --- a/Chart.js +++ b/Chart.js @@ -528,8 +528,7 @@ var Chart = function(context){ ctx.save(); //translate to the centre of the canvas. - ctx.translate(width/2,height/2); - ctx.rotate(rotationDegree); + ctx.translate(width/2,height/2); //We accept multiple data sets for radar charts, so show loop through each set for (var i=0; i Date: Wed, 20 Mar 2013 20:28:53 +0100 Subject: [PATCH 12/47] optimized tooltip registration --- Chart.js | 84 +++++++++++++++++++++++++----------------------------- radar.html | 9 ++++-- 2 files changed, 46 insertions(+), 47 deletions(-) diff --git a/Chart.js b/Chart.js index bf97dc33ad0..a1f58e4631f 100755 --- a/Chart.js +++ b/Chart.js @@ -148,10 +148,30 @@ var Chart = function(context, tooltipOptions){ right: 10, bottom: 10, left: 10 + }, + offset: { + left: 0, + top: 0 + }, + stroke: { + width: 0, + color: '#000' } }, tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; + function registerTooltip(ctx,x,y,width,height,data,type) { + tooltips.push(new Tooltip( + ctx, + x, + y, + width, + height, + data, + type + )); + } + var Tooltip = function(ctx, x, y, width, height, data, type) { this.ctx = ctx; this.x = x; @@ -168,8 +188,8 @@ var Chart = function(context, tooltipOptions){ this.render = function() { if(!this.isRendered) { - var posX = this.x+this.width/2, - posY = this.y+this.height/2, + var posX = this.x+this.width/2+tooltipOptions.offset.left, + posY = this.y+this.height/2+tooltipOptions.offset.top, rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; if(posX + rectWidth > ctx.canvas.width) { posX -= rectWidth; @@ -179,6 +199,11 @@ var Chart = function(context, tooltipOptions){ } this.ctx.fillStyle = tooltipOptions.background; this.ctx.fillRect(posX, posY, rectWidth, 24); + if(tooltipOptions.stroke.width > 0) { + this.ctx.fillStyle = tooltipOptions.stroke.color; + this.ctx.lineWidth = tooltipOptions.stroke.width; + this.ctx.strokeRect(posX, posY, rectWidth, 24); + } this.ctx.fillStyle = tooltipOptions.fontColor; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; @@ -605,16 +630,10 @@ var Chart = function(context, tooltipOptions){ ctx.moveTo(0,animationDecimal*(-1*offset)); if(animationDecimal == 1) { var curX = width/2+offset*Math.cos(0-Math.PI/2), - curY = height/2+offset*Math.sin(0-Math.PI/2); - tooltips.push(new Tooltip( - ctx, - curX-5, - curY-5, - 10, - 10, - data.datasets[i].data[0], - 'Radar' - )); + curY = height/2+offset*Math.sin(0-Math.PI/2), + pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10, + ttData = data.labels[0].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[0] : data.datasets[i].data[0]; + registerTooltip(ctx,curX-pointRadius,curY-pointRadius,2*pointRadius,2*pointRadius,ttData,'Radar'); } for (var j=1; j From e5e746d9b808c4ae8eedaed3df3fe0e363d3ddfc Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 20 Mar 2013 20:35:36 +0100 Subject: [PATCH 13/47] fixd radar tooltip typo --- Chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Chart.js b/Chart.js index a1f58e4631f..0e16669ae55 100755 --- a/Chart.js +++ b/Chart.js @@ -643,7 +643,7 @@ var Chart = function(context, tooltipOptions){ var curX = width/2+offset*Math.cos(j*rotationDegree-Math.PI/2), curY = height/2+offset*Math.sin(j*rotationDegree-Math.PI/2), pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10, - ttData = data.labels[j].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; + ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; registerTooltip(ctx,curX-pointRadius,curY-pointRadius,2*pointRadius,2*pointRadius,ttData,'Radar'); } } From e73f76137912a69e651aede88a157af9e235ed82 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sat, 23 Mar 2013 16:04:00 +0100 Subject: [PATCH 14/47] implemented pie chart tooltips --- Chart.js | 3119 +++++++++++++++++++++++++++--------------------------- pie.html | 3 +- 2 files changed, 1578 insertions(+), 1544 deletions(-) diff --git a/Chart.js b/Chart.js index 0e16669ae55..4058b024055 100755 --- a/Chart.js +++ b/Chart.js @@ -1,1454 +1,1487 @@ //Define the global Chart Variable as a class. var Chart = function(context, tooltipOptions){ - var chart = this; - - - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ - - var animationOptions = { - linear : function (t){ - return t; - }, - easeInQuad: function (t) { - return t*t; - }, - easeOutQuad: function (t) { - return -1 *t*(t-2); - }, - easeInOutQuad: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t; - return -1/2 * ((--t)*(t-2) - 1); - }, - easeInCubic: function (t) { - return t*t*t; - }, - easeOutCubic: function (t) { - return 1*((t=t/1-1)*t*t + 1); - }, - easeInOutCubic: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t; - return 1/2*((t-=2)*t*t + 2); - }, - easeInQuart: function (t) { - return t*t*t*t; - }, - easeOutQuart: function (t) { - return -1 * ((t=t/1-1)*t*t*t - 1); - }, - easeInOutQuart: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t*t; - return -1/2 * ((t-=2)*t*t*t - 2); - }, - easeInQuint: function (t) { - return 1*(t/=1)*t*t*t*t; - }, - easeOutQuint: function (t) { - return 1*((t=t/1-1)*t*t*t*t + 1); - }, - easeInOutQuint: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; - return 1/2*((t-=2)*t*t*t*t + 2); - }, - easeInSine: function (t) { - return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; - }, - easeOutSine: function (t) { - return 1 * Math.sin(t/1 * (Math.PI/2)); - }, - easeInOutSine: function (t) { - return -1/2 * (Math.cos(Math.PI*t/1) - 1); - }, - easeInExpo: function (t) { - return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); - }, - easeOutExpo: function (t) { - return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); - }, - easeInOutExpo: function (t) { - if (t==0) return 0; - if (t==1) return 1; - if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); - return 1/2 * (-Math.pow(2, -10 * --t) + 2); - }, - easeInCirc: function (t) { - if (t>=1) return t; - return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); - }, - easeOutCirc: function (t) { - return 1 * Math.sqrt(1 - (t=t/1-1)*t); - }, - easeInOutCirc: function (t) { - if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); - return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); - }, - easeInElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); - }, - easeOutElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; - }, - easeInOutElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); - return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; - }, - easeInBack: function (t) { - var s = 1.70158; - return 1*(t/=1)*t*((s+1)*t - s); - }, - easeOutBack: function (t) { - var s = 1.70158; - return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); - }, - easeInOutBack: function (t) { - var s = 1.70158; - if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); - return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); - }, - easeInBounce: function (t) { - return 1 - animationOptions.easeOutBounce (1-t); - }, - easeOutBounce: function (t) { - if ((t/=1) < (1/2.75)) { - return 1*(7.5625*t*t); - } else if (t < (2/2.75)) { - return 1*(7.5625*(t-=(1.5/2.75))*t + .75); - } else if (t < (2.5/2.75)) { - return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); - } else { - return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); - } - }, - easeInOutBounce: function (t) { - if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; - return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; - } - }; + var chart = this; + + + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + + var animationOptions = { + linear : function (t){ + return t; + }, + easeInQuad: function (t) { + return t*t; + }, + easeOutQuad: function (t) { + return -1 *t*(t-2); + }, + easeInOutQuad: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t; + return -1/2 * ((--t)*(t-2) - 1); + }, + easeInCubic: function (t) { + return t*t*t; + }, + easeOutCubic: function (t) { + return 1*((t=t/1-1)*t*t + 1); + }, + easeInOutCubic: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t; + return 1/2*((t-=2)*t*t + 2); + }, + easeInQuart: function (t) { + return t*t*t*t; + }, + easeOutQuart: function (t) { + return -1 * ((t=t/1-1)*t*t*t - 1); + }, + easeInOutQuart: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t; + return -1/2 * ((t-=2)*t*t*t - 2); + }, + easeInQuint: function (t) { + return 1*(t/=1)*t*t*t*t; + }, + easeOutQuint: function (t) { + return 1*((t=t/1-1)*t*t*t*t + 1); + }, + easeInOutQuint: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; + return 1/2*((t-=2)*t*t*t*t + 2); + }, + easeInSine: function (t) { + return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; + }, + easeOutSine: function (t) { + return 1 * Math.sin(t/1 * (Math.PI/2)); + }, + easeInOutSine: function (t) { + return -1/2 * (Math.cos(Math.PI*t/1) - 1); + }, + easeInExpo: function (t) { + return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); + }, + easeOutExpo: function (t) { + return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); + }, + easeInOutExpo: function (t) { + if (t==0) return 0; + if (t==1) return 1; + if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); + return 1/2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function (t) { + if (t>=1) return t; + return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); + }, + easeOutCirc: function (t) { + return 1 * Math.sqrt(1 - (t=t/1-1)*t); + }, + easeInOutCirc: function (t) { + if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); + return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); + }, + easeInElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + }, + easeOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; + }, + easeInOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; + }, + easeInBack: function (t) { + var s = 1.70158; + return 1*(t/=1)*t*((s+1)*t - s); + }, + easeOutBack: function (t) { + var s = 1.70158; + return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); + }, + easeInOutBack: function (t) { + var s = 1.70158; + if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); + return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); + }, + easeInBounce: function (t) { + return 1 - animationOptions.easeOutBounce (1-t); + }, + easeOutBounce: function (t) { + if ((t/=1) < (1/2.75)) { + return 1*(7.5625*t*t); + } else if (t < (2/2.75)) { + return 1*(7.5625*(t-=(1.5/2.75))*t + .75); + } else if (t < (2.5/2.75)) { + return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); + } else { + return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); + } + }, + easeInOutBounce: function (t) { + if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; + return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; + } + }; - var tooltips = [], - tooltipDefaults = { - background: 'rgba(0,0,0,0.6)', - fontColor: 'white', - fontSize: '12px', - padding: { - top: 10, - right: 10, - bottom: 10, - left: 10 - }, - offset: { - left: 0, - top: 0 - }, - stroke: { - width: 0, - color: '#000' - } - }, - tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; + this.tooltips = [], + tooltipDefaults = { + background: 'rgba(0,0,0,0.6)', + fontFamily : "'Arial'", + fontStyle : "normal", + fontColor: 'white', + fontSize: '12px', + padding: { + top: 10, + right: 10, + bottom: 10, + left: 10 + }, + offset: { + left: 0, + top: 0 + }, + stroke: { + width: 0, + color: '#000' + } + }, + tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; - function registerTooltip(ctx,x,y,width,height,data,type) { - tooltips.push(new Tooltip( - ctx, - x, - y, - width, - height, - data, - type - )); - } + function registerTooltip(ctx,areaObj,data,type) { + chart.tooltips.push(new Tooltip( + ctx, + areaObj, + data, + type + )); + } - var Tooltip = function(ctx, x, y, width, height, data, type) { - this.ctx = ctx; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - this.data = data; - this.isRendered = false; + var Tooltip = function(ctx, areaObj, data, type) { + this.ctx = ctx; + this.areaObj = areaObj; + this.data = data; + this.savedState = null; + this.x = null; + this.y = null; - this.inRange = function(x,y) { - return (x >= this.x && x <= this.x+this.width) && - (y >= this.y && y <= this.y+this.height); - } + this.inRange = function(x,y) { + if(this.areaObj.type) { + switch(this.areaObj.type) { + case 'rect': + return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) && + (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height); + break; + case 'circle': + return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2)); + break; + case 'shape': + var poly = this.areaObj.points; + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y)) + && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x) + && (c = !c); + return c; + break; + } + } + } - this.render = function() { - if(!this.isRendered) { - var posX = this.x+this.width/2+tooltipOptions.offset.left, - posY = this.y+this.height/2+tooltipOptions.offset.top, - rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; - if(posX + rectWidth > ctx.canvas.width) { - posX -= rectWidth; - } - if(posY + 24 > ctx.canvas.height) { - posY -= 24; - } - this.ctx.fillStyle = tooltipOptions.background; - this.ctx.fillRect(posX, posY, rectWidth, 24); - if(tooltipOptions.stroke.width > 0) { - this.ctx.fillStyle = tooltipOptions.stroke.color; - this.ctx.lineWidth = tooltipOptions.stroke.width; - this.ctx.strokeRect(posX, posY, rectWidth, 24); - } - this.ctx.fillStyle = tooltipOptions.fontColor; - this.ctx.textAlign = 'center'; - this.ctx.textBaseline = 'middle'; - this.ctx.fillText(data, posX+rectWidth/2, posY+12); - this.isRendered = true; - } - } - } + this.render = function(x,y) { + if(this.savedState == null) { + this.ctx.putImageData(chart.savedState,0,0); + this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); + } + this.ctx.putImageData(this.savedState,0,0); + if(this.x != x || this.y != y) { + var posX = x+tooltipOptions.offset.left, + posY = y+tooltipOptions.offset.top, + rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; + if(posX + rectWidth > ctx.canvas.width) { + posX -= rectWidth; + } + if(posY + 24 > ctx.canvas.height) { + posY -= 24; + } + this.ctx.fillStyle = tooltipOptions.background; + this.ctx.fillRect(posX, posY, rectWidth, 24); + if(tooltipOptions.stroke.width > 0) { + this.ctx.fillStyle = tooltipOptions.stroke.color; + this.ctx.lineWidth = tooltipOptions.stroke.width; + this.ctx.strokeRect(posX, posY, rectWidth, 24); + } + this.ctx.font = tooltipOptions.fontStyle+ " " +tooltipOptions.fontSize+" " + tooltipOptions.fontFamily; + this.ctx.fillStyle = tooltipOptions.fontColor; + this.ctx.textAlign = 'center'; + this.ctx.textBaseline = 'middle'; + this.ctx.fillText(data, posX+rectWidth/2, posY+12); + this.x = x; + this.y = y; + } + } + } - //Variables global to the chart - var width = context.canvas.width; - var height = context.canvas.height; - var savedState = null; + //Variables global to the chart + var width = context.canvas.width, + height = context.canvas.height; - context.canvas.onmousemove = function(e) { - if(tooltips.length > 0) { - savedState = savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : savedState; - var rendered = 0; - for(var i in tooltips) { - if(tooltips[i].inRange(e.x-context.canvas.offsetLeft, e.y-context.canvas.offsetTop)) { - tooltips[i].render(); - rendered++; - } else { - tooltips[i].isRendered = false; - } - } - if(rendered == 0) { - context.putImageData(savedState,0,0); - } - } - } + this.savedState = null; + context.canvas.onmousemove = function(e) { + if(chart.tooltips.length > 0) { + chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState; + var rendered = 0; + for(var i in chart.tooltips) { + var mx = e.x-context.canvas.offsetLeft, + my = e.y-context.canvas.offsetTop; + if(chart.tooltips[i].inRange(mx,my)) { + chart.tooltips[i].render(mx,my); + rendered++; + } + } + if(rendered == 0) { + context.putImageData(chart.savedState,0,0); + } + } + } - //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. - if (window.devicePixelRatio) { - context.canvas.style.width = width + "px"; - context.canvas.style.height = height + "px"; - context.canvas.height = height * window.devicePixelRatio; - context.canvas.width = width * window.devicePixelRatio; - context.scale(window.devicePixelRatio, window.devicePixelRatio); - } - this.PolarArea = function(data,options){ - - chart.PolarArea.defaults = { - scaleOverlay : true, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleShowLine : true, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowLabelBackdrop : true, - scaleBackdropColor : "rgba(255,255,255,0.75)", - scaleBackdropPaddingY : 2, - scaleBackdropPaddingX : 2, - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null - }; - - var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; - - return new PolarArea(data,config,context); - }; + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + if (window.devicePixelRatio) { + context.canvas.style.width = width + "px"; + context.canvas.style.height = height + "px"; + context.canvas.height = height * window.devicePixelRatio; + context.canvas.width = width * window.devicePixelRatio; + context.scale(window.devicePixelRatio, window.devicePixelRatio); + } - this.Radar = function(data,options){ - - chart.Radar.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleShowLine : true, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : false, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowLabelBackdrop : true, - scaleBackdropColor : "rgba(255,255,255,0.75)", - scaleBackdropPaddingY : 2, - scaleBackdropPaddingX : 2, - angleShowLineOut : true, - angleLineColor : "rgba(0,0,0,.1)", - angleLineWidth : 1, - pointLabelFontFamily : "'Arial'", - pointLabelFontStyle : "normal", - pointLabelFontSize : 12, - pointLabelFontColor : "#666", - pointDot : true, - pointDotRadius : 3, - pointDotStrokeWidth : 1, - datasetStroke : true, - datasetStrokeWidth : 2, - datasetFill : true, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null - }; - - var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + this.PolarArea = function(data,options){ + + chart.PolarArea.defaults = { + scaleOverlay : true, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; - return new Radar(data,config,context); - }; - - this.Pie = function(data,options){ - chart.Pie.defaults = { - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null, - labelFontFamily : "'Arial'", - labelFontStyle : "normal", - labelFontSize : 12, - labelFontColor : "#666", - }; + this.Radar = function(data,options){ + + chart.Radar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : false, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + angleShowLineOut : true, + angleLineColor : "rgba(0,0,0,.1)", + angleLineWidth : 1, + pointLabelFontFamily : "'Arial'", + pointLabelFontStyle : "normal", + pointLabelFontSize : 12, + pointLabelFontColor : "#666", + pointDot : true, + pointDotRadius : 3, + pointDotStrokeWidth : 1, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; - var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; - - return new Pie(data,config,context); - }; - - this.Doughnut = function(data,options){ - - chart.Doughnut.defaults = { - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - percentageInnerCutout : 50, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null - }; + return new Radar(data,config,context); + }; + + this.Pie = function(data,options){ + chart.Pie.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null, + labelFontFamily : "'Arial'", + labelFontStyle : "normal", + labelFontSize : 12, + labelFontColor : "#666", + }; - var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; - - return new Doughnut(data,config,context); - - }; + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + + this.Doughnut = function(data,options){ + + chart.Doughnut.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + percentageInnerCutout : 50, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; - this.Line = function(data,options){ - - chart.Line.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowGridLines : true, - scaleGridLineColor : "rgba(0,0,0,.05)", - scaleGridLineWidth : 1, - bezierCurve : true, - pointDot : true, - pointDotRadius : 4, - pointDotStrokeWidth : 2, - datasetStroke : true, - datasetStrokeWidth : 2, - datasetFill : true, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null - }; - var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; - - return new Line(data,config,context); - } - - this.Bar = function(data,options){ - chart.Bar.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowGridLines : true, - scaleGridLineColor : "rgba(0,0,0,.05)", - scaleGridLineWidth : 1, - barShowStroke : true, - barStrokeWidth : 2, - barValueSpacing : 5, - barDatasetSpacing : 1, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null - }; - var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; - - return new Bar(data,config,context); - } - - var clear = function(c){ - c.clearRect(0, 0, width, height); - }; + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; - var PolarArea = function(data,config,ctx){ - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; - - - calculateDrawingSizes(); - - valueBounds = getValueBounds(); + this.Line = function(data,options){ + + chart.Line.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + bezierCurve : true, + pointDot : true, + pointDotRadius : 4, + pointDotStrokeWidth : 2, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; + + return new Line(data,config,context); + } + + this.Bar = function(data,options){ + chart.Bar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + barShowStroke : true, + barStrokeWidth : 2, + barValueSpacing : 5, + barDatasetSpacing : 1, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } + + var clear = function(c){ + c.clearRect(0, 0, width, height); + }; - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + var PolarArea = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); - //Check and set the scale - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i upperValue) {upperValue = data[i].value;} - if (data[i].value < lowerValue) {lowerValue = data[i].value;} - }; + if(config.segmentShowStroke){ + ctx.strokeStyle = config.segmentStrokeColor; + ctx.lineWidth = config.segmentStrokeWidth; + ctx.stroke(); + } + startAngle += rotateAnimation*angleStep; + } + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + - } - } + } + } - var Radar = function (data,config,ctx) { - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; - - //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. - if (!data.labels) data.labels = []; - - calculateDrawingSizes(); + var Radar = function (data,config,ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); - var valueBounds = getValueBounds(); + var valueBounds = getValueBounds(); - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; - //Check and set the scale - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i Math.PI){ - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "left"; - } - - ctx.textBaseline = "middle"; - - ctx.fillText(data.labels[k],opposite,-adjacent); - - } - ctx.restore(); - }; - function calculateDrawingSizes(){ - maxSize = (Min([width,height])/2); + } + for (var k=0; k Math.PI){ + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillText(data.labels[k],opposite,-adjacent); + + } + ctx.restore(); + }; + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); - labelHeight = config.scaleFontSize*2; - - var labelLength = 0; - for (var i=0; ilabelLength) labelLength = textMeasurement; - } - - //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. - maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); - - maxSize -= config.pointLabelFontSize; - maxSize = CapValue(maxSize, null, 0); - scaleHeight = maxSize; - //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. - labelHeight = Default(labelHeight,5); - }; - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - - for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} - if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} - } - } + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + }; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} + } + } - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + - } - } + } + } - var Pie = function(data,config,ctx){ - var segmentTotal = 0; - - //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var pieRadius = Min([height/2,width/2]) - 5; - - for (var i=0; i config.labelFontSize) { - var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; - if(fontSize.match(/^[0-9]+$/g) != null) { - fontSize = fontSize+'px'; - } - ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; - ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); - // rotate text, so it perfectly fits in segments - var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, - tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, - tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); - ctx.textAlign = 'right'; - if(textRotation < -Math.PI/2) { - textRotation -= Math.PI; - ctx.textAlign = 'left'; - tX += 20; - } - ctx.translate(tX, tY); - ctx.rotate(-textRotation); - ctx.fillText(data[i].label, 0, 0); - ctx.rotate(textRotation); - ctx.translate(-tX, -tY); - } - - if(config.segmentShowStroke){ - ctx.lineWidth = config.segmentStrokeWidth; - ctx.strokeStyle = config.segmentStrokeColor; - ctx.stroke(); - } - cumulativeAngle += segmentAngle; - } - } - } + if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) { + var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; + if(fontSize.match(/^[0-9]+$/g) != null) { + fontSize = fontSize+'px'; + } + ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; + ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); + // rotate text, so it perfectly fits in segments + var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, + tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, + tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); + ctx.textAlign = 'right'; + if(textRotation < -Math.PI/2) { + textRotation -= Math.PI; + ctx.textAlign = 'left'; + tX += 20; + } + ctx.translate(tX, tY); + ctx.rotate(-textRotation); + ctx.fillText(data[i].label, 0, 0); + ctx.rotate(textRotation); + ctx.translate(-tX, -tY); + } + if(animationDecimal > 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [{x:width/2,y:height/2}], + pAmount = 50; + points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'Pie'); + } + + if(config.segmentShowStroke){ + ctx.lineWidth = config.segmentStrokeWidth; + ctx.strokeStyle = config.segmentStrokeColor; + ctx.stroke(); + } + cumulativeAngle += segmentAngle; + } + } + } - var Doughnut = function(data,config,ctx){ - var segmentTotal = 0; - - //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var doughnutRadius = Min([height/2,width/2]) - 5; - - var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100); - - for (var i=0; i 0){ - ctx.save(); - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "center"; - } - ctx.fillStyle = config.scaleFontColor; - for (var i=0; i 0){ - ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); - ctx.rotate(-(rotateLabels * (Math.PI/180))); - ctx.fillText(data.labels[i], 0,0); - ctx.restore(); - } - - else{ - ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); - } + for (var j=1; j 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); + } - ctx.beginPath(); - ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); - - //Check i isnt 0, so we dont go over the Y axis twice. - if(config.scaleShowGridLines && i>0){ - ctx.lineWidth = config.scaleGridLineWidth; - ctx.strokeStyle = config.scaleGridLineColor; - ctx.lineTo(yAxisPosX + i * valueHop, 5); - } - else{ - ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); - } - ctx.stroke(); - } - - //Y axis - ctx.lineWidth = config.scaleLineWidth; - ctx.strokeStyle = config.scaleLineColor; - ctx.beginPath(); - ctx.moveTo(yAxisPosX,xAxisPosY+5); - ctx.lineTo(yAxisPosX,5); - ctx.stroke(); - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - for (var j=0; j longestText)? measuredText : longestText; - } - //Add a little extra padding from the y axis - longestText +=10; - } - xAxisLength = width - longestText - widestXLabel; - valueHop = Math.floor(xAxisLength/(data.labels.length-1)); - - yAxisPosX = width-widestXLabel/2-xAxisLength; - xAxisPosY = scaleHeight + config.scaleFontSize/2; - } - function calculateDrawingSizes(){ - maxSize = height; + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + if(config.scaleShowGridLines && i>0){ + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + i * valueHop, 5); + } + else{ + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); + } + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length-1)); + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; - //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. - ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; - widestXLabel = 1; - for (var i=0; i widestXLabel)? textLength : widestXLabel; - } - if (width/data.labels.length < widestXLabel){ - rotateLabels = 45; - if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ - rotateLabels = 90; - maxSize -= widestXLabel; - } - else{ - maxSize -= Math.sin(rotateLabels) * widestXLabel; - } - } - else{ - maxSize -= config.scaleFontSize; - } - - //Add a little padding between the x line and the text - maxSize -= 5; - - - labelHeight = config.scaleFontSize; - - maxSize -= labelHeight; - //Set 5 pixels greater than the font size to allow for a little padding from the X axis. - - scaleHeight = maxSize; - - //Then get the area above we can safely draw on. - - } - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; - } - }; - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } - - } - - var Bar = function(data,config,ctx){ - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; - - calculateDrawingSizes(); - - valueBounds = getValueBounds(); - //Check and set the scale - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i 0){ - ctx.save(); - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "center"; - } - ctx.fillStyle = config.scaleFontColor; - for (var i=0; i 0){ - ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); - ctx.rotate(-(rotateLabels * (Math.PI/180))); - ctx.fillText(data.labels[i], 0,0); - ctx.restore(); - } - - else{ - ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); - } + if(animPc == 1) { + // register tooltips + var x = barOffset, + height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2), + y = xAxisPosY-height, + width = barWidth, + ttData = data.labels[j] != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; + registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},ttData,'Bar'); + } + } + } + + } + function drawScale(){ + //X axis line + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(width-widestXLabel/2+5,xAxisPosY); + ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY); + ctx.stroke(); + + + if (rotateLabels > 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); + } - ctx.beginPath(); - ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); - - //Check i isnt 0, so we dont go over the Y axis twice. - ctx.lineWidth = config.scaleGridLineWidth; - ctx.strokeStyle = config.scaleGridLineColor; - ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); - ctx.stroke(); - } - - //Y axis - ctx.lineWidth = config.scaleLineWidth; - ctx.strokeStyle = config.scaleLineColor; - ctx.beginPath(); - ctx.moveTo(yAxisPosX,xAxisPosY+5); - ctx.lineTo(yAxisPosX,5); - ctx.stroke(); - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - for (var j=0; j longestText)? measuredText : longestText; - } - //Add a little extra padding from the y axis - longestText +=10; - } - xAxisLength = width - longestText - widestXLabel; - valueHop = Math.floor(xAxisLength/(data.labels.length)); - - barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; - - yAxisPosX = width-widestXLabel/2-xAxisLength; - xAxisPosY = scaleHeight + config.scaleFontSize/2; - } - function calculateDrawingSizes(){ - maxSize = height; + ctx.beginPath(); + ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length)); + + barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; - //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. - ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; - widestXLabel = 1; - for (var i=0; i widestXLabel)? textLength : widestXLabel; - } - if (width/data.labels.length < widestXLabel){ - rotateLabels = 45; - if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ - rotateLabels = 90; - maxSize -= widestXLabel; - } - else{ - maxSize -= Math.sin(rotateLabels) * widestXLabel; - } - } - else{ - maxSize -= config.scaleFontSize; - } - - //Add a little padding between the x line and the text - maxSize -= 5; - - - labelHeight = config.scaleFontSize; - - maxSize -= labelHeight; - //Set 5 pixels greater than the font size to allow for a little padding from the X axis. - - scaleHeight = maxSize; - - //Then get the area above we can safely draw on. - - } - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; - } - }; - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } - } - - function calculateOffset(val,calculatedScale,scaleHop){ - var outerValue = calculatedScale.steps * calculatedScale.stepValue; - var adjustedValue = val - calculatedScale.graphMin; - var scalingFactor = CapValue(adjustedValue/outerValue,1,0); - return (scaleHop*calculatedScale.steps) * scalingFactor; - } - - function animationLoop(config,drawScale,drawData,ctx){ - var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, - easingFunction = animationOptions[config.animationEasing], - percentAnimComplete =(config.animation)? 0 : 1; - - - - if (typeof drawScale !== "function") drawScale = function(){}; - - requestAnimFrame(animLoop); - - function animateFrame(){ - var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; - clear(ctx); - if(config.scaleOverlay){ - drawData(easeAdjustedAnimationPercent); - drawScale(); - } else { - drawScale(); - drawData(easeAdjustedAnimationPercent); - } - } - function animLoop(){ - //We need to check if the animation is incomplete (less than 1), or complete (1). - percentAnimComplete += animFrameAmount; - animateFrame(); - //Stop the loop continuing forever - if (percentAnimComplete <= 1){ - requestAnimFrame(animLoop); - } - else{ - if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); - } - - } - - } + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + function calculateOffset(val,calculatedScale,scaleHop){ + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue/outerValue,1,0); + return (scaleHop*calculatedScale.steps) * scalingFactor; + } + + function animationLoop(config,drawScale,drawData,ctx){ + var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, + easingFunction = animationOptions[config.animationEasing], + percentAnimComplete =(config.animation)? 0 : 1; + + + + if (typeof drawScale !== "function") drawScale = function(){}; + + requestAnimFrame(animLoop); + + function animateFrame(){ + var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; + clear(ctx); + if(config.scaleOverlay){ + drawData(easeAdjustedAnimationPercent); + drawScale(); + } else { + drawScale(); + drawData(easeAdjustedAnimationPercent); + } + } + function animLoop(){ + //We need to check if the animation is incomplete (less than 1), or complete (1). + percentAnimComplete += animFrameAmount; + animateFrame(); + //Stop the loop continuing forever + if (percentAnimComplete <= 1){ + requestAnimFrame(animLoop); + } + else{ + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); + } + + } + + } - //Declare global functions to be called within this namespace here. - - - // shim layer with setTimeout fallback - var requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - })(); + //Declare global functions to be called within this namespace here. + + + // shim layer with setTimeout fallback + var requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); - function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ - var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; - - valueRange = maxValue - minValue; - - rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + + valueRange = maxValue - minValue; + + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); - graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); @@ -1456,156 +1489,156 @@ var Chart = function(context, tooltipOptions){ stepValue = Math.pow(10, rangeOrderOfMagnitude); - numberOfSteps = Math.round(graphRange / stepValue); - - //Compare number of steps to the max and min for that size graph, and add in half steps if need be. - while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { - if (numberOfSteps < minSteps){ - stepValue /= 2; - numberOfSteps = Math.round(graphRange/stepValue); - } - else{ - stepValue *=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - }; - + numberOfSteps = Math.round(graphRange / stepValue); + + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; + - - //Create an array of all the labels by interpolating the string. - - var labels = []; - - if(labelTemplateString){ - //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. - for (var i=1; i maxValue ) { - return maxValue; - } - } - if(isNumber(minValue)){ - if ( valueToCap < minValue ){ - return minValue; - } - } - return valueToCap; - } - function getDecimalPlaces (num){ - var numberOfDecimalPlaces; - if (num%1!=0){ - return num.toString().split(".")[1].length - } - else{ - return 0; - } - - } - - function mergeChartConfig(defaults,userDefined){ - var returnObj = {}; - for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } - for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } - return returnObj; - } - - //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ - var cache = {}; - - function tmpl(str, data){ - // Figure out if we're getting a template, or if we need to - // load the template - and be sure to cache the result. - var fn = !/\W/.test(str) ? - cache[str] = cache[str] || - tmpl(document.getElementById(str).innerHTML) : - - // Generate a reusable function that will serve as a template - // generator (and which will be cached). - new Function("obj", - "var p=[],print=function(){p.push.apply(p,arguments);};" + - - // Introduce the data as local variables using with(){} - "with(obj){p.push('" + - - // Convert the template into pure JavaScript - str - .replace(/[\r\t\n]/g, " ") - .split("<%").join("\t") - .replace(/((^|%>)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") - + "');}return p.join('');"); - - // Provide some basic currying to the user - return data ? fn( data ) : fn; - }; + } + + //Max value from array + function Max( array ){ + return Math.max.apply( Math, array ); + }; + //Min value from array + function Min( array ){ + return Math.min.apply( Math, array ); + }; + //Default if undefined + function Default(userDeclared,valueIfFalse){ + if(!userDeclared){ + return valueIfFalse; + } else { + return userDeclared; + } + }; + //Is a number function + function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + //Apply cap a value at a high or low number + function CapValue(valueToCap, maxValue, minValue){ + if(isNumber(maxValue)) { + if( valueToCap > maxValue ) { + return maxValue; + } + } + if(isNumber(minValue)){ + if ( valueToCap < minValue ){ + return minValue; + } + } + return valueToCap; + } + function getDecimalPlaces (num){ + var numberOfDecimalPlaces; + if (num%1!=0){ + return num.toString().split(".")[1].length + } + else{ + return 0; + } + + } + + function mergeChartConfig(defaults,userDefined){ + var returnObj = {}; + for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } + for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + return returnObj; + } + + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + var cache = {}; + + function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; - function getFadeColor(percent, primColor, secColor) { - var pseudoEl = document.createElement('div'), - rgbPrim, - rgbSec; - pseudoEl.style.color = primColor; - document.body.appendChild(pseudoEl); - rgbPrim = window.getComputedStyle(pseudoEl).color; - pseudoEl.style.color = secColor; - rgbSec = window.getComputedStyle(pseudoEl).color; - var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/, - valuesP = regex.exec(rgbPrim), - valuesS = regex.exec(rgbSec), - rP = Math.round(parseFloat(valuesP[1])), - gP = Math.round(parseFloat(valuesP[2])), - bP = Math.round(parseFloat(valuesP[3])), - rS = Math.round(parseFloat(valuesS[1])), - gS = Math.round(parseFloat(valuesS[2])), - bS = Math.round(parseFloat(valuesS[3])), - rCur = parseInt((rP-rS)*percent+rS), - gCur = parseInt((gP-gS)*percent+gS), - bCur = parseInt((bP-bS)*percent+bS); - return "rgb("+rCur+','+gCur+','+bCur+')'; - } + function getFadeColor(percent, primColor, secColor) { + var pseudoEl = document.createElement('div'), + rgbPrim, + rgbSec; + pseudoEl.style.color = primColor; + document.body.appendChild(pseudoEl); + rgbPrim = window.getComputedStyle(pseudoEl).color; + pseudoEl.style.color = secColor; + rgbSec = window.getComputedStyle(pseudoEl).color; + var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/, + valuesP = regex.exec(rgbPrim), + valuesS = regex.exec(rgbSec), + rP = Math.round(parseFloat(valuesP[1])), + gP = Math.round(parseFloat(valuesP[2])), + bP = Math.round(parseFloat(valuesP[3])), + rS = Math.round(parseFloat(valuesS[1])), + gS = Math.round(parseFloat(valuesS[2])), + bS = Math.round(parseFloat(valuesS[3])), + rCur = parseInt((rP-rS)*percent+rS), + gCur = parseInt((gP-gS)*percent+gS), + bCur = parseInt((bP-bS)*percent+bS); + return "rgb("+rCur+','+gCur+','+bCur+')'; + } } diff --git a/pie.html b/pie.html index cbce378aed4..cdbe2e86b40 100644 --- a/pie.html +++ b/pie.html @@ -39,7 +39,8 @@ } ]; - var myPie = new Chart(document.getElementById("canvas").getContext("2d")).Pie(pieData, { + var myChart = new Chart(document.getElementById("canvas").getContext("2d")); + var myPie = myChart.Pie(pieData, { animationSteps: 100, animationEasing: 'easeOutBounce' }); From a3d5a96b6ba0bb278d939d4e4ec2e9f2d71b3855 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sat, 23 Mar 2013 16:08:20 +0100 Subject: [PATCH 15/47] implemented polar area tooltips --- Chart.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Chart.js b/Chart.js index 4058b024055..6f78ab17f66 100755 --- a/Chart.js +++ b/Chart.js @@ -574,6 +574,18 @@ var Chart = function(context, tooltipOptions){ ctx.fillStyle = data[i].color; ctx.fill(); + if(animationDecimal > 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [{x:width/2,y:height/2}], + pAmount = 50, + radius = calculateOffset(data[i].value,calculatedScale,scaleHop); + points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'PolarArea'); + } + if(config.segmentShowStroke){ ctx.strokeStyle = config.segmentStrokeColor; ctx.lineWidth = config.segmentStrokeWidth; From ec530f573f9a30d44ec2b869f60c601ae7557af4 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sat, 23 Mar 2013 16:15:58 +0100 Subject: [PATCH 16/47] implemented doughnut tooltips --- Chart.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Chart.js b/Chart.js index 6f78ab17f66..a9d3397821f 100755 --- a/Chart.js +++ b/Chart.js @@ -258,6 +258,9 @@ var Chart = function(context, tooltipOptions){ } } } + context.canvas.onmouseout = function(e) { + context.putImageData(chart.savedState,0,0); + } //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. @@ -952,6 +955,21 @@ var Chart = function(context, tooltipOptions){ ctx.closePath(); ctx.fillStyle = data[i].color; ctx.fill(); + + if(animationDecimal > 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [], + pAmount = 50; + points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)}); + for(var p = pAmount; p >= 0; p--) { + points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'Doughnut'); + } if(config.segmentShowStroke){ ctx.lineWidth = config.segmentStrokeWidth; From 6f4e43ecae617d6e8173d1d0550a7dcdc3e1d44a Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sat, 23 Mar 2013 16:23:22 +0100 Subject: [PATCH 17/47] renamed tooltip stroke to border --- Chart.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Chart.js b/Chart.js index a9d3397821f..416daf54749 100755 --- a/Chart.js +++ b/Chart.js @@ -155,7 +155,7 @@ var Chart = function(context, tooltipOptions){ left: 0, top: 0 }, - stroke: { + border: { width: 0, color: '#000' } @@ -219,9 +219,9 @@ var Chart = function(context, tooltipOptions){ } this.ctx.fillStyle = tooltipOptions.background; this.ctx.fillRect(posX, posY, rectWidth, 24); - if(tooltipOptions.stroke.width > 0) { - this.ctx.fillStyle = tooltipOptions.stroke.color; - this.ctx.lineWidth = tooltipOptions.stroke.width; + if(tooltipOptions.border.width > 0) { + this.ctx.fillStyle = tooltipOptions.border.color; + this.ctx.lineWidth = tooltipOptions.border.width; this.ctx.strokeRect(posX, posY, rectWidth, 24); } this.ctx.font = tooltipOptions.fontStyle+ " " +tooltipOptions.fontSize+" " + tooltipOptions.fontFamily; @@ -301,7 +301,8 @@ var Chart = function(context, tooltipOptions){ animationEasing : "easeOutBounce", animateRotate : true, animateScale : false, - onAnimationComplete : null + onAnimationComplete : null, + showTooltips : true }; var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; @@ -346,7 +347,8 @@ var Chart = function(context, tooltipOptions){ animation : true, animationSteps : 60, animationEasing : "easeOutQuart", - onAnimationComplete : null + onAnimationComplete : null, + showTooltips : true }; var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; @@ -369,6 +371,7 @@ var Chart = function(context, tooltipOptions){ labelFontStyle : "normal", labelFontSize : 12, labelFontColor : "#666", + showTooltips : true }; var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; @@ -388,7 +391,8 @@ var Chart = function(context, tooltipOptions){ animationEasing : "easeOutBounce", animateRotate : true, animateScale : false, - onAnimationComplete : null + onAnimationComplete : null, + showTooltips : true }; var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; @@ -426,7 +430,8 @@ var Chart = function(context, tooltipOptions){ animation : true, animationSteps : 60, animationEasing : "easeOutQuart", - onAnimationComplete : null + onAnimationComplete : null, + showTooltips : true }; var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; @@ -458,7 +463,8 @@ var Chart = function(context, tooltipOptions){ animation : true, animationSteps : 60, animationEasing : "easeOutQuart", - onAnimationComplete : null + onAnimationComplete : null, + showTooltips : true }; var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; From 6f72688d53e0158b9ddd310677b8bcfcc87ada4a Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sun, 24 Mar 2013 10:45:21 +0100 Subject: [PATCH 18/47] added further plans to readme --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 23d8c2a19ee..ee5d9497fd6 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,11 @@ Chart.js ======= *Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org) +Further plans +------- +* Tooltips (see Branch [tooltips](https://github.com/Regaddi/Chart.js/tree/tooltips)) +* Label positioning (inside, outside, legend) +* code refactoring for more object-oriented programming style and better extendability Documentation ------- From 5501808467035ce07ee6265a387a4f5b889a0598 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sun, 24 Mar 2013 10:45:58 +0100 Subject: [PATCH 19/47] added further plans to readme --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 23d8c2a19ee..ee5d9497fd6 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,11 @@ Chart.js ======= *Simple HTML5 Charts using the canvas element* [chartjs.org](http://www.chartjs.org) +Further plans +------- +* Tooltips (see Branch [tooltips](https://github.com/Regaddi/Chart.js/tree/tooltips)) +* Label positioning (inside, outside, legend) +* code refactoring for more object-oriented programming style and better extendability Documentation ------- From c010ecc192c5b1fd828d93592e0a796adf4ba893 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Tue, 26 Mar 2013 08:58:05 +0100 Subject: [PATCH 20/47] fixes issues #1 and #2 --- Chart.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Chart.js b/Chart.js index 416daf54749..565d1aebcf9 100755 --- a/Chart.js +++ b/Chart.js @@ -212,7 +212,7 @@ var Chart = function(context, tooltipOptions){ posY = y+tooltipOptions.offset.top, rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; if(posX + rectWidth > ctx.canvas.width) { - posX -= rectWidth; + posX -= posX-rectWidth < 0 ? posX : rectWidth; } if(posY + 24 > ctx.canvas.height) { posY -= 24; @@ -241,13 +241,26 @@ var Chart = function(context, tooltipOptions){ this.savedState = null; + function getPosition(e) { + var xPosition = 0; + var yPosition = 0; + + while(e) { + xPosition += (e.offsetLeft - e.scrollLeft + e.clientLeft); + yPosition += (e.offsetTop - e.scrollTop + e.clientTop); + e = e.offsetParent; + } + return { x: xPosition, y: yPosition }; + } + context.canvas.onmousemove = function(e) { if(chart.tooltips.length > 0) { chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState; var rendered = 0; for(var i in chart.tooltips) { - var mx = e.x-context.canvas.offsetLeft, - my = e.y-context.canvas.offsetTop; + var position = getPosition(context.canvas), + mx = (e.clientX)-position.x, + my = (e.clientY)-position.y; if(chart.tooltips[i].inRange(mx,my)) { chart.tooltips[i].render(mx,my); rendered++; @@ -259,7 +272,9 @@ var Chart = function(context, tooltipOptions){ } } context.canvas.onmouseout = function(e) { - context.putImageData(chart.savedState,0,0); + if(chart.savedState != null) { + context.putImageData(chart.savedState,0,0); + } } From 590a0eaeb6c158c446c5961aed5c0610575f281f Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Fri, 29 Mar 2013 10:44:56 +0100 Subject: [PATCH 21/47] fixes issue #3 --- Chart.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Chart.js b/Chart.js index 565d1aebcf9..09afaea0b8c 100755 --- a/Chart.js +++ b/Chart.js @@ -246,10 +246,12 @@ var Chart = function(context, tooltipOptions){ var yPosition = 0; while(e) { - xPosition += (e.offsetLeft - e.scrollLeft + e.clientLeft); - yPosition += (e.offsetTop - e.scrollTop + e.clientTop); + xPosition += (e.offsetLeft + e.clientLeft); + yPosition += (e.offsetTop + e.clientTop); e = e.offsetParent; } + xPosition -= window.pageXOffset; + yPosition -= window.pageYOffset; return { x: xPosition, y: yPosition }; } From 6917e9d7d168343ad124c8a60eef68437a425856 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Fri, 29 Mar 2013 11:10:53 +0100 Subject: [PATCH 22/47] final fix for tooltip on scrolls issue #3 --- Chart.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Chart.js b/Chart.js index 09afaea0b8c..756efb44522 100755 --- a/Chart.js +++ b/Chart.js @@ -250,8 +250,13 @@ var Chart = function(context, tooltipOptions){ yPosition += (e.offsetTop + e.clientTop); e = e.offsetParent; } - xPosition -= window.pageXOffset; - yPosition -= window.pageYOffset; + if(window.pageXOffset > 0 || window.pageYOffset > 0) { + xPosition -= window.pageXOffset; + yPosition -= window.pageYOffset; + } else if(document.body.scrollLeft > 0 || document.body.scrollTop > 0) { + xPosition -= document.body.scrollLeft; + yPosition -= document.body.scrollTop; + } return { x: xPosition, y: yPosition }; } From b4e34de85ab3bc05310feff572310f3f1668705d Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Fri, 29 Mar 2013 11:13:02 +0100 Subject: [PATCH 23/47] removing color node after creation --- Chart.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Chart.js b/Chart.js index 756efb44522..3e343c07b60 100755 --- a/Chart.js +++ b/Chart.js @@ -1695,6 +1695,7 @@ var Chart = function(context, tooltipOptions){ rCur = parseInt((rP-rS)*percent+rS), gCur = parseInt((gP-gS)*percent+gS), bCur = parseInt((bP-bS)*percent+bS); + pseudoEl.parentNode.removeChild(pseudoEl); return "rgb("+rCur+','+gCur+','+bCur+')'; } } From eec343f0254b2351a0bc77a07a0feb4ecb8cafac Mon Sep 17 00:00:00 2001 From: default_git_name Date: Sun, 7 Apr 2013 09:35:16 +0000 Subject: [PATCH 24/47] added highlighting to currently hovered chart element --- Chart.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Chart.js b/Chart.js index 3e343c07b60..c958966cdf7 100755 --- a/Chart.js +++ b/Chart.js @@ -158,6 +158,14 @@ var Chart = function(context, tooltipOptions){ border: { width: 0, color: '#000' + }, + showHighlight: true, + highlight: { + stroke: { + width: 3, + color: 'rgba(230,230,230,0.25)' + }, + fill: 'rgba(255,255,255,0.25)' } }, tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; @@ -176,6 +184,7 @@ var Chart = function(context, tooltipOptions){ this.areaObj = areaObj; this.data = data; this.savedState = null; + this.highlightState = null; this.x = null; this.y = null; @@ -207,6 +216,39 @@ var Chart = function(context, tooltipOptions){ this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); } this.ctx.putImageData(this.savedState,0,0); + if(tooltipOptions.showHighlight) { + if(this.highlightState == null) { + this.ctx.fillStyle = tooltipOptions.highlight.stroke.color; + this.ctx.lineWidth = tooltipOptions.highlight.stroke.width; + switch(this.areaObj.type) { + case 'rect': + this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); + this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); + break; + case 'circle': + this.ctx.beginPath(); + this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false); + this.ctx.stroke(); + this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.fill(); + break; + case 'shape': + this.ctx.beginPath(); + this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y); + for(var p in this.areaObj.points) { + this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y); + } + this.ctx.stroke(); + this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.fill(); + break; + } + this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); + } else { + this.ctx.putImageData(this.highlightState,0,0); + } + } if(this.x != x || this.y != y) { var posX = x+tooltipOptions.offset.left, posY = y+tooltipOptions.offset.top, From 38dfd3eede8f245e013274bbd4e2839cb2998d68 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sun, 7 Apr 2013 13:06:25 +0200 Subject: [PATCH 25/47] More accurate bar tooltip area --- Chart.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Chart.js b/Chart.js index c958966cdf7..aacda4c9224 100755 --- a/Chart.js +++ b/Chart.js @@ -162,7 +162,7 @@ var Chart = function(context, tooltipOptions){ showHighlight: true, highlight: { stroke: { - width: 3, + width: 1, color: 'rgba(230,230,230,0.25)' }, fill: 'rgba(255,255,255,0.25)' @@ -1350,7 +1350,7 @@ var Chart = function(context, tooltipOptions){ if(animPc == 1) { // register tooltips var x = barOffset, - height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2), + height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop), y = xAxisPosY-height, width = barWidth, ttData = data.labels[j] != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; From c42b3cab8ffd264080c8d2851647fb03aacef5a2 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Sun, 7 Apr 2013 13:16:58 +0200 Subject: [PATCH 26/47] Fixed highlight stroke and fill --- Chart.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Chart.js b/Chart.js index aacda4c9224..a85154226ee 100755 --- a/Chart.js +++ b/Chart.js @@ -218,8 +218,9 @@ var Chart = function(context, tooltipOptions){ this.ctx.putImageData(this.savedState,0,0); if(tooltipOptions.showHighlight) { if(this.highlightState == null) { - this.ctx.fillStyle = tooltipOptions.highlight.stroke.color; + this.ctx.strokeStyle = tooltipOptions.highlight.stroke.color; this.ctx.lineWidth = tooltipOptions.highlight.stroke.width; + this.ctx.fillStyle = tooltipOptions.highlight.fill; switch(this.areaObj.type) { case 'rect': this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); @@ -230,7 +231,6 @@ var Chart = function(context, tooltipOptions){ this.ctx.beginPath(); this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false); this.ctx.stroke(); - this.ctx.fillStyle = tooltipOptions.highlight.fill; this.ctx.fill(); break; case 'shape': @@ -240,7 +240,6 @@ var Chart = function(context, tooltipOptions){ this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y); } this.ctx.stroke(); - this.ctx.fillStyle = tooltipOptions.highlight.fill; this.ctx.fill(); break; } From b0d385c5bd0908d8ac29c029dbf6b9b31f56f13d Mon Sep 17 00:00:00 2001 From: Marvin Rabe Date: Tue, 9 Apr 2013 11:06:11 +0300 Subject: [PATCH 27/47] Label problem fixed. I use numbers as label. The trim function is not definded for numbers. Variable will be converted to string before the trim function is called. --- Chart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Chart.js b/Chart.js index a85154226ee..79c09727b51 100755 --- a/Chart.js +++ b/Chart.js @@ -1102,7 +1102,7 @@ var Chart = function(context, tooltipOptions){ for(var j = 0; j < data.datasets[i].data.length; j++) { if(animPc == 1) { // register tooltips - var ttData = data.labels[j].trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; + var ttData = data.labels[j].toString().trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},ttData,'Line'); } } From 6929b78ad4e7d46bd0b44886050067c11047612c Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Wed, 10 Apr 2013 17:10:30 +0200 Subject: [PATCH 28/47] reverted to tabs --- Chart.js | 3478 +++++++++++++++++++++++++++--------------------------- 1 file changed, 1739 insertions(+), 1739 deletions(-) diff --git a/Chart.js b/Chart.js index 79c09727b51..bc50d14a724 100755 --- a/Chart.js +++ b/Chart.js @@ -1,1744 +1,1744 @@ //Define the global Chart Variable as a class. var Chart = function(context, tooltipOptions){ - - var chart = this; - - - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ - - var animationOptions = { - linear : function (t){ - return t; - }, - easeInQuad: function (t) { - return t*t; - }, - easeOutQuad: function (t) { - return -1 *t*(t-2); - }, - easeInOutQuad: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t; - return -1/2 * ((--t)*(t-2) - 1); - }, - easeInCubic: function (t) { - return t*t*t; - }, - easeOutCubic: function (t) { - return 1*((t=t/1-1)*t*t + 1); - }, - easeInOutCubic: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t; - return 1/2*((t-=2)*t*t + 2); - }, - easeInQuart: function (t) { - return t*t*t*t; - }, - easeOutQuart: function (t) { - return -1 * ((t=t/1-1)*t*t*t - 1); - }, - easeInOutQuart: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t*t; - return -1/2 * ((t-=2)*t*t*t - 2); - }, - easeInQuint: function (t) { - return 1*(t/=1)*t*t*t*t; - }, - easeOutQuint: function (t) { - return 1*((t=t/1-1)*t*t*t*t + 1); - }, - easeInOutQuint: function (t) { - if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; - return 1/2*((t-=2)*t*t*t*t + 2); - }, - easeInSine: function (t) { - return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; - }, - easeOutSine: function (t) { - return 1 * Math.sin(t/1 * (Math.PI/2)); - }, - easeInOutSine: function (t) { - return -1/2 * (Math.cos(Math.PI*t/1) - 1); - }, - easeInExpo: function (t) { - return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); - }, - easeOutExpo: function (t) { - return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); - }, - easeInOutExpo: function (t) { - if (t==0) return 0; - if (t==1) return 1; - if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); - return 1/2 * (-Math.pow(2, -10 * --t) + 2); - }, - easeInCirc: function (t) { - if (t>=1) return t; - return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); - }, - easeOutCirc: function (t) { - return 1 * Math.sqrt(1 - (t=t/1-1)*t); - }, - easeInOutCirc: function (t) { - if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); - return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); - }, - easeInElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); - }, - easeOutElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; - }, - easeInOutElastic: function (t) { - var s=1.70158;var p=0;var a=1; - if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); - if (a < Math.abs(1)) { a=1; var s=p/4; } - else var s = p/(2*Math.PI) * Math.asin (1/a); - if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); - return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; - }, - easeInBack: function (t) { - var s = 1.70158; - return 1*(t/=1)*t*((s+1)*t - s); - }, - easeOutBack: function (t) { - var s = 1.70158; - return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); - }, - easeInOutBack: function (t) { - var s = 1.70158; - if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); - return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); - }, - easeInBounce: function (t) { - return 1 - animationOptions.easeOutBounce (1-t); - }, - easeOutBounce: function (t) { - if ((t/=1) < (1/2.75)) { - return 1*(7.5625*t*t); - } else if (t < (2/2.75)) { - return 1*(7.5625*(t-=(1.5/2.75))*t + .75); - } else if (t < (2.5/2.75)) { - return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); - } else { - return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); - } - }, - easeInOutBounce: function (t) { - if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; - return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; - } - }; - - this.tooltips = [], - tooltipDefaults = { - background: 'rgba(0,0,0,0.6)', - fontFamily : "'Arial'", - fontStyle : "normal", - fontColor: 'white', - fontSize: '12px', - padding: { - top: 10, - right: 10, - bottom: 10, - left: 10 - }, - offset: { - left: 0, - top: 0 - }, - border: { - width: 0, - color: '#000' - }, - showHighlight: true, - highlight: { - stroke: { - width: 1, - color: 'rgba(230,230,230,0.25)' - }, - fill: 'rgba(255,255,255,0.25)' - } - }, - tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; - - function registerTooltip(ctx,areaObj,data,type) { - chart.tooltips.push(new Tooltip( - ctx, - areaObj, - data, - type - )); - } - - var Tooltip = function(ctx, areaObj, data, type) { - this.ctx = ctx; - this.areaObj = areaObj; - this.data = data; - this.savedState = null; - this.highlightState = null; - this.x = null; - this.y = null; - - this.inRange = function(x,y) { - if(this.areaObj.type) { - switch(this.areaObj.type) { - case 'rect': - return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) && - (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height); - break; - case 'circle': - return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2)); - break; - case 'shape': - var poly = this.areaObj.points; - for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) - ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y)) - && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x) - && (c = !c); - return c; - break; - } - } - } - - this.render = function(x,y) { - if(this.savedState == null) { - this.ctx.putImageData(chart.savedState,0,0); - this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); - } - this.ctx.putImageData(this.savedState,0,0); - if(tooltipOptions.showHighlight) { - if(this.highlightState == null) { - this.ctx.strokeStyle = tooltipOptions.highlight.stroke.color; - this.ctx.lineWidth = tooltipOptions.highlight.stroke.width; - this.ctx.fillStyle = tooltipOptions.highlight.fill; - switch(this.areaObj.type) { - case 'rect': - this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); - this.ctx.fillStyle = tooltipOptions.highlight.fill; - this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); - break; - case 'circle': - this.ctx.beginPath(); - this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false); - this.ctx.stroke(); - this.ctx.fill(); - break; - case 'shape': - this.ctx.beginPath(); - this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y); - for(var p in this.areaObj.points) { - this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y); - } - this.ctx.stroke(); - this.ctx.fill(); - break; - } - this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); - } else { - this.ctx.putImageData(this.highlightState,0,0); - } - } - if(this.x != x || this.y != y) { - var posX = x+tooltipOptions.offset.left, - posY = y+tooltipOptions.offset.top, - rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; - if(posX + rectWidth > ctx.canvas.width) { - posX -= posX-rectWidth < 0 ? posX : rectWidth; - } - if(posY + 24 > ctx.canvas.height) { - posY -= 24; - } - this.ctx.fillStyle = tooltipOptions.background; - this.ctx.fillRect(posX, posY, rectWidth, 24); - if(tooltipOptions.border.width > 0) { - this.ctx.fillStyle = tooltipOptions.border.color; - this.ctx.lineWidth = tooltipOptions.border.width; - this.ctx.strokeRect(posX, posY, rectWidth, 24); - } - this.ctx.font = tooltipOptions.fontStyle+ " " +tooltipOptions.fontSize+" " + tooltipOptions.fontFamily; - this.ctx.fillStyle = tooltipOptions.fontColor; - this.ctx.textAlign = 'center'; - this.ctx.textBaseline = 'middle'; - this.ctx.fillText(data, posX+rectWidth/2, posY+12); - this.x = x; - this.y = y; - } - } - } - - //Variables global to the chart - var width = context.canvas.width, - height = context.canvas.height; - - this.savedState = null; - - function getPosition(e) { - var xPosition = 0; - var yPosition = 0; - - while(e) { - xPosition += (e.offsetLeft + e.clientLeft); - yPosition += (e.offsetTop + e.clientTop); - e = e.offsetParent; - } - if(window.pageXOffset > 0 || window.pageYOffset > 0) { - xPosition -= window.pageXOffset; - yPosition -= window.pageYOffset; - } else if(document.body.scrollLeft > 0 || document.body.scrollTop > 0) { - xPosition -= document.body.scrollLeft; - yPosition -= document.body.scrollTop; - } - return { x: xPosition, y: yPosition }; - } - - context.canvas.onmousemove = function(e) { - if(chart.tooltips.length > 0) { - chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState; - var rendered = 0; - for(var i in chart.tooltips) { - var position = getPosition(context.canvas), - mx = (e.clientX)-position.x, - my = (e.clientY)-position.y; - if(chart.tooltips[i].inRange(mx,my)) { - chart.tooltips[i].render(mx,my); - rendered++; - } - } - if(rendered == 0) { - context.putImageData(chart.savedState,0,0); - } - } - } - context.canvas.onmouseout = function(e) { - if(chart.savedState != null) { - context.putImageData(chart.savedState,0,0); - } - } - - - //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. - if (window.devicePixelRatio) { - context.canvas.style.width = width + "px"; - context.canvas.style.height = height + "px"; - context.canvas.height = height * window.devicePixelRatio; - context.canvas.width = width * window.devicePixelRatio; - context.scale(window.devicePixelRatio, window.devicePixelRatio); - } - - this.PolarArea = function(data,options){ - - chart.PolarArea.defaults = { - scaleOverlay : true, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleShowLine : true, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowLabelBackdrop : true, - scaleBackdropColor : "rgba(255,255,255,0.75)", - scaleBackdropPaddingY : 2, - scaleBackdropPaddingX : 2, - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null, - showTooltips : true - }; - - var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; - - return new PolarArea(data,config,context); - }; - - this.Radar = function(data,options){ - - chart.Radar.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleShowLine : true, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : false, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowLabelBackdrop : true, - scaleBackdropColor : "rgba(255,255,255,0.75)", - scaleBackdropPaddingY : 2, - scaleBackdropPaddingX : 2, - angleShowLineOut : true, - angleLineColor : "rgba(0,0,0,.1)", - angleLineWidth : 1, - pointLabelFontFamily : "'Arial'", - pointLabelFontStyle : "normal", - pointLabelFontSize : 12, - pointLabelFontColor : "#666", - pointDot : true, - pointDotRadius : 3, - pointDotStrokeWidth : 1, - datasetStroke : true, - datasetStrokeWidth : 2, - datasetFill : true, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null, - showTooltips : true - }; - - var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; - - return new Radar(data,config,context); - }; - - this.Pie = function(data,options){ - chart.Pie.defaults = { - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null, - labelFontFamily : "'Arial'", - labelFontStyle : "normal", - labelFontSize : 12, - labelFontColor : "#666", - showTooltips : true - }; - - var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; - - return new Pie(data,config,context); - }; - - this.Doughnut = function(data,options){ - - chart.Doughnut.defaults = { - segmentShowStroke : true, - segmentStrokeColor : "#fff", - segmentStrokeWidth : 2, - percentageInnerCutout : 50, - animation : true, - animationSteps : 100, - animationEasing : "easeOutBounce", - animateRotate : true, - animateScale : false, - onAnimationComplete : null, - showTooltips : true - }; - - var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; - - return new Doughnut(data,config,context); - - }; - - this.Line = function(data,options){ - - chart.Line.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowGridLines : true, - scaleGridLineColor : "rgba(0,0,0,.05)", - scaleGridLineWidth : 1, - bezierCurve : true, - pointDot : true, - pointDotRadius : 4, - pointDotStrokeWidth : 2, - datasetStroke : true, - datasetStrokeWidth : 2, - datasetFill : true, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null, - showTooltips : true - }; - var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; - - return new Line(data,config,context); - } - - this.Bar = function(data,options){ - chart.Bar.defaults = { - scaleOverlay : false, - scaleOverride : false, - scaleSteps : null, - scaleStepWidth : null, - scaleStartValue : null, - scaleLineColor : "rgba(0,0,0,.1)", - scaleLineWidth : 1, - scaleShowLabels : true, - scaleLabel : "<%=value%>", - scaleFontFamily : "'Arial'", - scaleFontSize : 12, - scaleFontStyle : "normal", - scaleFontColor : "#666", - scaleShowGridLines : true, - scaleGridLineColor : "rgba(0,0,0,.05)", - scaleGridLineWidth : 1, - barShowStroke : true, - barStrokeWidth : 2, - barValueSpacing : 5, - barDatasetSpacing : 1, - animation : true, - animationSteps : 60, - animationEasing : "easeOutQuart", - onAnimationComplete : null, - showTooltips : true - }; - var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; - - return new Bar(data,config,context); - } - - var clear = function(c){ - c.clearRect(0, 0, width, height); - }; - - var PolarArea = function(data,config,ctx){ - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; - - - calculateDrawingSizes(); - - valueBounds = getValueBounds(); - - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; - - //Check and set the scale - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [{x:width/2,y:height/2}], - pAmount = 50, - radius = calculateOffset(data[i].value,calculatedScale,scaleHop); - points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)}); - for(var p = 0; p <= pAmount; p++) { - points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)}); - } - registerTooltip(ctx,{type:'shape',points:points},ttData,'PolarArea'); - } - - if(config.segmentShowStroke){ - ctx.strokeStyle = config.segmentStrokeColor; - ctx.lineWidth = config.segmentStrokeWidth; - ctx.stroke(); - } - startAngle += rotateAnimation*angleStep; - } - } - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - for (var i=0; i upperValue) {upperValue = data[i].value;} - if (data[i].value < lowerValue) {lowerValue = data[i].value;} - }; - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } - } - - var Radar = function (data,config,ctx) { - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; - - //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. - if (!data.labels) data.labels = []; - - calculateDrawingSizes(); - - var valueBounds = getValueBounds(); - - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; - - //Check and set the scale - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i Math.PI){ - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "left"; - } - - ctx.textBaseline = "middle"; - - ctx.fillText(data.labels[k],opposite,-adjacent); - - } - ctx.restore(); - }; - function calculateDrawingSizes(){ - maxSize = (Min([width,height])/2); - - labelHeight = config.scaleFontSize*2; - - var labelLength = 0; - for (var i=0; ilabelLength) labelLength = textMeasurement; - } - - //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. - maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); - - maxSize -= config.pointLabelFontSize; - maxSize = CapValue(maxSize, null, 0); - scaleHeight = maxSize; - //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. - labelHeight = Default(labelHeight,5); - }; - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - - for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} - if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} - } - } - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } - } - - var Pie = function(data,config,ctx){ - var segmentTotal = 0; - - //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var pieRadius = Min([height/2,width/2]) - 5; - - for (var i=0; i config.labelFontSize) { - var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; - if(fontSize.match(/^[0-9]+$/g) != null) { - fontSize = fontSize+'px'; - } - ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; - ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); - // rotate text, so it perfectly fits in segments - var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, - tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, - tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); - ctx.textAlign = 'right'; - if(textRotation < -Math.PI/2) { - textRotation -= Math.PI; - ctx.textAlign = 'left'; - tX += 20; - } - ctx.translate(tX, tY); - ctx.rotate(-textRotation); - ctx.fillText(data[i].label, 0, 0); - ctx.rotate(textRotation); - ctx.translate(-tX, -tY); - } - if(animationDecimal > 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [{x:width/2,y:height/2}], - pAmount = 50; - points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)}); - for(var p = 0; p <= pAmount; p++) { - points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); - } - registerTooltip(ctx,{type:'shape',points:points},ttData,'Pie'); - } - - if(config.segmentShowStroke){ - ctx.lineWidth = config.segmentStrokeWidth; - ctx.strokeStyle = config.segmentStrokeColor; - ctx.stroke(); - } - cumulativeAngle += segmentAngle; - } - } - } - - var Doughnut = function(data,config,ctx){ - var segmentTotal = 0; - - //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. - var doughnutRadius = Min([height/2,width/2]) - 5; - - var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100); - - for (var i=0; i 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [], - pAmount = 50; - points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)}); - for(var p = 0; p <= pAmount; p++) { - points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); - } - points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)}); - for(var p = pAmount; p >= 0; p--) { - points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); - } - registerTooltip(ctx,{type:'shape',points:points},ttData,'Doughnut'); - } - - if(config.segmentShowStroke){ - ctx.lineWidth = config.segmentStrokeWidth; - ctx.strokeStyle = config.segmentStrokeColor; - ctx.stroke(); - } - cumulativeAngle += segmentAngle; - } - } - - - - } - - var Line = function(data,config,ctx){ - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0; - - calculateDrawingSizes(); - - valueBounds = getValueBounds(); - //Check and set the scale - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i 0){ - ctx.save(); - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "center"; - } - ctx.fillStyle = config.scaleFontColor; - for (var i=0; i 0){ - ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); - ctx.rotate(-(rotateLabels * (Math.PI/180))); - ctx.fillText(data.labels[i], 0,0); - ctx.restore(); - } - - else{ - ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); - } - - ctx.beginPath(); - ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); - - //Check i isnt 0, so we dont go over the Y axis twice. - if(config.scaleShowGridLines && i>0){ - ctx.lineWidth = config.scaleGridLineWidth; - ctx.strokeStyle = config.scaleGridLineColor; - ctx.lineTo(yAxisPosX + i * valueHop, 5); - } - else{ - ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); - } - ctx.stroke(); - } - - //Y axis - ctx.lineWidth = config.scaleLineWidth; - ctx.strokeStyle = config.scaleLineColor; - ctx.beginPath(); - ctx.moveTo(yAxisPosX,xAxisPosY+5); - ctx.lineTo(yAxisPosX,5); - ctx.stroke(); - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - for (var j=0; j longestText)? measuredText : longestText; - } - //Add a little extra padding from the y axis - longestText +=10; - } - xAxisLength = width - longestText - widestXLabel; - valueHop = Math.floor(xAxisLength/(data.labels.length-1)); - - yAxisPosX = width-widestXLabel/2-xAxisLength; - xAxisPosY = scaleHeight + config.scaleFontSize/2; - } - function calculateDrawingSizes(){ - maxSize = height; - - //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. - ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; - widestXLabel = 1; - for (var i=0; i widestXLabel)? textLength : widestXLabel; - } - if (width/data.labels.length < widestXLabel){ - rotateLabels = 45; - if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ - rotateLabels = 90; - maxSize -= widestXLabel; - } - else{ - maxSize -= Math.sin(rotateLabels) * widestXLabel; - } - } - else{ - maxSize -= config.scaleFontSize; - } - - //Add a little padding between the x line and the text - maxSize -= 5; - - - labelHeight = config.scaleFontSize; - - maxSize -= labelHeight; - //Set 5 pixels greater than the font size to allow for a little padding from the X axis. - - scaleHeight = maxSize; - - //Then get the area above we can safely draw on. - - } - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; - } - }; - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } - - - } - - var Bar = function(data,config,ctx){ - var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; - - calculateDrawingSizes(); - - valueBounds = getValueBounds(); - //Check and set the scale - labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; - if (!config.scaleOverride){ - - calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); - } - else { - calculatedScale = { - steps : config.scaleSteps, - stepValue : config.scaleStepWidth, - graphMin : config.scaleStartValue, - labels : [] - } - for (var i=0; i 0){ - ctx.save(); - ctx.textAlign = "right"; - } - else{ - ctx.textAlign = "center"; - } - ctx.fillStyle = config.scaleFontColor; - for (var i=0; i 0){ - ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); - ctx.rotate(-(rotateLabels * (Math.PI/180))); - ctx.fillText(data.labels[i], 0,0); - ctx.restore(); - } - - else{ - ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); - } - - ctx.beginPath(); - ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); - - //Check i isnt 0, so we dont go over the Y axis twice. - ctx.lineWidth = config.scaleGridLineWidth; - ctx.strokeStyle = config.scaleGridLineColor; - ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); - ctx.stroke(); - } - - //Y axis - ctx.lineWidth = config.scaleLineWidth; - ctx.strokeStyle = config.scaleLineColor; - ctx.beginPath(); - ctx.moveTo(yAxisPosX,xAxisPosY+5); - ctx.lineTo(yAxisPosX,5); - ctx.stroke(); - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - for (var j=0; j longestText)? measuredText : longestText; - } - //Add a little extra padding from the y axis - longestText +=10; - } - xAxisLength = width - longestText - widestXLabel; - valueHop = Math.floor(xAxisLength/(data.labels.length)); - - barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; - - yAxisPosX = width-widestXLabel/2-xAxisLength; - xAxisPosY = scaleHeight + config.scaleFontSize/2; - } - function calculateDrawingSizes(){ - maxSize = height; - - //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. - ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; - widestXLabel = 1; - for (var i=0; i widestXLabel)? textLength : widestXLabel; - } - if (width/data.labels.length < widestXLabel){ - rotateLabels = 45; - if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ - rotateLabels = 90; - maxSize -= widestXLabel; - } - else{ - maxSize -= Math.sin(rotateLabels) * widestXLabel; - } - } - else{ - maxSize -= config.scaleFontSize; - } - - //Add a little padding between the x line and the text - maxSize -= 5; - - - labelHeight = config.scaleFontSize; - - maxSize -= labelHeight; - //Set 5 pixels greater than the font size to allow for a little padding from the X axis. - - scaleHeight = maxSize; - - //Then get the area above we can safely draw on. - - } - function getValueBounds() { - var upperValue = Number.MIN_VALUE; - var lowerValue = Number.MAX_VALUE; - for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; - if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; - } - }; - - var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); - var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); - - return { - maxValue : upperValue, - minValue : lowerValue, - maxSteps : maxSteps, - minSteps : minSteps - }; - - - } - } - - function calculateOffset(val,calculatedScale,scaleHop){ - var outerValue = calculatedScale.steps * calculatedScale.stepValue; - var adjustedValue = val - calculatedScale.graphMin; - var scalingFactor = CapValue(adjustedValue/outerValue,1,0); - return (scaleHop*calculatedScale.steps) * scalingFactor; - } - - function animationLoop(config,drawScale,drawData,ctx){ - var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, - easingFunction = animationOptions[config.animationEasing], - percentAnimComplete =(config.animation)? 0 : 1; - - - - if (typeof drawScale !== "function") drawScale = function(){}; - - requestAnimFrame(animLoop); - - function animateFrame(){ - var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; - clear(ctx); - if(config.scaleOverlay){ - drawData(easeAdjustedAnimationPercent); - drawScale(); - } else { - drawScale(); - drawData(easeAdjustedAnimationPercent); - } - } - function animLoop(){ - //We need to check if the animation is incomplete (less than 1), or complete (1). - percentAnimComplete += animFrameAmount; - animateFrame(); - //Stop the loop continuing forever - if (percentAnimComplete <= 1){ - requestAnimFrame(animLoop); - } - else{ - if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); - } - - } - - } - - //Declare global functions to be called within this namespace here. - - - // shim layer with setTimeout fallback - var requestAnimFrame = (function(){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - })(); - - function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ - var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; - - valueRange = maxValue - minValue; - - rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); - - graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); - - graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); - - graphRange = graphMax - graphMin; - - stepValue = Math.pow(10, rangeOrderOfMagnitude); - - numberOfSteps = Math.round(graphRange / stepValue); - - //Compare number of steps to the max and min for that size graph, and add in half steps if need be. - while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { - if (numberOfSteps < minSteps){ - stepValue /= 2; - numberOfSteps = Math.round(graphRange/stepValue); - } - else{ - stepValue *=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - }; - - - - //Create an array of all the labels by interpolating the string. - - var labels = []; - - if(labelTemplateString){ - //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. - for (var i=1; i maxValue ) { - return maxValue; - } - } - if(isNumber(minValue)){ - if ( valueToCap < minValue ){ - return minValue; - } - } - return valueToCap; - } - function getDecimalPlaces (num){ - var numberOfDecimalPlaces; - if (num%1!=0){ - return num.toString().split(".")[1].length - } - else{ - return 0; - } - - } - - function mergeChartConfig(defaults,userDefined){ - var returnObj = {}; - for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } - for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } - return returnObj; - } - - //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ - var cache = {}; - - function tmpl(str, data){ - // Figure out if we're getting a template, or if we need to - // load the template - and be sure to cache the result. - var fn = !/\W/.test(str) ? - cache[str] = cache[str] || - tmpl(document.getElementById(str).innerHTML) : - - // Generate a reusable function that will serve as a template - // generator (and which will be cached). - new Function("obj", - "var p=[],print=function(){p.push.apply(p,arguments);};" + - - // Introduce the data as local variables using with(){} - "with(obj){p.push('" + - - // Convert the template into pure JavaScript - str - .replace(/[\r\t\n]/g, " ") - .split("<%").join("\t") - .replace(/((^|%>)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") - + "');}return p.join('');"); - - // Provide some basic currying to the user - return data ? fn( data ) : fn; - }; - - function getFadeColor(percent, primColor, secColor) { - var pseudoEl = document.createElement('div'), - rgbPrim, - rgbSec; - pseudoEl.style.color = primColor; - document.body.appendChild(pseudoEl); - rgbPrim = window.getComputedStyle(pseudoEl).color; - pseudoEl.style.color = secColor; - rgbSec = window.getComputedStyle(pseudoEl).color; - var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/, - valuesP = regex.exec(rgbPrim), - valuesS = regex.exec(rgbSec), - rP = Math.round(parseFloat(valuesP[1])), - gP = Math.round(parseFloat(valuesP[2])), - bP = Math.round(parseFloat(valuesP[3])), - rS = Math.round(parseFloat(valuesS[1])), - gS = Math.round(parseFloat(valuesS[2])), - bS = Math.round(parseFloat(valuesS[3])), - rCur = parseInt((rP-rS)*percent+rS), - gCur = parseInt((gP-gS)*percent+gS), - bCur = parseInt((bP-bS)*percent+bS); - pseudoEl.parentNode.removeChild(pseudoEl); - return "rgb("+rCur+','+gCur+','+bCur+')'; - } + + var chart = this; + + + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + + var animationOptions = { + linear : function (t){ + return t; + }, + easeInQuad: function (t) { + return t*t; + }, + easeOutQuad: function (t) { + return -1 *t*(t-2); + }, + easeInOutQuad: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t; + return -1/2 * ((--t)*(t-2) - 1); + }, + easeInCubic: function (t) { + return t*t*t; + }, + easeOutCubic: function (t) { + return 1*((t=t/1-1)*t*t + 1); + }, + easeInOutCubic: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t; + return 1/2*((t-=2)*t*t + 2); + }, + easeInQuart: function (t) { + return t*t*t*t; + }, + easeOutQuart: function (t) { + return -1 * ((t=t/1-1)*t*t*t - 1); + }, + easeInOutQuart: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t; + return -1/2 * ((t-=2)*t*t*t - 2); + }, + easeInQuint: function (t) { + return 1*(t/=1)*t*t*t*t; + }, + easeOutQuint: function (t) { + return 1*((t=t/1-1)*t*t*t*t + 1); + }, + easeInOutQuint: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; + return 1/2*((t-=2)*t*t*t*t + 2); + }, + easeInSine: function (t) { + return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; + }, + easeOutSine: function (t) { + return 1 * Math.sin(t/1 * (Math.PI/2)); + }, + easeInOutSine: function (t) { + return -1/2 * (Math.cos(Math.PI*t/1) - 1); + }, + easeInExpo: function (t) { + return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); + }, + easeOutExpo: function (t) { + return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); + }, + easeInOutExpo: function (t) { + if (t==0) return 0; + if (t==1) return 1; + if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); + return 1/2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function (t) { + if (t>=1) return t; + return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); + }, + easeOutCirc: function (t) { + return 1 * Math.sqrt(1 - (t=t/1-1)*t); + }, + easeInOutCirc: function (t) { + if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); + return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); + }, + easeInElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + }, + easeOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; + }, + easeInOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; + }, + easeInBack: function (t) { + var s = 1.70158; + return 1*(t/=1)*t*((s+1)*t - s); + }, + easeOutBack: function (t) { + var s = 1.70158; + return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); + }, + easeInOutBack: function (t) { + var s = 1.70158; + if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); + return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); + }, + easeInBounce: function (t) { + return 1 - animationOptions.easeOutBounce (1-t); + }, + easeOutBounce: function (t) { + if ((t/=1) < (1/2.75)) { + return 1*(7.5625*t*t); + } else if (t < (2/2.75)) { + return 1*(7.5625*(t-=(1.5/2.75))*t + .75); + } else if (t < (2.5/2.75)) { + return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); + } else { + return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); + } + }, + easeInOutBounce: function (t) { + if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; + return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; + } + }; + + this.tooltips = [], + tooltipDefaults = { + background: 'rgba(0,0,0,0.6)', + fontFamily : "'Arial'", + fontStyle : "normal", + fontColor: 'white', + fontSize: '12px', + padding: { + top: 10, + right: 10, + bottom: 10, + left: 10 + }, + offset: { + left: 0, + top: 0 + }, + border: { + width: 0, + color: '#000' + }, + showHighlight: true, + highlight: { + stroke: { + width: 1, + color: 'rgba(230,230,230,0.25)' + }, + fill: 'rgba(255,255,255,0.25)' + } + }, + tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; + + function registerTooltip(ctx,areaObj,data,type) { + chart.tooltips.push(new Tooltip( + ctx, + areaObj, + data, + type + )); + } + + var Tooltip = function(ctx, areaObj, data, type) { + this.ctx = ctx; + this.areaObj = areaObj; + this.data = data; + this.savedState = null; + this.highlightState = null; + this.x = null; + this.y = null; + + this.inRange = function(x,y) { + if(this.areaObj.type) { + switch(this.areaObj.type) { + case 'rect': + return (x >= this.areaObj.x && x <= this.areaObj.x+this.areaObj.width) && + (y >= this.areaObj.y && y <= this.areaObj.y+this.areaObj.height); + break; + case 'circle': + return ((Math.pow(x-this.areaObj.x, 2)+Math.pow(y-this.areaObj.y, 2)) < Math.pow(this.areaObj.r,2)); + break; + case 'shape': + var poly = this.areaObj.points; + for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) + ((poly[i].y <= y && y < poly[j].y) || (poly[j].y <= y && y < poly[i].y)) + && (x < (poly[j].x - poly[i].x) * (y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x) + && (c = !c); + return c; + break; + } + } + } + + this.render = function(x,y) { + if(this.savedState == null) { + this.ctx.putImageData(chart.savedState,0,0); + this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); + } + this.ctx.putImageData(this.savedState,0,0); + if(tooltipOptions.showHighlight) { + if(this.highlightState == null) { + this.ctx.strokeStyle = tooltipOptions.highlight.stroke.color; + this.ctx.lineWidth = tooltipOptions.highlight.stroke.width; + this.ctx.fillStyle = tooltipOptions.highlight.fill; + switch(this.areaObj.type) { + case 'rect': + this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); + this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); + break; + case 'circle': + this.ctx.beginPath(); + this.ctx.arc(this.areaObj.x, this.areaObj.y, this.areaObj.r, 0, 2*Math.PI, false); + this.ctx.stroke(); + this.ctx.fill(); + break; + case 'shape': + this.ctx.beginPath(); + this.ctx.moveTo(this.areaObj.points[0].x, this.areaObj.points[0].y); + for(var p in this.areaObj.points) { + this.ctx.lineTo(this.areaObj.points[p].x, this.areaObj.points[p].y); + } + this.ctx.stroke(); + this.ctx.fill(); + break; + } + this.highlightState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); + } else { + this.ctx.putImageData(this.highlightState,0,0); + } + } + if(this.x != x || this.y != y) { + var posX = x+tooltipOptions.offset.left, + posY = y+tooltipOptions.offset.top, + rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; + if(posX + rectWidth > ctx.canvas.width) { + posX -= posX-rectWidth < 0 ? posX : rectWidth; + } + if(posY + 24 > ctx.canvas.height) { + posY -= 24; + } + this.ctx.fillStyle = tooltipOptions.background; + this.ctx.fillRect(posX, posY, rectWidth, 24); + if(tooltipOptions.border.width > 0) { + this.ctx.fillStyle = tooltipOptions.border.color; + this.ctx.lineWidth = tooltipOptions.border.width; + this.ctx.strokeRect(posX, posY, rectWidth, 24); + } + this.ctx.font = tooltipOptions.fontStyle+ " " +tooltipOptions.fontSize+" " + tooltipOptions.fontFamily; + this.ctx.fillStyle = tooltipOptions.fontColor; + this.ctx.textAlign = 'center'; + this.ctx.textBaseline = 'middle'; + this.ctx.fillText(data, posX+rectWidth/2, posY+12); + this.x = x; + this.y = y; + } + } + } + + //Variables global to the chart + var width = context.canvas.width, + height = context.canvas.height; + + this.savedState = null; + + function getPosition(e) { + var xPosition = 0; + var yPosition = 0; + + while(e) { + xPosition += (e.offsetLeft + e.clientLeft); + yPosition += (e.offsetTop + e.clientTop); + e = e.offsetParent; + } + if(window.pageXOffset > 0 || window.pageYOffset > 0) { + xPosition -= window.pageXOffset; + yPosition -= window.pageYOffset; + } else if(document.body.scrollLeft > 0 || document.body.scrollTop > 0) { + xPosition -= document.body.scrollLeft; + yPosition -= document.body.scrollTop; + } + return { x: xPosition, y: yPosition }; + } + + context.canvas.onmousemove = function(e) { + if(chart.tooltips.length > 0) { + chart.savedState = chart.savedState == null ? context.getImageData(0,0,context.canvas.width,context.canvas.height) : chart.savedState; + var rendered = 0; + for(var i in chart.tooltips) { + var position = getPosition(context.canvas), + mx = (e.clientX)-position.x, + my = (e.clientY)-position.y; + if(chart.tooltips[i].inRange(mx,my)) { + chart.tooltips[i].render(mx,my); + rendered++; + } + } + if(rendered == 0) { + context.putImageData(chart.savedState,0,0); + } + } + } + context.canvas.onmouseout = function(e) { + if(chart.savedState != null) { + context.putImageData(chart.savedState,0,0); + } + } + + + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + if (window.devicePixelRatio) { + context.canvas.style.width = width + "px"; + context.canvas.style.height = height + "px"; + context.canvas.height = height * window.devicePixelRatio; + context.canvas.width = width * window.devicePixelRatio; + context.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + this.PolarArea = function(data,options){ + + chart.PolarArea.defaults = { + scaleOverlay : true, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null, + showTooltips : true + }; + + var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; + + this.Radar = function(data,options){ + + chart.Radar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : false, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + angleShowLineOut : true, + angleLineColor : "rgba(0,0,0,.1)", + angleLineWidth : 1, + pointLabelFontFamily : "'Arial'", + pointLabelFontStyle : "normal", + pointLabelFontSize : 12, + pointLabelFontColor : "#666", + pointDot : true, + pointDotRadius : 3, + pointDotStrokeWidth : 1, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null, + showTooltips : true + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + + return new Radar(data,config,context); + }; + + this.Pie = function(data,options){ + chart.Pie.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null, + labelFontFamily : "'Arial'", + labelFontStyle : "normal", + labelFontSize : 12, + labelFontColor : "#666", + showTooltips : true + }; + + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + + this.Doughnut = function(data,options){ + + chart.Doughnut.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + percentageInnerCutout : 50, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null, + showTooltips : true + }; + + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; + + this.Line = function(data,options){ + + chart.Line.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + bezierCurve : true, + pointDot : true, + pointDotRadius : 4, + pointDotStrokeWidth : 2, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null, + showTooltips : true + }; + var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; + + return new Line(data,config,context); + } + + this.Bar = function(data,options){ + chart.Bar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + barShowStroke : true, + barStrokeWidth : 2, + barValueSpacing : 5, + barDatasetSpacing : 1, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null, + showTooltips : true + }; + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } + + var clear = function(c){ + c.clearRect(0, 0, width, height); + }; + + var PolarArea = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [{x:width/2,y:height/2}], + pAmount = 50, + radius = calculateOffset(data[i].value,calculatedScale,scaleHop); + points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'PolarArea'); + } + + if(config.segmentShowStroke){ + ctx.strokeStyle = config.segmentStrokeColor; + ctx.lineWidth = config.segmentStrokeWidth; + ctx.stroke(); + } + startAngle += rotateAnimation*angleStep; + } + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Radar = function (data,config,ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); + + var valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i Math.PI){ + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillText(data.labels[k],opposite,-adjacent); + + } + ctx.restore(); + }; + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + }; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} + } + } + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Pie = function(data,config,ctx){ + var segmentTotal = 0; + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + var pieRadius = Min([height/2,width/2]) - 5; + + for (var i=0; i config.labelFontSize) { + var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; + if(fontSize.match(/^[0-9]+$/g) != null) { + fontSize = fontSize+'px'; + } + ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; + ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); + // rotate text, so it perfectly fits in segments + var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, + tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, + tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); + ctx.textAlign = 'right'; + if(textRotation < -Math.PI/2) { + textRotation -= Math.PI; + ctx.textAlign = 'left'; + tX += 20; + } + ctx.translate(tX, tY); + ctx.rotate(-textRotation); + ctx.fillText(data[i].label, 0, 0); + ctx.rotate(textRotation); + ctx.translate(-tX, -tY); + } + if(animationDecimal > 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [{x:width/2,y:height/2}], + pAmount = 50; + points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'Pie'); + } + + if(config.segmentShowStroke){ + ctx.lineWidth = config.segmentStrokeWidth; + ctx.strokeStyle = config.segmentStrokeColor; + ctx.stroke(); + } + cumulativeAngle += segmentAngle; + } + } + } + + var Doughnut = function(data,config,ctx){ + var segmentTotal = 0; + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + var doughnutRadius = Min([height/2,width/2]) - 5; + + var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100); + + for (var i=0; i 0.9999999) { + var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, + points = [], + pAmount = 50; + points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)}); + for(var p = 0; p <= pAmount; p++) { + points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+segmentAngle)}); + for(var p = pAmount; p >= 0; p--) { + points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); + } + registerTooltip(ctx,{type:'shape',points:points},ttData,'Doughnut'); + } + + if(config.segmentShowStroke){ + ctx.lineWidth = config.segmentStrokeWidth; + ctx.strokeStyle = config.segmentStrokeColor; + ctx.stroke(); + } + cumulativeAngle += segmentAngle; + } + } + + + + } + + var Line = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0; + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + if(config.scaleShowGridLines && i>0){ + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + i * valueHop, 5); + } + else{ + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); + } + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length-1)); + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + + + } + + var Bar = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length)); + + barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + function calculateOffset(val,calculatedScale,scaleHop){ + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue/outerValue,1,0); + return (scaleHop*calculatedScale.steps) * scalingFactor; + } + + function animationLoop(config,drawScale,drawData,ctx){ + var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, + easingFunction = animationOptions[config.animationEasing], + percentAnimComplete =(config.animation)? 0 : 1; + + + + if (typeof drawScale !== "function") drawScale = function(){}; + + requestAnimFrame(animLoop); + + function animateFrame(){ + var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; + clear(ctx); + if(config.scaleOverlay){ + drawData(easeAdjustedAnimationPercent); + drawScale(); + } else { + drawScale(); + drawData(easeAdjustedAnimationPercent); + } + } + function animLoop(){ + //We need to check if the animation is incomplete (less than 1), or complete (1). + percentAnimComplete += animFrameAmount; + animateFrame(); + //Stop the loop continuing forever + if (percentAnimComplete <= 1){ + requestAnimFrame(animLoop); + } + else{ + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); + } + + } + + } + + //Declare global functions to be called within this namespace here. + + + // shim layer with setTimeout fallback + var requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + + function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + + valueRange = maxValue - minValue; + + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphRange = graphMax - graphMin; + + stepValue = Math.pow(10, rangeOrderOfMagnitude); + + numberOfSteps = Math.round(graphRange / stepValue); + + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; + + + + //Create an array of all the labels by interpolating the string. + + var labels = []; + + if(labelTemplateString){ + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + for (var i=1; i maxValue ) { + return maxValue; + } + } + if(isNumber(minValue)){ + if ( valueToCap < minValue ){ + return minValue; + } + } + return valueToCap; + } + function getDecimalPlaces (num){ + var numberOfDecimalPlaces; + if (num%1!=0){ + return num.toString().split(".")[1].length + } + else{ + return 0; + } + + } + + function mergeChartConfig(defaults,userDefined){ + var returnObj = {}; + for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } + for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + return returnObj; + } + + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + var cache = {}; + + function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; + + function getFadeColor(percent, primColor, secColor) { + var pseudoEl = document.createElement('div'), + rgbPrim, + rgbSec; + pseudoEl.style.color = primColor; + document.body.appendChild(pseudoEl); + rgbPrim = window.getComputedStyle(pseudoEl).color; + pseudoEl.style.color = secColor; + rgbSec = window.getComputedStyle(pseudoEl).color; + var regex = /rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/, + valuesP = regex.exec(rgbPrim), + valuesS = regex.exec(rgbSec), + rP = Math.round(parseFloat(valuesP[1])), + gP = Math.round(parseFloat(valuesP[2])), + bP = Math.round(parseFloat(valuesP[3])), + rS = Math.round(parseFloat(valuesS[1])), + gS = Math.round(parseFloat(valuesS[2])), + bS = Math.round(parseFloat(valuesS[3])), + rCur = parseInt((rP-rS)*percent+rS), + gCur = parseInt((gP-gS)*percent+gS), + bCur = parseInt((bP-bS)*percent+bS); + pseudoEl.parentNode.removeChild(pseudoEl); + return "rgb("+rCur+','+gCur+','+bCur+')'; + } } From 7c72dc1b41c21828e635a972a743106b2ae541bf Mon Sep 17 00:00:00 2001 From: Jose Martinez Date: Mon, 25 Mar 2013 10:56:12 -0400 Subject: [PATCH 29/47] fixes issue #6. merged callado4's fix --- Chart.js | 90 +++++++++++++++++++++------------------------------- Chart.min.js | 41 +----------------------- 2 files changed, 38 insertions(+), 93 deletions(-) diff --git a/Chart.js b/Chart.js index bc50d14a724..6bd4618d908 100755 --- a/Chart.js +++ b/Chart.js @@ -560,11 +560,7 @@ var Chart = function(context, tooltipOptions){ graphMin : config.scaleStartValue, labels : [] } - for (var i=0; i maxSteps) { - if (numberOfSteps < minSteps){ - stepValue /= 2; - numberOfSteps = Math.round(graphRange/stepValue); - } - else{ - stepValue *=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - }; - + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphRange = graphMax - graphMin; + + stepValue = Math.pow(10, rangeOrderOfMagnitude); + + numberOfSteps = Math.round(graphRange / stepValue); + + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; - - //Create an array of all the labels by interpolating the string. - - var labels = []; - - if(labelTemplateString){ - //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. - for (var i=1; i=f)E(l);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var g=a.animation?1/B(a.animationSteps,Number.MAX_VALUE,1):1,h=C[a.animationEasing],f=a.animation?0:1;"function"!==typeof c&&(c=function(){});E(l)}function D(a,c,b,d,l,g){var h;a= -Math.floor(Math.log(d-l)/Math.LN10);l=Math.floor(l/(1*Math.pow(10,a)))*Math.pow(10,a);h=Math.ceil(d/(1*Math.pow(10,a)))*Math.pow(10,a)-l;d=Math.pow(10,a);for(a=Math.round(h/d);ac;)d=ac?c:!isNaN(parseFloat(b))&&isFinite(b)&&a)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c?b(c):b}var r= -this,C={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)?0.5*a*a*a* -a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==a?1:1* -(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);da?-0.5*d*Math.pow(2,10*(a-=1))* -Math.sin((1*a-c)*2*Math.PI/b):0.5*d*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-C.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*a+0.75): -a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*C.easeInBounce(2*a):0.5*C.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0, -scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce", -animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?z(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)", -scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?z(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a, -c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?z(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1, -onAnimationComplete:null};var b=c?z(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0, -pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?z(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'", -scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?z(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var d,l,g,h,f,m,j,e,k;f=Math.min.apply(Math,[q,u])/2;f-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]); -e=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(e+=2*c.scaleBackdropPaddingY,f-=1.5*c.scaleBackdropPaddingY);k=f;e=e?e:5;d=Number.MIN_VALUE;l=Number.MAX_VALUE;for(g=0;gd&&(d=a[g].value),a[g].valuek&&(k=l);f-=Math.max.apply(Math,[k,1.5*(c.pointLabelFontSize/ -2)]);f-=c.pointLabelFontSize;k=f=B(f,null,0);e=e?e:5;d=Number.MIN_VALUE;l=Number.MAX_VALUE;for(g=0;gd&&(d=a.datasets[g].data[h]),a.datasets[g].data[h]Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[e],g,-h)}b.restore()},function(d){var e= -2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);b.rotate(e);for(var f=0;ft?d:t;q/a.labels.lengthd&&(d=a.datasets[g].data[h]),a.datasets[g].data[h]e?l:e;e+=10}r=q-e-t;k=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=f+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0t?d:t;q/a.labels.lengthd&&(d=a.datasets[g].data[h]),a.datasets[g].data[h]e?l:e;e+=10}r=q-e-t;k=Math.floor(r/a.labels.length);s=(k-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=f+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor; -b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();00||window.pageYOffset>0?(b-=window.pageXOffset,c-=window.pageYOffset):(document.body.scrollLeft>0||document.body.scrollTop>0)&&(b-=document.body.scrollLeft,c-=document.body.scrollTop),{x:b,y:c}}function q(a,b,c){var d=b.steps*b.stepValue,e=a-b.graphMin,f=z(e/d,1,0);return c*b.steps*f}function r(a,b,c,e){function i(){var d=a.animation?z(g(h),null,0):1;j(e),a.scaleOverlay?(c(d),b()):(b(),c(d))}function k(){h+=f,i(),1>=h?s(k):"function"==typeof a.onAnimationComplete&&a.onAnimationComplete()}var f=a.animation?1/z(a.animationSteps,Number.MAX_VALUE,1):1,g=d[a.animationEasing],h=a.animation?0:1;"function"!=typeof b&&(b=function(){}),s(k)}function t(a,b,c,d,e,f){function p(a){return Math.floor(Math.log(a)/Math.LN10)}var g,h,i,j,k,l,m;for(l=d-e,m=p(l),g=Math.floor(e/(1*Math.pow(10,m)))*Math.pow(10,m),h=Math.ceil(d/(1*Math.pow(10,m)))*Math.pow(10,m),i=h-g,j=Math.pow(10,m),k=Math.round(i/j);c>k||k>b;)c>k?(j/=2,k=Math.round(i/j)):(j*=2,k=Math.round(i/j));var o=[];return u(f,o,k,g,j),{steps:k,stepValue:j,graphMin:g,labels:o}}function u(a,b,c,d,e){if(a)for(var f=1;c+1>f;f++)b.push(D(a,{value:(d+e*f).toFixed(A(e))}))}function v(a){return Math.max.apply(Math,a)}function w(a){return Math.min.apply(Math,a)}function x(a,b){return a?a:b}function y(a){return!isNaN(parseFloat(a))&&isFinite(a)}function z(a,b,c){return y(b)&&a>b?b:y(c)&&c>a?c:a}function A(a){return 0!=a%1?(""+a).split(".")[1].length:0}function B(a,b){var c={};for(var d in a)c[d]=a[d];for(var d in b)c[d]=b[d];return c}function D(a,b){var c=/\W/.test(a)?Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):C[a]=C[a]||D(document.getElementById(a).innerHTML);return b?c(b):c}function E(a,b,c){var e,f,d=document.createElement("div");d.style.color=b,document.body.appendChild(d),e=window.getComputedStyle(d).color,d.style.color=c,f=window.getComputedStyle(d).color;var g=/rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/,h=g.exec(e),i=g.exec(f),j=Math.round(parseFloat(h[1])),k=Math.round(parseFloat(h[2])),l=Math.round(parseFloat(h[3])),m=Math.round(parseFloat(i[1])),n=Math.round(parseFloat(i[2])),o=Math.round(parseFloat(i[3])),p=parseInt((j-m)*a+m),q=parseInt((k-n)*a+n),r=parseInt((l-o)*a+o);return d.parentNode.removeChild(d),"rgb("+p+","+q+","+r+")"}var c=this,d={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=.5)?.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=.5)?.5*a*a*a:.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=.5)?.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=.5)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=.5)?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return a>=1?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=.5)?-0.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;if(c||(c=.3),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return-(d*Math.pow(2,10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c))},easeOutElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;if(c||(c=.3),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return d*Math.pow(2,-10*a)*Math.sin((1*a-b)*2*Math.PI/c)+1},easeInOutElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(2==(a/=.5))return 1;if(c||(c=1*.3*1.5),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return 1>a?-.5*d*Math.pow(2,10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c):.5*d*Math.pow(2,-10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c)+1},easeInBack:function(a){var b=1.70158;return 1*(a/=1)*a*((b+1)*a-b)},easeOutBack:function(a){var b=1.70158;return 1*((a=a/1-1)*a*((b+1)*a+b)+1)},easeInOutBack:function(a){var b=1.70158;return 1>(a/=.5)?.5*a*a*(((b*=1.525)+1)*a-b):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},easeInBounce:function(a){return 1-d.easeOutBounce(1-a)},easeOutBounce:function(a){return 1/2.75>(a/=1)?1*7.5625*a*a:2/2.75>a?1*(7.5625*(a-=1.5/2.75)*a+.75):2.5/2.75>a?1*(7.5625*(a-=2.25/2.75)*a+.9375):1*(7.5625*(a-=2.625/2.75)*a+.984375)},easeInOutBounce:function(a){return.5>a?.5*d.easeInBounce(2*a):.5*d.easeOutBounce(2*a-1)+.5}};this.tooltips=[],tooltipDefaults={background:"rgba(0,0,0,0.6)",fontFamily:"'Arial'",fontStyle:"normal",fontColor:"white",fontSize:"12px",padding:{top:10,right:10,bottom:10,left:10},offset:{left:0,top:0},border:{width:0,color:"#000"},showHighlight:!0,highlight:{stroke:{width:1,color:"rgba(230,230,230,0.25)"},fill:"rgba(255,255,255,0.25)"}},b=b?B(tooltipDefaults,b):tooltipDefaults;var f=function(a,d,e){this.ctx=a,this.areaObj=d,this.data=e,this.savedState=null,this.highlightState=null,this.x=null,this.y=null,this.inRange=function(a,b){if(this.areaObj.type)switch(this.areaObj.type){case"rect":return a>=this.areaObj.x&&this.areaObj.x+this.areaObj.width>=a&&b>=this.areaObj.y&&this.areaObj.y+this.areaObj.height>=b;case"circle":return Math.pow(a-this.areaObj.x,2)+Math.pow(b-this.areaObj.y,2)++e;g=e)(b>=c[e].y&&c[g].y>b||b>=c[g].y&&c[e].y>b)&&(c[g].x-c[e].x)*(b-c[e].y)/(c[g].y-c[e].y)+c[e].x>a&&(d=!d);return d}},this.render=function(d,f){if(null==this.savedState&&(this.ctx.putImageData(c.savedState,0,0),this.savedState=this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height)),this.ctx.putImageData(this.savedState,0,0),b.showHighlight)if(null==this.highlightState){switch(this.ctx.strokeStyle=b.highlight.stroke.color,this.ctx.lineWidth=b.highlight.stroke.width,this.ctx.fillStyle=b.highlight.fill,this.areaObj.type){case"rect":this.ctx.strokeRect(this.areaObj.x,this.areaObj.y,this.areaObj.width,this.areaObj.height),this.ctx.fillStyle=b.highlight.fill,this.ctx.fillRect(this.areaObj.x,this.areaObj.y,this.areaObj.width,this.areaObj.height);break;case"circle":this.ctx.beginPath(),this.ctx.arc(this.areaObj.x,this.areaObj.y,this.areaObj.r,0,2*Math.PI,!1),this.ctx.stroke(),this.ctx.fill();break;case"shape":this.ctx.beginPath(),this.ctx.moveTo(this.areaObj.points[0].x,this.areaObj.points[0].y);for(var g in this.areaObj.points)this.ctx.lineTo(this.areaObj.points[g].x,this.areaObj.points[g].y);this.ctx.stroke(),this.ctx.fill()}this.highlightState=this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height)}else this.ctx.putImageData(this.highlightState,0,0);if(this.x!=d||this.y!=f){var h=d+b.offset.left,i=f+b.offset.top,j=b.padding.left+this.ctx.measureText(this.data).width+b.padding.right;h+j>a.canvas.width&&(h-=0>h-j?h:j),i+24>a.canvas.height&&(i-=24),this.ctx.fillStyle=b.background,this.ctx.fillRect(h,i,j,24),b.border.width>0&&(this.ctx.fillStyle=b.border.color,this.ctx.lineWidth=b.border.width,this.ctx.strokeRect(h,i,j,24)),this.ctx.font=b.fontStyle+" "+b.fontSize+" "+b.fontFamily,this.ctx.fillStyle=b.fontColor,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText(e,h+j/2,i+12),this.x=d,this.y=f}}},g=a.canvas.width,h=a.canvas.height;this.savedState=null,a.canvas.onmousemove=function(b){if(c.tooltips.length>0){c.savedState=null==c.savedState?a.getImageData(0,0,a.canvas.width,a.canvas.height):c.savedState;var d=0;for(var e in c.tooltips){var f=i(a.canvas),g=b.clientX-f.x,h=b.clientY-f.y;c.tooltips[e].inRange(g,h)&&(c.tooltips[e].render(g,h),d++)}0==d&&a.putImageData(c.savedState,0,0)}},a.canvas.onmouseout=function(){null!=c.savedState&&a.putImageData(c.savedState,0,0)},window.devicePixelRatio&&(a.canvas.style.width=g+"px",a.canvas.style.height=h+"px",a.canvas.height=h*window.devicePixelRatio,a.canvas.width=g*window.devicePixelRatio,a.scale(window.devicePixelRatio,window.devicePixelRatio)),this.PolarArea=function(b,d){c.PolarArea.defaults={scaleOverlay:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var e=d?B(c.PolarArea.defaults,d):c.PolarArea.defaults;return new k(b,e,a)},this.Radar=function(b,d){c.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Radar.defaults,d):c.Radar.defaults;return new l(b,e,a)},this.Pie=function(b,d){c.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,labelFontFamily:"'Arial'",labelFontStyle:"normal",labelFontSize:12,labelFontColor:"#666",showTooltips:!0};var e=d?B(c.Pie.defaults,d):c.Pie.defaults;return new m(b,e,a)},this.Doughnut=function(b,d){c.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Doughnut.defaults,d):c.Doughnut.defaults;return new n(b,e,a)},this.Line=function(b,d){c.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Line.defaults,d):c.Line.defaults;return new o(b,e,a)},this.Bar=function(b,d){c.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Bar.defaults,d):c.Bar.defaults;return new p(b,e,a)};var j=function(a){a.clearRect(0,0,g,h)},k=function(a,b,c){function n(){d=w([g,h])/2,d-=v([.5*b.scaleFontSize,.5*b.scaleLineWidth]),j=2*b.scaleFontSize,b.scaleShowLabelBackdrop&&(j+=2*b.scaleBackdropPaddingY,d-=1.5*b.scaleBackdropPaddingY),k=d,j=x(j,5)}function o(){for(var a=0;i.steps>a;a++)if(b.scaleShowLine&&(c.beginPath(),c.arc(g/2,h/2,f*(a+1),0,2*Math.PI,!0),c.strokeStyle=b.scaleLineColor,c.lineWidth=b.scaleLineWidth,c.stroke()),b.scaleShowLabels){c.textAlign="center",c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;var d=i.labels[a];if(b.scaleShowLabelBackdrop){var e=c.measureText(d).width;c.fillStyle=b.scaleBackdropColor,c.beginPath(),c.rect(Math.round(g/2-e/2-b.scaleBackdropPaddingX),Math.round(h/2-f*(a+1)-.5*b.scaleFontSize-b.scaleBackdropPaddingY),Math.round(e+2*b.scaleBackdropPaddingX),Math.round(b.scaleFontSize+2*b.scaleBackdropPaddingY)),c.fill()}c.textBaseline="middle",c.fillStyle=b.scaleFontColor,c.fillText(d,g/2,h/2-f*(a+1))}}function p(d){var j=-Math.PI/2,k=2*Math.PI/a.length,l=1,m=1;b.animation&&(b.animateScale&&(l=d),b.animateRotate&&(m=d));for(var n=0;a.length>n;n++){if(c.beginPath(),c.arc(g/2,h/2,l*q(a[n].value,i,f),j,j+m*k,!1),c.lineTo(g/2,h/2),c.closePath(),c.fillStyle=a[n].color,c.fill(),d>.9999999){var o=void 0!=a[n].label&&""!=a[n].label?a[n].label+": "+a[n].value:a[n].value,p=[{x:g/2,y:h/2}],r=50,s=q(a[n].value,i,f);p.push({x:g/2+s*Math.cos(j),y:h/2+s*Math.sin(j)});for(var t=0;r>=t;t++)p.push({x:g/2+s*Math.cos(j+t/r*m*k),y:h/2+s*Math.sin(j+t/r*m*k)});e(c,{type:"shape",points:p},o,"PolarArea")}b.segmentShowStroke&&(c.strokeStyle=b.segmentStrokeColor,c.lineWidth=b.segmentStrokeWidth,c.stroke()),j+=m*k}}function s(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;a.length>d;d++)a[d].value>b&&(b=a[d].value),c>a[d].value&&(c=a[d].value);var e=Math.floor(k/(.66*j)),f=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:e,minSteps:f}}var d,f,i,j,k,l,m;n(),l=s(),m=b.scaleShowLabels?b.scaleLabel:null,b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=d/i.steps,r(b,o,p,c)},l=function(a,b,c){function n(d){var j=2*Math.PI/a.datasets[0].data.length;c.save(),c.translate(g/2,h/2);for(var k=0;a.datasets.length>k;k++){var l=q(a.datasets[k].data[0],i,f);if(c.beginPath(),c.moveTo(0,d*-1*l),1==d){var m=g/2+l*Math.cos(0-Math.PI/2),n=h/2+l*Math.sin(0-Math.PI/2),o=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,p=""!=a.labels[0].trim()?a.labels[0]+": "+a.datasets[k].data[0]:a.datasets[k].data[0];e(c,{type:"circle",x:m,y:n,r:o},p,"Radar")}for(var r=1;a.datasets[k].data.length>r;r++)if(l=q(a.datasets[k].data[r],i,f),c.rotate(j),c.lineTo(0,d*-1*l),1==d){var m=g/2+l*Math.cos(r*j-Math.PI/2),n=h/2+l*Math.sin(r*j-Math.PI/2),o=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,p=""!=a.labels[r].trim()?a.labels[r]+": "+a.datasets[k].data[r]:a.datasets[k].data[r];e(c,{type:"circle",x:m,y:n,r:o},p,"Radar")}if(c.closePath(),c.fillStyle=a.datasets[k].fillColor,c.strokeStyle=a.datasets[k].strokeColor,c.lineWidth=b.datasetStrokeWidth,c.fill(),c.stroke(),b.pointDot){c.fillStyle=a.datasets[k].pointColor,c.strokeStyle=a.datasets[k].pointStrokeColor,c.lineWidth=b.pointDotStrokeWidth;for(var s=0;a.datasets[k].data.length>s;s++)c.rotate(j),c.beginPath(),c.arc(0,d*-1*q(a.datasets[k].data[s],i,f),b.pointDotRadius,2*Math.PI,!1),c.fill(),c.stroke()}c.rotate(j)}c.restore()}function o(){var e=2*Math.PI/a.datasets[0].data.length;if(c.save(),c.translate(g/2,h/2),b.angleShowLineOut){c.strokeStyle=b.angleLineColor,c.lineWidth=b.angleLineWidth;for(var j=0;a.datasets[0].data.length>j;j++)c.rotate(e),c.beginPath(),c.moveTo(0,0),c.lineTo(0,-d),c.stroke()}for(var k=0;i.steps>k;k++){if(c.beginPath(),b.scaleShowLine){c.strokeStyle=b.scaleLineColor,c.lineWidth=b.scaleLineWidth,c.moveTo(0,-f*(k+1));for(var l=0;a.datasets[0].data.length>l;l++)c.rotate(e),c.lineTo(0,-f*(k+1));c.closePath(),c.stroke()}if(b.scaleShowLabels){if(c.textAlign="center",c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,c.textBaseline="middle",b.scaleShowLabelBackdrop){var m=c.measureText(i.labels[k]).width;c.fillStyle=b.scaleBackdropColor,c.beginPath(),c.rect(Math.round(-m/2-b.scaleBackdropPaddingX),Math.round(-f*(k+1)-.5*b.scaleFontSize-b.scaleBackdropPaddingY),Math.round(m+2*b.scaleBackdropPaddingX),Math.round(b.scaleFontSize+2*b.scaleBackdropPaddingY)),c.fill()}c.fillStyle=b.scaleFontColor,c.fillText(i.labels[k],0,-f*(k+1))}}for(var n=0;a.labels.length>n;n++){c.font=b.pointLabelFontStyle+" "+b.pointLabelFontSize+"px "+b.pointLabelFontFamily,c.fillStyle=b.pointLabelFontColor;var o=Math.sin(e*n)*(d+b.pointLabelFontSize),p=Math.cos(e*n)*(d+b.pointLabelFontSize);c.textAlign=e*n==Math.PI||0==e*n?"center":e*n>Math.PI?"right":"left",c.textBaseline="middle",c.fillText(a.labels[n],o,-p)}c.restore()}function p(){d=w([g,h])/2,j=2*b.scaleFontSize;for(var e=0,f=0;a.labels.length>f;f++){c.font=b.pointLabelFontStyle+" "+b.pointLabelFontSize+"px "+b.pointLabelFontFamily;var i=c.measureText(a.labels[f]).width;i>e&&(e=i)}d-=v([e,1.5*(b.pointLabelFontSize/2)]),d-=b.pointLabelFontSize,d=z(d,null,0),k=d,j=x(j,5)}function s(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;a.datasets.length>d;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m;a.labels||(a.labels=[]),p();var l=s();m=b.scaleShowLabels?b.scaleLabel:null,b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=d/i.steps,r(b,o,n,c)},m=function(a,b,c){function j(i){var j=-Math.PI/2,k=1,l=1;b.animation&&(b.animateScale&&(k=i),b.animateRotate&&(l=i));for(var m=0;a.length>m;m++){var n=l*a[m].value/d*2*Math.PI;if(c.beginPath(),c.arc(g/2,h/2,k*f,j,j+n),c.lineTo(g/2,h/2),c.closePath(),c.fillStyle=a[m].color,c.fill(),a[m].label&&2*k*f*n/(2*Math.PI)>b.labelFontSize){var o=a[m].labelFontSize||b.labelFontSize+"px";null!=o.match(/^[0-9]+$/g)&&(o+="px"),c.font=b.labelFontStyle+" "+o+" "+b.labelFontFamily,c.fillStyle=E(i,a[m].labelColor||"black",a[m].color);var p=-(j+n)+n/2,q=g/2+k*f*Math.cos(p)-10,r=h/2-k*f*Math.sin(p);c.textAlign="right",-Math.PI/2>p&&(p-=Math.PI,c.textAlign="left",q+=20),c.translate(q,r),c.rotate(-p),c.fillText(a[m].label,0,0),c.rotate(p),c.translate(-q,-r)}if(i>.9999999){var s=void 0!=a[m].label&&""!=a[m].label?a[m].label+": "+a[m].value:a[m].value,t=[{x:g/2,y:h/2}],u=50;t.push({x:g/2+f*Math.cos(j),y:h/2+f*Math.sin(j)});for(var v=0;u>=v;v++)t.push({x:g/2+f*Math.cos(j+v/u*n),y:h/2+f*Math.sin(j+v/u*n)});e(c,{type:"shape",points:t},s,"Pie")}b.segmentShowStroke&&(c.lineWidth=b.segmentStrokeWidth,c.strokeStyle=b.segmentStrokeColor,c.stroke()),j+=n}}for(var d=0,f=w([h/2,g/2])-5,i=0;a.length>i;i++)d+=a[i].value;c.fillStyle="black",c.textBaseline="base",r(b,null,j,c)},n=function(a,b,c){function k(j){var k=-Math.PI/2,l=1,m=1;b.animation&&(b.animateScale&&(l=j),b.animateRotate&&(m=j));for(var n=0;a.length>n;n++){var o=m*a[n].value/d*2*Math.PI;if(c.beginPath(),c.arc(g/2,h/2,l*f,k,k+o,!1),c.arc(g/2,h/2,l*i,k+o,k,!0),c.closePath(),c.fillStyle=a[n].color,c.fill(),j>.9999999){var p=void 0!=a[n].label&&""!=a[n].label?a[n].label+": "+a[n].value:a[n].value,q=[],r=50;q.push({x:g/2+f*Math.cos(k),y:h/2+f*Math.sin(k)});for(var s=0;r>=s;s++)q.push({x:g/2+f*Math.cos(k+s/r*o),y:h/2+f*Math.sin(k+s/r*o)});q.push({x:g/2+i*Math.cos(k+o),y:h/2+i*Math.sin(k+o)});for(var s=r;s>=0;s--)q.push({x:g/2+i*Math.cos(k+s/r*o),y:h/2+i*Math.sin(k+s/r*o)});e(c,{type:"shape",points:q},p,"Doughnut")}b.segmentShowStroke&&(c.lineWidth=b.segmentStrokeWidth,c.strokeStyle=b.segmentStrokeColor,c.stroke()),k+=o}}for(var d=0,f=w([h/2,g/2])-5,i=f*(b.percentageInnerCutout/100),j=0;a.length>j;j++)d+=a[j].value;r(b,null,k,c)},o=function(a,b,c){function x(d){function m(b,c){return v-d*q(a.datasets[b].data[c],i,f)}function o(a){return s+n*a}for(var g=0;a.datasets.length>g;g++){c.strokeStyle=a.datasets[g].strokeColor,c.lineWidth=b.datasetStrokeWidth,c.beginPath(),c.moveTo(s,v-d*q(a.datasets[g].data[0],i,f));for(var h=1;a.datasets[g].data.length>h;h++)b.bezierCurve?c.bezierCurveTo(o(h-.5),m(g,h-1),o(h-.5),m(g,h),o(h),m(g,h)):c.lineTo(o(h),m(g,h));for(var j=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,h=0;a.datasets[g].data.length>h;h++)if(1==d){var k=""!=(""+a.labels[h]).trim()?a.labels[h]+": "+a.datasets[g].data[h]:a.datasets[g].data[h];e(c,{type:"circle",x:o(h),y:m(g,h),r:j},k,"Line")}if(c.stroke(),b.datasetFill?(c.lineTo(s+n*(a.datasets[g].data.length-1),v),c.lineTo(s,v),c.closePath(),c.fillStyle=a.datasets[g].fillColor,c.fill()):c.closePath(),b.pointDot){c.fillStyle=a.datasets[g].pointColor,c.strokeStyle=a.datasets[g].pointStrokeColor,c.lineWidth=b.pointDotStrokeWidth;for(var l=0;a.datasets[g].data.length>l;l++)c.beginPath(),c.arc(s+n*l,v-d*q(a.datasets[g].data[l],i,f),b.pointDotRadius,0,2*Math.PI,!0),c.fill(),c.stroke()}}}function y(){c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(g-o/2+5,v),c.lineTo(g-o/2-p-5,v),c.stroke(),w>0?(c.save(),c.textAlign="right"):c.textAlign="center",c.fillStyle=b.scaleFontColor;for(var d=0;a.labels.length>d;d++)c.save(),w>0?(c.translate(s+d*n,v+b.scaleFontSize),c.rotate(-(w*(Math.PI/180))),c.fillText(a.labels[d],0,0),c.restore()):c.fillText(a.labels[d],s+d*n,v+b.scaleFontSize+3),c.beginPath(),c.moveTo(s+d*n,v+3),b.scaleShowGridLines&&d>0?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+d*n,5)):c.lineTo(s+d*n,v+3),c.stroke();c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(s,v+5),c.lineTo(s,5),c.stroke(),c.textAlign="right",c.textBaseline="middle";for(var e=0;i.steps>e;e++)c.beginPath(),c.moveTo(s-3,v-(e+1)*f),b.scaleShowGridLines?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+p+5,v-(e+1)*f)):c.lineTo(s-.5,v-(e+1)*f),c.stroke(),b.scaleShowLabels&&c.fillText(i.labels[e],s-8,v-(e+1)*f)}function z(){var d=1;if(b.scaleShowLabels){c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;for(var e=0;i.labels.length>e;e++){var f=c.measureText(i.labels[e]).width;d=f>d?f:d}d+=10}p=g-d-o,n=Math.floor(p/(a.labels.length-1)),s=g-o/2-p,v=k+b.scaleFontSize/2}function A(){d=h,c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,o=1;for(var e=0;a.labels.length>e;e++){var f=c.measureText(a.labels[e]).width;o=f>o?f:o}o>g/a.labels.length?(w=45,g/a.labels.lengthd;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m,n,o,p,s,v,w=0;A(),l=B(),m=b.scaleShowLabels?b.scaleLabel:"",b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=Math.floor(k/i.steps),z(),r(b,y,x,c)},p=function(a,b,c){function y(d){c.lineWidth=b.barStrokeWidth;for(var g=0;a.datasets.length>g;g++){c.fillStyle=a.datasets[g].fillColor,c.strokeStyle=a.datasets[g].strokeColor;for(var h=0;a.datasets[g].data.length>h;h++){var j=s+b.barValueSpacing+n*h+w*g+b.barDatasetSpacing*g+b.barStrokeWidth*g;if(c.beginPath(),c.moveTo(j,v),c.lineTo(j,v-d*q(a.datasets[g].data[h],i,f)+b.barStrokeWidth/2),c.lineTo(j+w,v-d*q(a.datasets[g].data[h],i,f)+b.barStrokeWidth/2),c.lineTo(j+w,v),b.barShowStroke&&c.stroke(),c.closePath(),c.fill(),1==d){var k=j,l=q(a.datasets[g].data[h],i,f),m=v-l,o=w,p=""!=a.labels[h]?a.labels[h]+": "+a.datasets[g].data[h]:a.datasets[g].data[h];e(c,{type:"rect",x:k,y:m,width:o,height:l},p,"Bar")}}}}function z(){c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(g-o/2+5,v),c.lineTo(g-o/2-p-5,v),c.stroke(),x>0?(c.save(),c.textAlign="right"):c.textAlign="center",c.fillStyle=b.scaleFontColor;for(var d=0;a.labels.length>d;d++)c.save(),x>0?(c.translate(s+d*n,v+b.scaleFontSize),c.rotate(-(x*(Math.PI/180))),c.fillText(a.labels[d],0,0),c.restore()):c.fillText(a.labels[d],s+d*n+n/2,v+b.scaleFontSize+3),c.beginPath(),c.moveTo(s+(d+1)*n,v+3),c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+(d+1)*n,5),c.stroke();c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(s,v+5),c.lineTo(s,5),c.stroke(),c.textAlign="right",c.textBaseline="middle";for(var e=0;i.steps>e;e++)c.beginPath(),c.moveTo(s-3,v-(e+1)*f),b.scaleShowGridLines?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+p+5,v-(e+1)*f)):c.lineTo(s-.5,v-(e+1)*f),c.stroke(),b.scaleShowLabels&&c.fillText(i.labels[e],s-8,v-(e+1)*f)}function A(){var d=1;if(b.scaleShowLabels){c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;for(var e=0;i.labels.length>e;e++){var f=c.measureText(i.labels[e]).width;d=f>d?f:d}d+=10}p=g-d-o,n=Math.floor(p/a.labels.length),w=(n-2*b.scaleGridLineWidth-2*b.barValueSpacing-(b.barDatasetSpacing*a.datasets.length-1)-(b.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length,s=g-o/2-p,v=k+b.scaleFontSize/2}function B(){d=h,c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,o=1;for(var e=0;a.labels.length>e;e++){var f=c.measureText(a.labels[e]).width;o=f>o?f:o}o>g/a.labels.length?(x=45,g/a.labels.lengthd;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m,n,o,p,s,v,w,x=0;B(),l=C(),m=b.scaleShowLabels?b.scaleLabel:"",b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=Math.floor(k/i.steps),A(),r(b,z,y,c)},s=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),C={}}; \ No newline at end of file From be88b6e69ad16833c5190ba0ac694dc1c925703c Mon Sep 17 00:00:00 2001 From: default_git_name Date: Mon, 15 Apr 2013 06:03:51 +0000 Subject: [PATCH 30/47] added tooltip template configuration --- Chart.js | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/Chart.js b/Chart.js index 6bd4618d908..bcf2b591552 100755 --- a/Chart.js +++ b/Chart.js @@ -145,6 +145,7 @@ var Chart = function(context, tooltipOptions){ fontStyle : "normal", fontColor: 'white', fontSize: '12px', + labelTemplate: '<%=label%>: <%=value%>', padding: { top: 10, right: 10, @@ -248,10 +249,11 @@ var Chart = function(context, tooltipOptions){ this.ctx.putImageData(this.highlightState,0,0); } } - if(this.x != x || this.y != y) { + //if(this.x != x || this.y != y) { var posX = x+tooltipOptions.offset.left, posY = y+tooltipOptions.offset.top, - rectWidth = tooltipOptions.padding.left+this.ctx.measureText(this.data).width+tooltipOptions.padding.right; + tpl = tmpl(tooltipOptions.labelTemplate, this.data), + rectWidth = tooltipOptions.padding.left+this.ctx.measureText(tpl).width+tooltipOptions.padding.right; if(posX + rectWidth > ctx.canvas.width) { posX -= posX-rectWidth < 0 ? posX : rectWidth; } @@ -269,10 +271,10 @@ var Chart = function(context, tooltipOptions){ this.ctx.fillStyle = tooltipOptions.fontColor; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; - this.ctx.fillText(data, posX+rectWidth/2, posY+12); + this.ctx.fillText(tpl, posX+rectWidth/2, posY+12); this.x = x; this.y = y; - } + //} } } @@ -643,15 +645,14 @@ var Chart = function(context, tooltipOptions){ ctx.fill(); if(animationDecimal > 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [{x:width/2,y:height/2}], + var points = [{x:width/2,y:height/2}], pAmount = 50, radius = calculateOffset(data[i].value,calculatedScale,scaleHop); points.push({x:width/2+radius*Math.cos(startAngle),y:height/2+radius*Math.sin(startAngle)}); for(var p = 0; p <= pAmount; p++) { points.push({x:width/2+radius*Math.cos(startAngle+p/pAmount*rotateAnimation*angleStep),y:height/2+radius*Math.sin(startAngle+p/pAmount*rotateAnimation*angleStep)}); } - registerTooltip(ctx,{type:'shape',points:points},ttData,'PolarArea'); + registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'PolarArea'); } if(config.segmentShowStroke){ @@ -732,7 +733,7 @@ var Chart = function(context, tooltipOptions){ curY = height/2+offset*Math.sin(0-Math.PI/2), pointRadius = config.pointDot ? config.pointDotRadius+config.pointDotStrokeWidth : 10, ttData = data.labels[0].trim() != "" ? data.labels[0]+": "+data.datasets[i].data[0] : data.datasets[i].data[0]; - registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},ttData,'Radar'); + registerTooltip(ctx,{type:'circle',x:curX,y:curY,r:pointRadius},{label:data.labels[0],value:data.datasets[i].data[0]},'Radar'); } for (var j=1; j 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [{x:width/2,y:height/2}], + var points = [{x:width/2,y:height/2}], pAmount = 50; points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle)}); for(var p = 0; p <= pAmount; p++) { points.push({x:width/2+pieRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+pieRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); } - registerTooltip(ctx,{type:'shape',points:points},ttData,'Pie'); + registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Pie'); } if(config.segmentShowStroke){ @@ -1018,8 +1018,7 @@ var Chart = function(context, tooltipOptions){ ctx.fill(); if(animationDecimal > 0.9999999) { - var ttData = data[i].label != undefined && data[i].label != "" ? data[i].label+": "+data[i].value : data[i].value, - points = [], + var points = [], pAmount = 50; points.push({x:width/2+doughnutRadius*Math.cos(cumulativeAngle),y:height/2+doughnutRadius*Math.sin(cumulativeAngle)}); for(var p = 0; p <= pAmount; p++) { @@ -1029,7 +1028,7 @@ var Chart = function(context, tooltipOptions){ for(var p = pAmount; p >= 0; p--) { points.push({x:width/2+cutoutRadius*Math.cos(cumulativeAngle+p/pAmount*segmentAngle),y:height/2+cutoutRadius*Math.sin(cumulativeAngle+p/pAmount*segmentAngle)}); } - registerTooltip(ctx,{type:'shape',points:points},ttData,'Doughnut'); + registerTooltip(ctx,{type:'shape',points:points},{label:data[i].label,value:data[i].value},'Doughnut'); } if(config.segmentShowStroke){ @@ -1090,8 +1089,7 @@ var Chart = function(context, tooltipOptions){ for(var j = 0; j < data.datasets[i].data.length; j++) { if(animPc == 1) { // register tooltips - var ttData = data.labels[j].toString().trim() != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; - registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},ttData,'Line'); + registerTooltip(ctx,{type:'circle',x:xPos(j),y:yPos(i,j),r:pointRadius},{label:data.labels[j],value:data.datasets[i].data[j]},'Line'); } } ctx.stroke(); @@ -1335,9 +1333,8 @@ var Chart = function(context, tooltipOptions){ var x = barOffset, height = calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop), y = xAxisPosY-height, - width = barWidth, - ttData = data.labels[j] != "" ? data.labels[j]+": "+data.datasets[i].data[j] : data.datasets[i].data[j]; - registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},ttData,'Bar'); + width = barWidth; + registerTooltip(ctx,{type:'rect',x:x,y:y,width:width,height:height},{label:data.labels[j],value:data.datasets[i].data[j]},'Bar'); } } } @@ -1662,7 +1659,13 @@ var Chart = function(context, tooltipOptions){ function mergeChartConfig(defaults,userDefined){ var returnObj = {}; for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } - for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + for (var attrname in userDefined) { + if(typeof(userDefined[attrname]) === "object" && defaults[attrname]) { + returnObj[attrname] = mergeChartConfig(defaults[attrname], userDefined[attrname]); + } else { + returnObj[attrname] = userDefined[attrname]; + } + } return returnObj; } From 0d5190030075b538e77f50fbe2eaf35f5dde784a Mon Sep 17 00:00:00 2001 From: default_git_name Date: Tue, 16 Apr 2013 05:31:48 +0000 Subject: [PATCH 31/47] removed some spaces --- Chart.js | 91 ++++++++++++++++++++++++-------------------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/Chart.js b/Chart.js index bcf2b591552..b38cabfcc4c 100755 --- a/Chart.js +++ b/Chart.js @@ -1555,61 +1555,51 @@ var Chart = function(context, tooltipOptions){ })(); function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ - var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; - - valueRange = maxValue - minValue; - - rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); - - graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); - - graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); - - graphRange = graphMax - graphMin; - - stepValue = Math.pow(10, rangeOrderOfMagnitude); - - numberOfSteps = Math.round(graphRange / stepValue); + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + valueRange = maxValue - minValue; + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + graphRange = graphMax - graphMin; + stepValue = Math.pow(10, rangeOrderOfMagnitude); + numberOfSteps = Math.round(graphRange / stepValue); - //Compare number of steps to the max and min for that size graph, and add in half steps if need be. - while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { - if (numberOfSteps < minSteps){ - stepValue /= 2; - numberOfSteps = Math.round(graphRange/stepValue); - } - else{ - stepValue *=2; - numberOfSteps = Math.round(graphRange/stepValue); - } - }; - - var labels = []; - populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); - - return { - steps : numberOfSteps, - stepValue : stepValue, - graphMin : graphMin, - labels : labels - + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); } - - function calculateOrderOfMagnitude(val){ - return Math.floor(Math.log(val) / Math.LN10); - } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + } + var labels = []; + populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); + return { + steps : numberOfSteps, + stepValue : stepValue, + graphMin : graphMin, + labels : labels + } + + function calculateOrderOfMagnitude(val){ + return Math.floor(Math.log(val) / Math.LN10); + } } - //Populate an array of all the labels by interpolating the string. - function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { - if (labelTemplateString) { - //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. - for (var i = 1; i < numberOfSteps + 1; i++) { - labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); - } - } - } + //Populate an array of all the labels by interpolating the string. + function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { + if (labelTemplateString) { + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + for (var i = 1; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); + } + } + } //Max value from array function Max( array ){ @@ -1727,6 +1717,3 @@ var Chart = function(context, tooltipOptions){ return "rgb("+rCur+','+gCur+','+bCur+')'; } } - - - From f82950132e240b6248c5d1bd9e71f5f55f1bbd24 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Tue, 16 Apr 2013 06:02:49 +0000 Subject: [PATCH 32/47] generalized tooltip options for more general options and defaults --- Chart.js | 90 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/Chart.js b/Chart.js index b38cabfcc4c..1a451d8d808 100755 --- a/Chart.js +++ b/Chart.js @@ -1,5 +1,5 @@ //Define the global Chart Variable as a class. -var Chart = function(context, tooltipOptions){ +var Chart = function(context, options){ var chart = this; @@ -139,37 +139,39 @@ var Chart = function(context, tooltipOptions){ }; this.tooltips = [], - tooltipDefaults = { - background: 'rgba(0,0,0,0.6)', - fontFamily : "'Arial'", - fontStyle : "normal", - fontColor: 'white', - fontSize: '12px', - labelTemplate: '<%=label%>: <%=value%>', - padding: { - top: 10, - right: 10, - bottom: 10, - left: 10 - }, - offset: { - left: 0, - top: 0 - }, - border: { - width: 0, - color: '#000' - }, - showHighlight: true, - highlight: { - stroke: { - width: 1, - color: 'rgba(230,230,230,0.25)' + defaults = { + tooltips: { + background: 'rgba(0,0,0,0.6)', + fontFamily : "'Arial'", + fontStyle : "normal", + fontColor: 'white', + fontSize: '12px', + labelTemplate: '<%=label%>: <%=value%>', + padding: { + top: 10, + right: 10, + bottom: 10, + left: 10 }, - fill: 'rgba(255,255,255,0.25)' + offset: { + left: 0, + top: 0 + }, + border: { + width: 0, + color: '#000' + }, + showHighlight: true, + highlight: { + stroke: { + width: 1, + color: 'rgba(230,230,230,0.25)' + }, + fill: 'rgba(255,255,255,0.25)' + } } }, - tooltipOptions = (tooltipOptions) ? mergeChartConfig(tooltipDefaults, tooltipOptions) : tooltipDefaults; + options = (options) ? mergeChartConfig(defaults, options) : defaults; function registerTooltip(ctx,areaObj,data,type) { chart.tooltips.push(new Tooltip( @@ -217,15 +219,15 @@ var Chart = function(context, tooltipOptions){ this.savedState = this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height); } this.ctx.putImageData(this.savedState,0,0); - if(tooltipOptions.showHighlight) { + if(options.tooltips.showHighlight) { if(this.highlightState == null) { - this.ctx.strokeStyle = tooltipOptions.highlight.stroke.color; - this.ctx.lineWidth = tooltipOptions.highlight.stroke.width; - this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.strokeStyle = options.tooltips.highlight.stroke.color; + this.ctx.lineWidth = options.tooltips.highlight.stroke.width; + this.ctx.fillStyle = options.tooltips.highlight.fill; switch(this.areaObj.type) { case 'rect': this.ctx.strokeRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); - this.ctx.fillStyle = tooltipOptions.highlight.fill; + this.ctx.fillStyle = options.tooltips.highlight.fill; this.ctx.fillRect(this.areaObj.x, this.areaObj.y, this.areaObj.width, this.areaObj.height); break; case 'circle': @@ -250,25 +252,25 @@ var Chart = function(context, tooltipOptions){ } } //if(this.x != x || this.y != y) { - var posX = x+tooltipOptions.offset.left, - posY = y+tooltipOptions.offset.top, - tpl = tmpl(tooltipOptions.labelTemplate, this.data), - rectWidth = tooltipOptions.padding.left+this.ctx.measureText(tpl).width+tooltipOptions.padding.right; + var posX = x+options.tooltips.offset.left, + posY = y+options.tooltips.offset.top, + tpl = tmpl(options.tooltips.labelTemplate, this.data), + rectWidth = options.tooltips.padding.left+this.ctx.measureText(tpl).width+options.tooltips.padding.right; if(posX + rectWidth > ctx.canvas.width) { posX -= posX-rectWidth < 0 ? posX : rectWidth; } if(posY + 24 > ctx.canvas.height) { posY -= 24; } - this.ctx.fillStyle = tooltipOptions.background; + this.ctx.fillStyle = options.tooltips.background; this.ctx.fillRect(posX, posY, rectWidth, 24); - if(tooltipOptions.border.width > 0) { - this.ctx.fillStyle = tooltipOptions.border.color; - this.ctx.lineWidth = tooltipOptions.border.width; + if(options.tooltips.border.width > 0) { + this.ctx.fillStyle = options.tooltips.order.color; + this.ctx.lineWidth = options.tooltips.border.width; this.ctx.strokeRect(posX, posY, rectWidth, 24); } - this.ctx.font = tooltipOptions.fontStyle+ " " +tooltipOptions.fontSize+" " + tooltipOptions.fontFamily; - this.ctx.fillStyle = tooltipOptions.fontColor; + this.ctx.font = options.tooltips.fontStyle+ " "+options.tooltips.fontSize+" " + options.tooltips.fontFamily; + this.ctx.fillStyle = options.tooltips.fontColor; this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle'; this.ctx.fillText(tpl, posX+rectWidth/2, posY+12); From 43a5186843c798a75b8570c9cf5348c2ed6709e6 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Tue, 16 Apr 2013 17:58:56 +0000 Subject: [PATCH 33/47] added pie label aligning --- Chart.js | 35 ++++++++++++++++++++++++++++++----- pie.html | 6 ++++-- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/Chart.js b/Chart.js index b38cabfcc4c..85f53a2260a 100755 --- a/Chart.js +++ b/Chart.js @@ -436,6 +436,7 @@ var Chart = function(context, tooltipOptions){ labelFontStyle : "normal", labelFontSize : 12, labelFontColor : "#666", + labelAlign : 'right', showTooltips : true }; @@ -938,28 +939,52 @@ var Chart = function(context, tooltipOptions){ ctx.fill(); if(data[i].label && scaleAnimation*pieRadius*2*segmentAngle/(2*Math.PI) > config.labelFontSize) { + function getPieLabelX(align, r) { + switch(align) { + case 'left': + return -r+20; + break; + case 'center': + return -r/2; + break; + } + return -10; + } + + function reversePieLabelAlign(align) { + switch(align) { + case 'left': return 'right'; break; + case 'right': return 'left'; break; + case 'center': return align; break; + } + } + var fontSize = data[i].labelFontSize || config.labelFontSize+'px'; + if(fontSize.match(/^[0-9]+$/g) != null) { fontSize = fontSize+'px'; } ctx.font = config.labelFontStyle+ " " +fontSize+" " + config.labelFontFamily; ctx.fillStyle = getFadeColor(animationDecimal, data[i].labelColor || 'black', data[i].color); + ctx.textBaseline = 'middle'; // rotate text, so it perfectly fits in segments var textRotation = -(cumulativeAngle + segmentAngle)+segmentAngle/2, - tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation)-10, + tX = width/2+scaleAnimation*pieRadius*Math.cos(textRotation), tY = height/2-scaleAnimation*pieRadius*Math.sin(textRotation); - ctx.textAlign = 'right'; + ctx.textAlign = data[i].labelAlign || config.labelAlign; + textX = getPieLabelX(ctx.textAlign, scaleAnimation*pieRadius); if(textRotation < -Math.PI/2) { textRotation -= Math.PI; - ctx.textAlign = 'left'; - tX += 20; + ctx.textAlign = reversePieLabelAlign(ctx.textAlign); + textX = -textX; } ctx.translate(tX, tY); ctx.rotate(-textRotation); - ctx.fillText(data[i].label, 0, 0); + ctx.fillText(data[i].label, textX, 0); ctx.rotate(textRotation); ctx.translate(-tX, -tY); } + if(animationDecimal > 0.9999999) { var points = [{x:width/2,y:height/2}], pAmount = 50; diff --git a/pie.html b/pie.html index cdbe2e86b40..155ad382999 100644 --- a/pie.html +++ b/pie.html @@ -21,14 +21,16 @@ color : "#F38630", label : 'Sleep', labelColor : 'white', - labelFontSize : '10' + labelFontSize : '10', + labelAlign : 'left' }, { value : 50, color : "#E0E4CC", label : 'Music', labelColor : '#444', - labelFontSize : '1.4em' + labelFontSize : '1.4em', + labelAlign: 'center' }, { value : 100, From 5097ad154d24ff87f51463cb99fdbfa54dffaa81 Mon Sep 17 00:00:00 2001 From: Christian Brandt Date: Tue, 16 Apr 2013 20:12:12 +0200 Subject: [PATCH 34/47] recompiled minified version --- Chart.min.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++- pie.html | 2 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Chart.min.js b/Chart.min.js index 9fe32f98414..d9fe32d37bb 100644 --- a/Chart.min.js +++ b/Chart.min.js @@ -1 +1,54 @@ -var Chart=function(a,b){function e(a,b,d,e){c.tooltips.push(new f(a,b,d,e))}function i(a){for(var b=0,c=0;a;)b+=a.offsetLeft+a.clientLeft,c+=a.offsetTop+a.clientTop,a=a.offsetParent;return window.pageXOffset>0||window.pageYOffset>0?(b-=window.pageXOffset,c-=window.pageYOffset):(document.body.scrollLeft>0||document.body.scrollTop>0)&&(b-=document.body.scrollLeft,c-=document.body.scrollTop),{x:b,y:c}}function q(a,b,c){var d=b.steps*b.stepValue,e=a-b.graphMin,f=z(e/d,1,0);return c*b.steps*f}function r(a,b,c,e){function i(){var d=a.animation?z(g(h),null,0):1;j(e),a.scaleOverlay?(c(d),b()):(b(),c(d))}function k(){h+=f,i(),1>=h?s(k):"function"==typeof a.onAnimationComplete&&a.onAnimationComplete()}var f=a.animation?1/z(a.animationSteps,Number.MAX_VALUE,1):1,g=d[a.animationEasing],h=a.animation?0:1;"function"!=typeof b&&(b=function(){}),s(k)}function t(a,b,c,d,e,f){function p(a){return Math.floor(Math.log(a)/Math.LN10)}var g,h,i,j,k,l,m;for(l=d-e,m=p(l),g=Math.floor(e/(1*Math.pow(10,m)))*Math.pow(10,m),h=Math.ceil(d/(1*Math.pow(10,m)))*Math.pow(10,m),i=h-g,j=Math.pow(10,m),k=Math.round(i/j);c>k||k>b;)c>k?(j/=2,k=Math.round(i/j)):(j*=2,k=Math.round(i/j));var o=[];return u(f,o,k,g,j),{steps:k,stepValue:j,graphMin:g,labels:o}}function u(a,b,c,d,e){if(a)for(var f=1;c+1>f;f++)b.push(D(a,{value:(d+e*f).toFixed(A(e))}))}function v(a){return Math.max.apply(Math,a)}function w(a){return Math.min.apply(Math,a)}function x(a,b){return a?a:b}function y(a){return!isNaN(parseFloat(a))&&isFinite(a)}function z(a,b,c){return y(b)&&a>b?b:y(c)&&c>a?c:a}function A(a){return 0!=a%1?(""+a).split(".")[1].length:0}function B(a,b){var c={};for(var d in a)c[d]=a[d];for(var d in b)c[d]=b[d];return c}function D(a,b){var c=/\W/.test(a)?Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):C[a]=C[a]||D(document.getElementById(a).innerHTML);return b?c(b):c}function E(a,b,c){var e,f,d=document.createElement("div");d.style.color=b,document.body.appendChild(d),e=window.getComputedStyle(d).color,d.style.color=c,f=window.getComputedStyle(d).color;var g=/rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/,h=g.exec(e),i=g.exec(f),j=Math.round(parseFloat(h[1])),k=Math.round(parseFloat(h[2])),l=Math.round(parseFloat(h[3])),m=Math.round(parseFloat(i[1])),n=Math.round(parseFloat(i[2])),o=Math.round(parseFloat(i[3])),p=parseInt((j-m)*a+m),q=parseInt((k-n)*a+n),r=parseInt((l-o)*a+o);return d.parentNode.removeChild(d),"rgb("+p+","+q+","+r+")"}var c=this,d={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=.5)?.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=.5)?.5*a*a*a:.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=.5)?.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=.5)?.5*a*a*a*a*a:.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=.5)?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return a>=1?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=.5)?-0.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;if(c||(c=.3),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return-(d*Math.pow(2,10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c))},easeOutElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;if(c||(c=.3),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return d*Math.pow(2,-10*a)*Math.sin((1*a-b)*2*Math.PI/c)+1},easeInOutElastic:function(a){var b=1.70158,c=0,d=1;if(0==a)return 0;if(2==(a/=.5))return 1;if(c||(c=1*.3*1.5),Math.abs(1)>d){d=1;var b=c/4}else var b=c/(2*Math.PI)*Math.asin(1/d);return 1>a?-.5*d*Math.pow(2,10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c):.5*d*Math.pow(2,-10*(a-=1))*Math.sin((1*a-b)*2*Math.PI/c)+1},easeInBack:function(a){var b=1.70158;return 1*(a/=1)*a*((b+1)*a-b)},easeOutBack:function(a){var b=1.70158;return 1*((a=a/1-1)*a*((b+1)*a+b)+1)},easeInOutBack:function(a){var b=1.70158;return 1>(a/=.5)?.5*a*a*(((b*=1.525)+1)*a-b):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},easeInBounce:function(a){return 1-d.easeOutBounce(1-a)},easeOutBounce:function(a){return 1/2.75>(a/=1)?1*7.5625*a*a:2/2.75>a?1*(7.5625*(a-=1.5/2.75)*a+.75):2.5/2.75>a?1*(7.5625*(a-=2.25/2.75)*a+.9375):1*(7.5625*(a-=2.625/2.75)*a+.984375)},easeInOutBounce:function(a){return.5>a?.5*d.easeInBounce(2*a):.5*d.easeOutBounce(2*a-1)+.5}};this.tooltips=[],tooltipDefaults={background:"rgba(0,0,0,0.6)",fontFamily:"'Arial'",fontStyle:"normal",fontColor:"white",fontSize:"12px",padding:{top:10,right:10,bottom:10,left:10},offset:{left:0,top:0},border:{width:0,color:"#000"},showHighlight:!0,highlight:{stroke:{width:1,color:"rgba(230,230,230,0.25)"},fill:"rgba(255,255,255,0.25)"}},b=b?B(tooltipDefaults,b):tooltipDefaults;var f=function(a,d,e){this.ctx=a,this.areaObj=d,this.data=e,this.savedState=null,this.highlightState=null,this.x=null,this.y=null,this.inRange=function(a,b){if(this.areaObj.type)switch(this.areaObj.type){case"rect":return a>=this.areaObj.x&&this.areaObj.x+this.areaObj.width>=a&&b>=this.areaObj.y&&this.areaObj.y+this.areaObj.height>=b;case"circle":return Math.pow(a-this.areaObj.x,2)+Math.pow(b-this.areaObj.y,2)++e;g=e)(b>=c[e].y&&c[g].y>b||b>=c[g].y&&c[e].y>b)&&(c[g].x-c[e].x)*(b-c[e].y)/(c[g].y-c[e].y)+c[e].x>a&&(d=!d);return d}},this.render=function(d,f){if(null==this.savedState&&(this.ctx.putImageData(c.savedState,0,0),this.savedState=this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height)),this.ctx.putImageData(this.savedState,0,0),b.showHighlight)if(null==this.highlightState){switch(this.ctx.strokeStyle=b.highlight.stroke.color,this.ctx.lineWidth=b.highlight.stroke.width,this.ctx.fillStyle=b.highlight.fill,this.areaObj.type){case"rect":this.ctx.strokeRect(this.areaObj.x,this.areaObj.y,this.areaObj.width,this.areaObj.height),this.ctx.fillStyle=b.highlight.fill,this.ctx.fillRect(this.areaObj.x,this.areaObj.y,this.areaObj.width,this.areaObj.height);break;case"circle":this.ctx.beginPath(),this.ctx.arc(this.areaObj.x,this.areaObj.y,this.areaObj.r,0,2*Math.PI,!1),this.ctx.stroke(),this.ctx.fill();break;case"shape":this.ctx.beginPath(),this.ctx.moveTo(this.areaObj.points[0].x,this.areaObj.points[0].y);for(var g in this.areaObj.points)this.ctx.lineTo(this.areaObj.points[g].x,this.areaObj.points[g].y);this.ctx.stroke(),this.ctx.fill()}this.highlightState=this.ctx.getImageData(0,0,this.ctx.canvas.width,this.ctx.canvas.height)}else this.ctx.putImageData(this.highlightState,0,0);if(this.x!=d||this.y!=f){var h=d+b.offset.left,i=f+b.offset.top,j=b.padding.left+this.ctx.measureText(this.data).width+b.padding.right;h+j>a.canvas.width&&(h-=0>h-j?h:j),i+24>a.canvas.height&&(i-=24),this.ctx.fillStyle=b.background,this.ctx.fillRect(h,i,j,24),b.border.width>0&&(this.ctx.fillStyle=b.border.color,this.ctx.lineWidth=b.border.width,this.ctx.strokeRect(h,i,j,24)),this.ctx.font=b.fontStyle+" "+b.fontSize+" "+b.fontFamily,this.ctx.fillStyle=b.fontColor,this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.fillText(e,h+j/2,i+12),this.x=d,this.y=f}}},g=a.canvas.width,h=a.canvas.height;this.savedState=null,a.canvas.onmousemove=function(b){if(c.tooltips.length>0){c.savedState=null==c.savedState?a.getImageData(0,0,a.canvas.width,a.canvas.height):c.savedState;var d=0;for(var e in c.tooltips){var f=i(a.canvas),g=b.clientX-f.x,h=b.clientY-f.y;c.tooltips[e].inRange(g,h)&&(c.tooltips[e].render(g,h),d++)}0==d&&a.putImageData(c.savedState,0,0)}},a.canvas.onmouseout=function(){null!=c.savedState&&a.putImageData(c.savedState,0,0)},window.devicePixelRatio&&(a.canvas.style.width=g+"px",a.canvas.style.height=h+"px",a.canvas.height=h*window.devicePixelRatio,a.canvas.width=g*window.devicePixelRatio,a.scale(window.devicePixelRatio,window.devicePixelRatio)),this.PolarArea=function(b,d){c.PolarArea.defaults={scaleOverlay:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var e=d?B(c.PolarArea.defaults,d):c.PolarArea.defaults;return new k(b,e,a)},this.Radar=function(b,d){c.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Radar.defaults,d):c.Radar.defaults;return new l(b,e,a)},this.Pie=function(b,d){c.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,labelFontFamily:"'Arial'",labelFontStyle:"normal",labelFontSize:12,labelFontColor:"#666",showTooltips:!0};var e=d?B(c.Pie.defaults,d):c.Pie.defaults;return new m(b,e,a)},this.Doughnut=function(b,d){c.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Doughnut.defaults,d):c.Doughnut.defaults;return new n(b,e,a)},this.Line=function(b,d){c.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Line.defaults,d):c.Line.defaults;return new o(b,e,a)},this.Bar=function(b,d){c.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var e=d?B(c.Bar.defaults,d):c.Bar.defaults;return new p(b,e,a)};var j=function(a){a.clearRect(0,0,g,h)},k=function(a,b,c){function n(){d=w([g,h])/2,d-=v([.5*b.scaleFontSize,.5*b.scaleLineWidth]),j=2*b.scaleFontSize,b.scaleShowLabelBackdrop&&(j+=2*b.scaleBackdropPaddingY,d-=1.5*b.scaleBackdropPaddingY),k=d,j=x(j,5)}function o(){for(var a=0;i.steps>a;a++)if(b.scaleShowLine&&(c.beginPath(),c.arc(g/2,h/2,f*(a+1),0,2*Math.PI,!0),c.strokeStyle=b.scaleLineColor,c.lineWidth=b.scaleLineWidth,c.stroke()),b.scaleShowLabels){c.textAlign="center",c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;var d=i.labels[a];if(b.scaleShowLabelBackdrop){var e=c.measureText(d).width;c.fillStyle=b.scaleBackdropColor,c.beginPath(),c.rect(Math.round(g/2-e/2-b.scaleBackdropPaddingX),Math.round(h/2-f*(a+1)-.5*b.scaleFontSize-b.scaleBackdropPaddingY),Math.round(e+2*b.scaleBackdropPaddingX),Math.round(b.scaleFontSize+2*b.scaleBackdropPaddingY)),c.fill()}c.textBaseline="middle",c.fillStyle=b.scaleFontColor,c.fillText(d,g/2,h/2-f*(a+1))}}function p(d){var j=-Math.PI/2,k=2*Math.PI/a.length,l=1,m=1;b.animation&&(b.animateScale&&(l=d),b.animateRotate&&(m=d));for(var n=0;a.length>n;n++){if(c.beginPath(),c.arc(g/2,h/2,l*q(a[n].value,i,f),j,j+m*k,!1),c.lineTo(g/2,h/2),c.closePath(),c.fillStyle=a[n].color,c.fill(),d>.9999999){var o=void 0!=a[n].label&&""!=a[n].label?a[n].label+": "+a[n].value:a[n].value,p=[{x:g/2,y:h/2}],r=50,s=q(a[n].value,i,f);p.push({x:g/2+s*Math.cos(j),y:h/2+s*Math.sin(j)});for(var t=0;r>=t;t++)p.push({x:g/2+s*Math.cos(j+t/r*m*k),y:h/2+s*Math.sin(j+t/r*m*k)});e(c,{type:"shape",points:p},o,"PolarArea")}b.segmentShowStroke&&(c.strokeStyle=b.segmentStrokeColor,c.lineWidth=b.segmentStrokeWidth,c.stroke()),j+=m*k}}function s(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;a.length>d;d++)a[d].value>b&&(b=a[d].value),c>a[d].value&&(c=a[d].value);var e=Math.floor(k/(.66*j)),f=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:e,minSteps:f}}var d,f,i,j,k,l,m;n(),l=s(),m=b.scaleShowLabels?b.scaleLabel:null,b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=d/i.steps,r(b,o,p,c)},l=function(a,b,c){function n(d){var j=2*Math.PI/a.datasets[0].data.length;c.save(),c.translate(g/2,h/2);for(var k=0;a.datasets.length>k;k++){var l=q(a.datasets[k].data[0],i,f);if(c.beginPath(),c.moveTo(0,d*-1*l),1==d){var m=g/2+l*Math.cos(0-Math.PI/2),n=h/2+l*Math.sin(0-Math.PI/2),o=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,p=""!=a.labels[0].trim()?a.labels[0]+": "+a.datasets[k].data[0]:a.datasets[k].data[0];e(c,{type:"circle",x:m,y:n,r:o},p,"Radar")}for(var r=1;a.datasets[k].data.length>r;r++)if(l=q(a.datasets[k].data[r],i,f),c.rotate(j),c.lineTo(0,d*-1*l),1==d){var m=g/2+l*Math.cos(r*j-Math.PI/2),n=h/2+l*Math.sin(r*j-Math.PI/2),o=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,p=""!=a.labels[r].trim()?a.labels[r]+": "+a.datasets[k].data[r]:a.datasets[k].data[r];e(c,{type:"circle",x:m,y:n,r:o},p,"Radar")}if(c.closePath(),c.fillStyle=a.datasets[k].fillColor,c.strokeStyle=a.datasets[k].strokeColor,c.lineWidth=b.datasetStrokeWidth,c.fill(),c.stroke(),b.pointDot){c.fillStyle=a.datasets[k].pointColor,c.strokeStyle=a.datasets[k].pointStrokeColor,c.lineWidth=b.pointDotStrokeWidth;for(var s=0;a.datasets[k].data.length>s;s++)c.rotate(j),c.beginPath(),c.arc(0,d*-1*q(a.datasets[k].data[s],i,f),b.pointDotRadius,2*Math.PI,!1),c.fill(),c.stroke()}c.rotate(j)}c.restore()}function o(){var e=2*Math.PI/a.datasets[0].data.length;if(c.save(),c.translate(g/2,h/2),b.angleShowLineOut){c.strokeStyle=b.angleLineColor,c.lineWidth=b.angleLineWidth;for(var j=0;a.datasets[0].data.length>j;j++)c.rotate(e),c.beginPath(),c.moveTo(0,0),c.lineTo(0,-d),c.stroke()}for(var k=0;i.steps>k;k++){if(c.beginPath(),b.scaleShowLine){c.strokeStyle=b.scaleLineColor,c.lineWidth=b.scaleLineWidth,c.moveTo(0,-f*(k+1));for(var l=0;a.datasets[0].data.length>l;l++)c.rotate(e),c.lineTo(0,-f*(k+1));c.closePath(),c.stroke()}if(b.scaleShowLabels){if(c.textAlign="center",c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,c.textBaseline="middle",b.scaleShowLabelBackdrop){var m=c.measureText(i.labels[k]).width;c.fillStyle=b.scaleBackdropColor,c.beginPath(),c.rect(Math.round(-m/2-b.scaleBackdropPaddingX),Math.round(-f*(k+1)-.5*b.scaleFontSize-b.scaleBackdropPaddingY),Math.round(m+2*b.scaleBackdropPaddingX),Math.round(b.scaleFontSize+2*b.scaleBackdropPaddingY)),c.fill()}c.fillStyle=b.scaleFontColor,c.fillText(i.labels[k],0,-f*(k+1))}}for(var n=0;a.labels.length>n;n++){c.font=b.pointLabelFontStyle+" "+b.pointLabelFontSize+"px "+b.pointLabelFontFamily,c.fillStyle=b.pointLabelFontColor;var o=Math.sin(e*n)*(d+b.pointLabelFontSize),p=Math.cos(e*n)*(d+b.pointLabelFontSize);c.textAlign=e*n==Math.PI||0==e*n?"center":e*n>Math.PI?"right":"left",c.textBaseline="middle",c.fillText(a.labels[n],o,-p)}c.restore()}function p(){d=w([g,h])/2,j=2*b.scaleFontSize;for(var e=0,f=0;a.labels.length>f;f++){c.font=b.pointLabelFontStyle+" "+b.pointLabelFontSize+"px "+b.pointLabelFontFamily;var i=c.measureText(a.labels[f]).width;i>e&&(e=i)}d-=v([e,1.5*(b.pointLabelFontSize/2)]),d-=b.pointLabelFontSize,d=z(d,null,0),k=d,j=x(j,5)}function s(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;a.datasets.length>d;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m;a.labels||(a.labels=[]),p();var l=s();m=b.scaleShowLabels?b.scaleLabel:null,b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=d/i.steps,r(b,o,n,c)},m=function(a,b,c){function j(i){var j=-Math.PI/2,k=1,l=1;b.animation&&(b.animateScale&&(k=i),b.animateRotate&&(l=i));for(var m=0;a.length>m;m++){var n=l*a[m].value/d*2*Math.PI;if(c.beginPath(),c.arc(g/2,h/2,k*f,j,j+n),c.lineTo(g/2,h/2),c.closePath(),c.fillStyle=a[m].color,c.fill(),a[m].label&&2*k*f*n/(2*Math.PI)>b.labelFontSize){var o=a[m].labelFontSize||b.labelFontSize+"px";null!=o.match(/^[0-9]+$/g)&&(o+="px"),c.font=b.labelFontStyle+" "+o+" "+b.labelFontFamily,c.fillStyle=E(i,a[m].labelColor||"black",a[m].color);var p=-(j+n)+n/2,q=g/2+k*f*Math.cos(p)-10,r=h/2-k*f*Math.sin(p);c.textAlign="right",-Math.PI/2>p&&(p-=Math.PI,c.textAlign="left",q+=20),c.translate(q,r),c.rotate(-p),c.fillText(a[m].label,0,0),c.rotate(p),c.translate(-q,-r)}if(i>.9999999){var s=void 0!=a[m].label&&""!=a[m].label?a[m].label+": "+a[m].value:a[m].value,t=[{x:g/2,y:h/2}],u=50;t.push({x:g/2+f*Math.cos(j),y:h/2+f*Math.sin(j)});for(var v=0;u>=v;v++)t.push({x:g/2+f*Math.cos(j+v/u*n),y:h/2+f*Math.sin(j+v/u*n)});e(c,{type:"shape",points:t},s,"Pie")}b.segmentShowStroke&&(c.lineWidth=b.segmentStrokeWidth,c.strokeStyle=b.segmentStrokeColor,c.stroke()),j+=n}}for(var d=0,f=w([h/2,g/2])-5,i=0;a.length>i;i++)d+=a[i].value;c.fillStyle="black",c.textBaseline="base",r(b,null,j,c)},n=function(a,b,c){function k(j){var k=-Math.PI/2,l=1,m=1;b.animation&&(b.animateScale&&(l=j),b.animateRotate&&(m=j));for(var n=0;a.length>n;n++){var o=m*a[n].value/d*2*Math.PI;if(c.beginPath(),c.arc(g/2,h/2,l*f,k,k+o,!1),c.arc(g/2,h/2,l*i,k+o,k,!0),c.closePath(),c.fillStyle=a[n].color,c.fill(),j>.9999999){var p=void 0!=a[n].label&&""!=a[n].label?a[n].label+": "+a[n].value:a[n].value,q=[],r=50;q.push({x:g/2+f*Math.cos(k),y:h/2+f*Math.sin(k)});for(var s=0;r>=s;s++)q.push({x:g/2+f*Math.cos(k+s/r*o),y:h/2+f*Math.sin(k+s/r*o)});q.push({x:g/2+i*Math.cos(k+o),y:h/2+i*Math.sin(k+o)});for(var s=r;s>=0;s--)q.push({x:g/2+i*Math.cos(k+s/r*o),y:h/2+i*Math.sin(k+s/r*o)});e(c,{type:"shape",points:q},p,"Doughnut")}b.segmentShowStroke&&(c.lineWidth=b.segmentStrokeWidth,c.strokeStyle=b.segmentStrokeColor,c.stroke()),k+=o}}for(var d=0,f=w([h/2,g/2])-5,i=f*(b.percentageInnerCutout/100),j=0;a.length>j;j++)d+=a[j].value;r(b,null,k,c)},o=function(a,b,c){function x(d){function m(b,c){return v-d*q(a.datasets[b].data[c],i,f)}function o(a){return s+n*a}for(var g=0;a.datasets.length>g;g++){c.strokeStyle=a.datasets[g].strokeColor,c.lineWidth=b.datasetStrokeWidth,c.beginPath(),c.moveTo(s,v-d*q(a.datasets[g].data[0],i,f));for(var h=1;a.datasets[g].data.length>h;h++)b.bezierCurve?c.bezierCurveTo(o(h-.5),m(g,h-1),o(h-.5),m(g,h),o(h),m(g,h)):c.lineTo(o(h),m(g,h));for(var j=b.pointDot?b.pointDotRadius+b.pointDotStrokeWidth:10,h=0;a.datasets[g].data.length>h;h++)if(1==d){var k=""!=(""+a.labels[h]).trim()?a.labels[h]+": "+a.datasets[g].data[h]:a.datasets[g].data[h];e(c,{type:"circle",x:o(h),y:m(g,h),r:j},k,"Line")}if(c.stroke(),b.datasetFill?(c.lineTo(s+n*(a.datasets[g].data.length-1),v),c.lineTo(s,v),c.closePath(),c.fillStyle=a.datasets[g].fillColor,c.fill()):c.closePath(),b.pointDot){c.fillStyle=a.datasets[g].pointColor,c.strokeStyle=a.datasets[g].pointStrokeColor,c.lineWidth=b.pointDotStrokeWidth;for(var l=0;a.datasets[g].data.length>l;l++)c.beginPath(),c.arc(s+n*l,v-d*q(a.datasets[g].data[l],i,f),b.pointDotRadius,0,2*Math.PI,!0),c.fill(),c.stroke()}}}function y(){c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(g-o/2+5,v),c.lineTo(g-o/2-p-5,v),c.stroke(),w>0?(c.save(),c.textAlign="right"):c.textAlign="center",c.fillStyle=b.scaleFontColor;for(var d=0;a.labels.length>d;d++)c.save(),w>0?(c.translate(s+d*n,v+b.scaleFontSize),c.rotate(-(w*(Math.PI/180))),c.fillText(a.labels[d],0,0),c.restore()):c.fillText(a.labels[d],s+d*n,v+b.scaleFontSize+3),c.beginPath(),c.moveTo(s+d*n,v+3),b.scaleShowGridLines&&d>0?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+d*n,5)):c.lineTo(s+d*n,v+3),c.stroke();c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(s,v+5),c.lineTo(s,5),c.stroke(),c.textAlign="right",c.textBaseline="middle";for(var e=0;i.steps>e;e++)c.beginPath(),c.moveTo(s-3,v-(e+1)*f),b.scaleShowGridLines?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+p+5,v-(e+1)*f)):c.lineTo(s-.5,v-(e+1)*f),c.stroke(),b.scaleShowLabels&&c.fillText(i.labels[e],s-8,v-(e+1)*f)}function z(){var d=1;if(b.scaleShowLabels){c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;for(var e=0;i.labels.length>e;e++){var f=c.measureText(i.labels[e]).width;d=f>d?f:d}d+=10}p=g-d-o,n=Math.floor(p/(a.labels.length-1)),s=g-o/2-p,v=k+b.scaleFontSize/2}function A(){d=h,c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,o=1;for(var e=0;a.labels.length>e;e++){var f=c.measureText(a.labels[e]).width;o=f>o?f:o}o>g/a.labels.length?(w=45,g/a.labels.lengthd;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m,n,o,p,s,v,w=0;A(),l=B(),m=b.scaleShowLabels?b.scaleLabel:"",b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=Math.floor(k/i.steps),z(),r(b,y,x,c)},p=function(a,b,c){function y(d){c.lineWidth=b.barStrokeWidth;for(var g=0;a.datasets.length>g;g++){c.fillStyle=a.datasets[g].fillColor,c.strokeStyle=a.datasets[g].strokeColor;for(var h=0;a.datasets[g].data.length>h;h++){var j=s+b.barValueSpacing+n*h+w*g+b.barDatasetSpacing*g+b.barStrokeWidth*g;if(c.beginPath(),c.moveTo(j,v),c.lineTo(j,v-d*q(a.datasets[g].data[h],i,f)+b.barStrokeWidth/2),c.lineTo(j+w,v-d*q(a.datasets[g].data[h],i,f)+b.barStrokeWidth/2),c.lineTo(j+w,v),b.barShowStroke&&c.stroke(),c.closePath(),c.fill(),1==d){var k=j,l=q(a.datasets[g].data[h],i,f),m=v-l,o=w,p=""!=a.labels[h]?a.labels[h]+": "+a.datasets[g].data[h]:a.datasets[g].data[h];e(c,{type:"rect",x:k,y:m,width:o,height:l},p,"Bar")}}}}function z(){c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(g-o/2+5,v),c.lineTo(g-o/2-p-5,v),c.stroke(),x>0?(c.save(),c.textAlign="right"):c.textAlign="center",c.fillStyle=b.scaleFontColor;for(var d=0;a.labels.length>d;d++)c.save(),x>0?(c.translate(s+d*n,v+b.scaleFontSize),c.rotate(-(x*(Math.PI/180))),c.fillText(a.labels[d],0,0),c.restore()):c.fillText(a.labels[d],s+d*n+n/2,v+b.scaleFontSize+3),c.beginPath(),c.moveTo(s+(d+1)*n,v+3),c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+(d+1)*n,5),c.stroke();c.lineWidth=b.scaleLineWidth,c.strokeStyle=b.scaleLineColor,c.beginPath(),c.moveTo(s,v+5),c.lineTo(s,5),c.stroke(),c.textAlign="right",c.textBaseline="middle";for(var e=0;i.steps>e;e++)c.beginPath(),c.moveTo(s-3,v-(e+1)*f),b.scaleShowGridLines?(c.lineWidth=b.scaleGridLineWidth,c.strokeStyle=b.scaleGridLineColor,c.lineTo(s+p+5,v-(e+1)*f)):c.lineTo(s-.5,v-(e+1)*f),c.stroke(),b.scaleShowLabels&&c.fillText(i.labels[e],s-8,v-(e+1)*f)}function A(){var d=1;if(b.scaleShowLabels){c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily;for(var e=0;i.labels.length>e;e++){var f=c.measureText(i.labels[e]).width;d=f>d?f:d}d+=10}p=g-d-o,n=Math.floor(p/a.labels.length),w=(n-2*b.scaleGridLineWidth-2*b.barValueSpacing-(b.barDatasetSpacing*a.datasets.length-1)-(b.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length,s=g-o/2-p,v=k+b.scaleFontSize/2}function B(){d=h,c.font=b.scaleFontStyle+" "+b.scaleFontSize+"px "+b.scaleFontFamily,o=1;for(var e=0;a.labels.length>e;e++){var f=c.measureText(a.labels[e]).width;o=f>o?f:o}o>g/a.labels.length?(x=45,g/a.labels.lengthd;d++)for(var e=0;a.datasets[d].data.length>e;e++)a.datasets[d].data[e]>b&&(b=a.datasets[d].data[e]),c>a.datasets[d].data[e]&&(c=a.datasets[d].data[e]);var f=Math.floor(k/(.66*j)),g=Math.floor(.5*(k/j));return{maxValue:b,minValue:c,maxSteps:f,minSteps:g}}var d,f,i,j,k,l,m,n,o,p,s,v,w,x=0;B(),l=C(),m=b.scaleShowLabels?b.scaleLabel:"",b.scaleOverride?(i={steps:b.scaleSteps,stepValue:b.scaleStepWidth,graphMin:b.scaleStartValue,labels:[]},u(m,i.labels,i.steps,b.scaleStartValue,b.scaleStepWidth)):i=t(k,l.maxSteps,l.minSteps,l.maxValue,l.minValue,m),f=Math.floor(k/i.steps),A(),r(b,z,y,c)},s=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1e3/60)}}(),C={}}; \ No newline at end of file +var Chart=function(t,v){function z(a,c,b,d){q.tooltips.push(new K(a,c,b,d))}function x(a,c,b){a=C((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function A(a,c,b,d){function k(){h+=f;var e=a.animation?C(p(h),null,0):1;d.clearRect(0,0,n,u);a.scaleOverlay?(b(e),c()):(c(),b(e));if(1>=h)I(k);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/C(a.animationSteps,Number.MAX_VALUE,1):1,p=D[a.animationEasing],h=a.animation?0:1;"function"!==typeof c&& +(c=function(){});I(k)}function E(a,c,b,d,k,f){var p;a=Math.floor(Math.log(d-k)/Math.LN10);k=Math.floor(k/(1*Math.pow(10,a)))*Math.pow(10,a);d=Math.ceil(d/(1*Math.pow(10,a)))*Math.pow(10,a)-k;a=Math.pow(10,a);for(p=Math.round(d/a);pc;)a=pc?c:!isNaN(parseFloat(b))&&isFinite(b)&&a)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+ +"');}return p.join('');");return c?b(c):b}function L(a,c,b){var d=document.createElement("div"),k;d.style.color=c;document.body.appendChild(d);c=window.getComputedStyle(d).color;d.style.color=b;k=window.getComputedStyle(d).color;var f=/rgb *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3}) *\)/;b=f.exec(c);var p=f.exec(k);k=Math.round(parseFloat(b[1]));c=Math.round(parseFloat(b[2]));b=Math.round(parseFloat(b[3]));var h=Math.round(parseFloat(p[1])),f=Math.round(parseFloat(p[2])),p=Math.round(parseFloat(p[3])); +k=parseInt((k-h)*a+h);c=parseInt((c-f)*a+f);a=parseInt((b-p)*a+p);d.parentNode.removeChild(d);return"rgb("+k+","+c+","+a+")"}var q=this,D={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a* +a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI* +a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c= +1.70158,b=0,d=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);da?-0.5*d*Math.pow(2,10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*d*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-D.easeOutBounce(1- +a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*D.easeInBounce(2*a):0.5*D.easeOutBounce(2*a-1)+0.5}};this.tooltips=[];tooltipDefaults={background:"rgba(0,0,0,0.6)",fontFamily:"'Arial'",fontStyle:"normal",fontColor:"white",fontSize:"12px",labelTemplate:"<%=label%>: <%=value%>",padding:{top:10,right:10,bottom:10,left:10}, +offset:{left:0,top:0},border:{width:0,color:"#000"},showHighlight:!0,highlight:{stroke:{width:1,color:"rgba(230,230,230,0.25)"},fill:"rgba(255,255,255,0.25)"}};v=v?y(tooltipDefaults,v):tooltipDefaults;var K=function(a,c,b,d){this.ctx=a;this.areaObj=c;this.data=b;this.y=this.x=this.highlightState=this.savedState=null;this.inRange=function(a,b){if(this.areaObj.type)switch(this.areaObj.type){case "rect":return a>=this.areaObj.x&&a<=this.areaObj.x+this.areaObj.width&&b>=this.areaObj.y&&b<=this.areaObj.y+ +this.areaObj.height;case "circle":return Math.pow(a-this.areaObj.x,2)+Math.pow(b-this.areaObj.y,2)a.canvas.width&&(d-=0>d-n?d:n);h+24>a.canvas.height&&(h-=24);this.ctx.fillStyle=v.background;this.ctx.fillRect(d,h,n,24);0",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff", +segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var b=c?y(q.PolarArea.defaults,c):q.PolarArea.defaults;return new M(a,b,t)};this.Radar=function(a,c){q.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'", +scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart", +onAnimationComplete:null,showTooltips:!0};var b=c?y(q.Radar.defaults,c):q.Radar.defaults;return new N(a,b,t)};this.Pie=function(a,c){q.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,labelFontFamily:"'Arial'",labelFontStyle:"normal",labelFontSize:12,labelFontColor:"#666",labelAlign:"right",showTooltips:!0};var b=c?y(q.Pie.defaults,c):q.Pie.defaults; +return new O(a,b,t)};this.Doughnut=function(a,c){q.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null,showTooltips:!0};var b=c?y(q.Doughnut.defaults,c):q.Doughnut.defaults;return new P(a,b,t)};this.Line=function(a,c){q.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null, +scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var b=c?y(q.Line.defaults, +c):q.Line.defaults;return new Q(a,b,t)};this.Bar=function(a,c){q.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1, +animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null,showTooltips:!0};var b=c?y(q.Bar.defaults,c):q.Bar.defaults;return new R(a,b,t)};var M=function(a,c,b){var d,k,f,p,h,e,q;d=Math.min.apply(Math,[n,u])/2;d-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]);p=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(p+=2*c.scaleBackdropPaddingY,d-=1.5*c.scaleBackdropPaddingY);h=d;p=p?p:5;e=function(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;d +b&&(b=a[d].value),a[d].value=G;G++)w.push({x:n/2+F*Math.cos(g+G/50*m*s),y:u/2+F*Math.sin(g+G/50*m*s)});z(b,{type:"shape",points:w},{label:a[e].label,value:a[e].value},"PolarArea")}c.segmentShowStroke&&(b.strokeStyle=c.segmentStrokeColor,b.lineWidth=c.segmentStrokeWidth,b.stroke());g+=m*s}},b)},N=function(a,c,b){var d,k,f,p,h,e,q;a.labels||(a.labels=[]);(function(){d=Math.min.apply(Math,[n,u])/2;p=2*c.scaleFontSize;for(var f=0,g=0;gf&&(f=k)}d-=Math.max.apply(Math,[f,1.5*(c.pointLabelFontSize/2)]);d-=c.pointLabelFontSize;h=d=C(d,null,0);p=p?p:5})();e=function(){for(var b=Number.MIN_VALUE,c=Number.MAX_VALUE,d=0;db&&(b=a.datasets[d].data[f]),a.datasets[d].data[f]Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[g],s,-l)}b.restore()},function(d){var g=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(n/2,u/2);for(var e=0;ec.labelFontSize){var s=function(a){switch(a){case "left":return"right";case "right":return"left";case "center":return a}},l=a[r].labelFontSize||c.labelFontSize+"px";null!=l.match(/^[0-9]+$/g)&&(l+="px");b.font=c.labelFontStyle+ +" "+l+" "+c.labelFontFamily;b.fillStyle=L(f,a[r].labelColor||"black",a[r].color);b.textBaseline="middle";var l=-(h+g)+g/2,m=n/2+e*k*Math.cos(l),t=u/2-e*k*Math.sin(l);b.textAlign=a[r].labelAlign||c.labelAlign;textX=function(a,b){switch(a){case "left":return-b+20;case "center":return-b/2}return-10}(b.textAlign,e*k);l<-Math.PI/2&&(l-=Math.PI,b.textAlign=s(b.textAlign),textX=-textX);b.translate(m,t);b.rotate(-l);b.fillText(a[r].label,textX,0);b.rotate(l);b.translate(-m,-t)}if(0.9999999=l;l++)s.push({x:n/2+k*Math.cos(h+l/50*g),y:u/2+k*Math.sin(h+l/50*g)});z(b,{type:"shape",points:s},{label:a[r].label,value:a[r].value},"Pie")}c.segmentShowStroke&&(b.lineWidth=c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());h+=g}},b)},P=function(a,c,b){for(var d=0,k=Math.min.apply(Math,[u/2,n/2])-5,f=k*(c.percentageInnerCutout/100),p=0;p=m;m++)l.push({x:n/2+k*Math.cos(e+m/50*s),y:u/2+k*Math.sin(e+m/50*s)});l.push({x:n/2+f*Math.cos(e+s),y:u/2+f*Math.sin(e+s)});for(m=50;0<=m;m--)l.push({x:n/2+f*Math.cos(e+m/50*s),y:u/2+f*Math.sin(e+ +m/50*s)});z(b,{type:"shape",points:l},{label:a[g].label,value:a[g].value},"Doughnut")}c.segmentShowStroke&&(b.lineWidth=c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());e+=s}},b)},Q=function(a,c,b){var d,k,f,p,h,e,q,r,g,s,l,m,t=0;(function(){d=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;g=1;for(var f=0;fg?e:g}n/a.labels.lengthb&&(b=a.datasets[d].data[f]),a.datasets[d].data[f]d?k:d;d+=10}s=n-d-g;r=Math.floor(s/(a.labels.length-1));l=n-g/2-s;m=h+c.scaleFontSize/2})();A(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath(); +b.moveTo(n-g/2+5,m);b.lineTo(n-g/2-s-5,m);b.stroke();0g?e:g}n/a.labels.lengthb&&(b=a.datasets[d].data[f]), +a.datasets[d].data[f]d?k:d;d+=10}s=n-d-g;r=Math.floor(s/a.labels.length);t=(r-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;l=n-g/2-s;m=h+c.scaleFontSize/2})();A(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n-g/2+5,m);b.lineTo(n-g/2-s-5,m);b.stroke();0 Radar Chart - +