diff --git a/e107_admin/includes/infopanel.php b/e107_admin/includes/infopanel.php index 10063c8b8..57846fd17 100644 --- a/e107_admin/includes/infopanel.php +++ b/e107_admin/includes/infopanel.php @@ -686,7 +686,8 @@ class adminstyle_infopanel 'strokeColor' => "rgba(220,220,220,1)", 'pointColor ' => "rgba(220,220,220,1)", 'pointStrokeColor' => "#fff", - 'data' => array(65,59,90,81,56,55,40) + 'data' => array(65,59,90,81,56,55,40), + 'title' => "Visits" ); @@ -695,7 +696,8 @@ class adminstyle_infopanel 'strokeColor' => "rgba(151,187,205,1)", 'pointColor ' => "rgba(151,187,205,1)", 'pointStrokeColor' => "#fff", - 'data' => array(28,48,40,19,96,27,100) + 'data' => array(28,48,40,19,96,27,100), + 'title' => "Unique Visits" ); return $data; @@ -849,6 +851,10 @@ class adminstyle_infopanel $cht = e107::getChart(); $cht->setType('line'); + $cht->setOptions(array( + 'annotateDisplay' => true, + 'annotateFontSize' => 8 + )); $cht->setData($data,'canvas'); $text = $cht->render('canvas'); diff --git a/e107_handlers/chart_class.php b/e107_handlers/chart_class.php index 271f5f61e..3d4407576 100644 --- a/e107_handlers/chart_class.php +++ b/e107_handlers/chart_class.php @@ -6,8 +6,7 @@ * Released under the terms and conditions of the * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * - * Chart Class for e107. see http://www.chartjs.org for details. - * + * Chart Class for e107. @see https://github.com/FVANCOP/ChartNew.js for details. */ /** @@ -37,26 +36,137 @@ 'data' => array(28,48,40,19,96,27,100) ); + $options = array('bezierCurve' => false); + ' $cht = e107::getChart(); $cht->setType('line'); + $cht->setOptions($options); $cht->setData($data,'canvas'); echo $cht->render('canvas'); */ - +/* + * var allopts = { + //Boolean - If we show the scale above the chart data -> Default value Changed + scaleOverlay : true, + //Boolean - If we want to override with a hard coded scale + scaleOverride : false, + //** Required if scaleOverride is true ** + //Number - The number of steps in a hard coded scale + scaleSteps : null, + //Number - The value jump in the hard coded scale + scaleStepWidth : null, + //Number - The scale starting value + scaleStartValue : null, + //String - Colour of the scale line + scaleLineColor : "rgba(0,0,0,.1)", + //Number - Pixel width of the scale line + scaleLineWidth : 1, + //Boolean - Whether to show labels on the scale + scaleShowLabels : true, + //Interpolated JS string - can access value + scaleLabel : "<%=value%>", + //String - Scale label font declaration for the scale label + scaleFontFamily : "'Arial'", + //Number - Scale label font size in pixels + scaleFontSize : 12, + //String - Scale label font weight style + scaleFontStyle : "normal", + //String - Scale label font colour + scaleFontColor : "#666", + ///Boolean - Whether grid lines are shown across the chart + scaleShowGridLines : true, + //String - Colour of the grid lines + scaleGridLineColor : "rgba(0,0,0,.05)", + //Number - Width of the grid lines + scaleGridLineWidth : 1, + //Boolean - Whether the line is curved between points -> Default value Changed + bezierCurve : false, + //Boolean - Whether to show a dot for each point -> Default value Changed + pointDot : false, + //Number - Radius of each point dot in pixels + pointDotRadius : 3, + //Number - Pixel width of point dot stroke + pointDotStrokeWidth : 1, + //Boolean - Whether to show a stroke for datasets + datasetStroke : true, + //Number - Pixel width of dataset stroke + datasetStrokeWidth : 2, + //Boolean - Whether to fill the dataset with a colour + datasetFill : true, + //Boolean - Whether to animate the chart -> Default value changed + animation : false, + //Number - Number of animation steps + animationSteps : 60, + //String - Animation easing effect + animationEasing : "easeOutQuart", + //Function - Fires when the animation is complete + onAnimationComplete : null, + canvasBorders : true, + canvasBordersWidth : 30, + canvasBordersColor : "black", + yAxisLeft : true, + yAxisRight : true, + yAxisLabel : "Y axis", + yAxisFontFamily : "'Arial'", + yAxisFontSize : 50, + yAxisFontStyle : "normal", + yAxisFontColor : "#666", + xAxisLabel : "", + xAxisFontFamily : "'Arial'", + xAxisFontSize : 16, + xAxisFontStyle : "normal", + xAxisFontColor : "#666", + yAxisUnit : "UNIT", + yAxisUnitFontFamily : "'Arial'", + yAxisUnitFontSize : 12, + yAxisUnitFontStyle : "normal", + yAxisUnitFontColor : "#666", + graphTitle : "", + graphTitleFontFamily : "'Arial'", + graphTitleFontSize : 24, + graphTitleFontStyle : "bold", + graphTitleFontColor : "#666", + graphSubTitle : "", + graphSubTitleFontFamily : "'Arial'", + graphSubTitleFontSize : 18, + graphSubTitleFontStyle : "normal", + graphSubTitleFontColor : "#666", + footNote : "Footnote", + footNoteFontFamily : "'Arial'", + footNoteFontSize : 50, + footNoteFontStyle : "bold", + footNoteFontColor : "#666", + legend : true, + legendFontFamily : "'Arial'", + legendFontSize : 18, + legendFontStyle : "normal", + legendFontColor : "#666", + legendBlockSize : 30, + legendBorders : true, + legendBordersWidth : 30, + legendBordersColor : "#666", + // ADDED PARAMETERS + graphMin : "DEFAULT", + graphMax : "DEFAULT" + */ class e_chart { protected $id; protected $data = null; protected $type = 'line'; + protected $options = array('scaleFontSize' => 14, 'annotateDisplay' => true, 'bezierCurve' => true, 'inGraphDataShow'=>false); function __construct() { - e107::js('core','chart/Chart.min.js','jquery'); - e107::css('inline','canvas.e-graph { width: 100% !important; max-width: 800px; height: auto !important; }'); + // e107::js('core','chart/Chart.min.js','jquery'); + e107::js('core','chart/ChartNew.js','jquery'); + + + // e107::css('inline','canvas.e-graph { width: 100% !important; max-width: 800px; height: auto !important; }'); } @@ -65,6 +175,11 @@ class e_chart return json_encode($this->data); } + private function getOptions() + { + return json_encode($this->options); + } + /** * Set the type of graph * @param string $type - line | bar | pie | radar | doughnut | polar @@ -87,6 +202,13 @@ class e_chart $this->data = $data; return $this; } + + + public function setOptions($data,$id) + { + $this->options = $data; + return $this; + } /** * Render Graph @@ -95,7 +217,7 @@ class e_chart * @param integer $height * @return null */ - public function render($id, $width=800,$height=300) + public function render($id, $width='100%',$height=300) { if($this->data == null) @@ -105,13 +227,15 @@ class e_chart $js = "var ChartData = ".$this->getData()."\n"; + $js .= 'var ChartOptions = '.$this->getOptions(); + $js .= ";\n"; switch ($this->type) { case 'bar': - $js .= 'var myLine = new Chart(document.getElementById("'.$id.'").getContext("2d")).Bar(ChartData);'; + $js .= 'var myLine = new Chart(document.getElementById("'.$id.'").getContext("2d")).Bar(ChartData, ChartOptions);'; break; case 'radar': @@ -123,7 +247,7 @@ class e_chart break; case 'doughnut': - $js .= 'var myDoughnut = new Chart(document.getElementById("'.$id.'").getContext("2d")).Doughnut(ChartData);'; + $js .= 'var myDoughnut = new Chart(document.getElementById("'.$id.'").getContext("2d")).Doughnut(ChartData, ChartOptions);'; break; case 'pie': @@ -133,12 +257,9 @@ class e_chart default: case 'line': - - //TODO Chart Options. $js .= ' - // var lineChartData = '.$this->getData().' - // var ChartOptions = "{ scaleFontSize : 18 }" - var myLine = new Chart(document.getElementById("'.$id.'").getContext("2d")).Line(ChartData); + + var myLine = new Chart(document.getElementById("'.$id.'").getContext("2d")).Line(ChartData, ChartOptions); '; @@ -147,6 +268,28 @@ class e_chart + + // Auto-resize the canvas. //TODO Get it working with multiple instances. + e107::js('footer-inline', " + var c = $('#".$id."'); + var ct = c.get(0).getContext('2d'); + var container = $(c).parent(); + + //Run function when browser resizes + $(window).resize( respondCanvas ); + + function respondCanvas(){ + c.attr('width', $(container).width() ); //max width + c.attr('height', $(container).height() ); //max height + + //Call a function to redraw other content (texts, images etc) + } + + //Initial call + respondCanvas(); + "); + + e107::js('footer-inline',$js); return 'HTML5 Canvas not supported.'; diff --git a/e107_web/js/chart/ChartNew.js b/e107_web/js/chart/ChartNew.js new file mode 100644 index 000000000..8e6516523 --- /dev/null +++ b/e107_web/js/chart/ChartNew.js @@ -0,0 +1,5365 @@ + + + +/* + * ChartNew.js + * + * Vancoppenolle Francois - January 2014 + * francois.vancoppenolle@favomo.be + * + * Source location : http:\\www.favomo.be\graphjs + * GitHub community : https://github.com/FVANCOP/ChartNew.js + * + * This file is an adaptation of the chart.js source developped by Nick Downie (2013) + * https://github.com/nnnick/Chart.js + * + * new charts + * + * horizontalBar + * horizontalStackedBar + * + * Added items : + * + * Title + * Subtitle + * X Axis Label + * Y Axis Label + * Unit Label + * Y Axis on the right and/or the left + * Annotates + * canvas Border + * Legend + * Footnote + * crossText + * graphMin / graphMax + * logarithmic y-axis (for line and bar) + * rotateLabels + * + */ + + + var charJSPersonalDefaultOptions = { } + + var charJSPersonalDefaultOptionsLine = { } + var charJSPersonalDefaultOptionsRadar = { } + var charJSPersonalDefaultOptionsPolarArea = { } + var charJSPersonalDefaultOptionsPie = { } + var charJSPersonalDefaultOptionsDoughnut = { } + var charJSPersonalDefaultOptionsBar = { } + var charJSPersonalDefaultOptionsStackedBar = { } + var charJSPersonalDefaultOptionsHorizontalBar = { } + var charJSPersonalDefaultOptionsHorizontalStackedBar = { } + + + +///////// FUNCTIONS THAN CAN BE USED IN THE TEMPLATES /////////////////////////////////////////// + + + + +function roundToWithThousands(config, num, place) { + var newval=1*unFormat(config, num); + + if(typeof(newval)=="number" && place !="none"){ + if(place<=0){ + var roundVal=-place; + newval= +(Math.round(newval + "e+" + roundVal) + "e-" + roundVal); + } + else { + var roundVal=place; + var divval= "1e+"+roundVal; + value= +(Math.round(newval/divval))*divval; + } + } + newval= fmtChartJS(config,newval,"none"); + return(newval); +} ; + +function unFormat(config, num) { + + if((config.decimalSeparator!="." || config.thousandSeparator !="") && typeof(num)=="string") { + var v1=""+num; + if(config.thousandSeparator!=""){ + while(v1.indexOf(config.thousandSeparator)>=0)v1=""+v1.replace(config.thousandSeparator,""); + } + if(config.decimalSeparator!=".")v1=""+v1.replace(config.decimalSeparator,".") +// v1=fmtChartJS(config,1*roundToWithThousands(1*v1,place),"none") + return 1*v1; + } + else { + return num; + } +}; + + + +///////// ANNOTATE PART OF THE SCRIPT /////////////////////////////////////////// + +/******************************************************************************** +Copyright (C) 1999 Thomas Brattli +This script is made by and copyrighted to Thomas Brattli +Visit for more great scripts. This may be used freely as long as this msg is intact! +I will also appriciate any links you could give me. +Distributed by Hypergurl +********************************************************************************/ + +var cachebis = {}; + +function fmtChartJSPerso(config,value,fmt){ + switch(fmt){ + case "SampleJS_Format": + if(typeof(value)=="number")return_value="My Format : " + value.toString()+ " $"; + else return_value=value + "XX"; + break; + case "Change_Month": + if(typeof(value)=="string")return_value=value.toString()+ " 2014"; + else return_value=value.toString()+"YY"; + break; + + default: + return_value=value; + break; + } + return(return_value); +}; + +function fmtChartJS(config,value,fmt){ + + var return_value; + if(fmt=="notformatted") { + return_value=value; + } + else if(fmt=="none" && typeof(value)=="number") { + if(config.roundNumber !="none"){ + if(config.roundNumber<=0){ + var roundVal=-config.roundNumber; + value= +(Math.round(value + "e+" + roundVal) + "e-" + roundVal); + } + else { + var roundVal=config.roundNumber; + var divval= "1e+"+roundVal; + value= +(Math.round(value/divval))*divval; + } + } + if(config.decimalSeparator!="." || config.thousandSeparator !=""){ + return_value=value.toString().replace(/\./g,config.decimalSeparator); + if(config.thousandSeparator !=""){ + var part1=return_value; + var part2=""; + var posdec=part1.indexOf(config.decimalSeparator); + if(posdec>=0){ + part2=part1.substring(posdec+1,part1.length); + part2=part2.split('').reverse().join(''); // reverse string + part1=part1.substring(0,posdec); + } + part1=part1.toString().replace(/\B(?=(\d{3})+(?!\d))/g, config.thousandSeparator); + // part2=part2.toString().replace(/\B(?=(\d{3})+(?!\d))/g, config.thousandSeparator); + part2=part2.split('').reverse().join(''); // reverse string + return_value=part1 + if(part2!="")return_value=return_value+config.decimalSeparator+part2; + } + } + else return_value=value; + } + else if(fmt!="none" && fmt != "notformatted") { + return_value=fmtChartJSPerso(config,value,fmt); + } + else { + return_value=value; + } + return(return_value); +}; + +function addParameters2Function(data,fctName,fctList) { + var mathFunctions = { + mean: {data:data.data,datasetNr:data.v11}, + varianz: {data:data.data, datasetNr:data.v11}, + stddev: {data:data.data, datasetNr:data.v11}, + cv: {data:data.data, datasetNr:data.v11} + }; + // difference to current value (v3) + dif = false; + if (fctName.substr(-3) == "Dif") { + fctName = fctName.substr(0,fctName.length-3); + dif = true; + } + + if (typeof eval(fctName) == "function") { + var parameter = eval(fctList+"."+fctName); + if (dif) { + // difference between v3 (current value) and math function + return data.v3-window[fctName](parameter); + } + return window[fctName](parameter); + } + return; +} + +//Is a number function +function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} ; + +function tmplbis(str, data) { + var mathFunctionList = ["mean","varianz","stddev","cv"]; + var regexMath = new RegExp('<%=((?:(?:.*?)\\W)??)((?:'+mathFunctionList.join('|')+')(?:Dif)?)\\(([0-9]*?)\\)(.*?)%>','g'); + while (regexMath.test(str)) { + str = str.replace(regexMath, function($0,$1,$2,$3,$4) { + if ($3) { var rndFac = $3; } else {var rndFac = 2; } + var value = addParameters2Function(data,$2,"mathFunctions"); + if (isNumber(value)) { + return '<%='+$1+''+Math.round(Math.pow(10,rndFac)*value)/Math.pow(10,rndFac)+''+$4+'%>'; + } + return '<%= %>'; + }); + } + + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + // first check if it's can be an id + var fn = /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(str) ? cachebis[str] = cachebis[str] || + tmplbis(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\n]/g, "\\n") + .replace(/[\t]/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; +}; + +/** + * ctx.prototype + * fillText option for canvas Multiline Support + * @param text string \n for newline + * @param x x position + * @param y y position + * @param yLevel = "bottom" => last line has this y-Pos [default], = "middle" => the middle line has this y-Pos) + * @param lineHeight lineHeight + */ +CanvasRenderingContext2D.prototype.fillTextMultiLine = function(text, x, y,yLevel, lineHeight) { + + var lines = (""+text).split("\n"); + // if its one line => in the middle + // two lines one above the mid one below etc. + if (yLevel == "middle") { + y -= ((lines.length-1)/2)*lineHeight; + } else if(yLevel=="bottom") { // default + y -= (lines.length-1)*lineHeight; + } + for (var i = 0; i < lines.length; i++) { + this.fillText(lines[i], x, y); + y += lineHeight; + } +} + +CanvasRenderingContext2D.prototype.measureTextMultiLine = function(text,lineHeight) { + var textWidth=0; + var lg; + var lines = (""+text).split("\n"); + var textHeight=lines.length*lineHeight; + // if its one line => in the middle + // two lines one above the mid one below etc. + for (var i = 0; i < lines.length; i++) { + lg= this.measureText(lines[i]).width ; + if(lg>textWidth)textWidth=lg; + } + return { + textWidth: textWidth, + textHeight: textHeight + }; +} + + +cursorDivCreated = false; + +function createCursorDiv() { + if (cursorDivCreated == false) { + var div = document.createElement('divCursor'); + div.id = 'divCursor'; + div.style.position = 'absolute'; + document.body.appendChild(div); + cursorDivCreated = true; + } +} ; + + +//Default browsercheck, added to all scripts! +function checkBrowser() { + this.ver = navigator.appVersion + this.dom = document.getElementById ? 1 : 0 + this.ie5 = (this.ver.indexOf("MSIE 5") > -1 && this.dom) ? 1 : 0; + this.ie4 = (document.all && !this.dom) ? 1 : 0; + this.ns5 = (this.dom && parseInt(this.ver) >= 5) ? 1 : 0; + this.ns4 = (document.layers && !this.dom) ? 1 : 0; + this.bw = (this.ie5 || this.ie4 || this.ns4 || this.ns5) + return this +}; +bw = new checkBrowser(); + +//Set these variables: +fromLeft = 10; // How much from the left of the cursor should the div be? +fromTop = 10; // How much from the top of the cursor should the div be? + +/******************************************************************** +Initilizes the objects +*********************************************************************/ + +function cursorInit() { + scrolled = bw.ns4 || bw.ns5 ? "window.pageYOffset" : "document.body.scrollTop" + if (bw.ns4) document.captureEvents(Event.MOUSEMOVE) +} ; +/******************************************************************** +Contructs the cursorobjects +*********************************************************************/ +function makeCursorObj(obj, nest) { + + createCursorDiv(); + + nest = (!nest) ? '' : 'document.' + nest + '.' + this.css = bw.dom ? document.getElementById(obj).style : bw.ie4 ? document.all[obj].style : bw.ns4 ? eval(nest + "document.layers." + obj) : 0; + this.moveIt = b_moveIt; + + cursorInit(); + + return this +} ; +function b_moveIt(x, y) { + + + this.x = x; + this.y = y; + this.css.left = this.x + "px"; + this.css.top = this.y + "px"; +}; + + + + +function isIE() { + var myNav = navigator.userAgent.toLowerCase(); + return (myNav.indexOf('msie') != -1) ? parseInt(myNav.split('msie')[1]) : false; +}; + +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; +}; + +function sleep(ms){ + var dt = new Date(); + dt.setTime(dt.getTime() + ms); + while (new Date().getTime() < dt.getTime()){}; +} ; + +function saveCanvas(ctx,data,config,tpgraph) { + cvSave = ctx.getImageData(0,0,ctx.canvas.width, ctx.canvas.height); + + var saveCanvasConfig = { + savePng : false, + annotateDisplay : false, + animation : false, + dynamicDisplay : false + }; + + var savePngConfig = mergeChartConfig(config, saveCanvasConfig); + + savePngConfig.clearRect = false; + + /* And ink them */ + + switch(tpgraph){ + case "Bar": + new Chart(ctx.canvas.getContext("2d")).Bar(data,savePngConfig); + break; + case "Pie": + new Chart(ctx.canvas.getContext("2d")).Pie(data,savePngConfig); + break; + case "Doughnut": + new Chart(ctx.canvas.getContext("2d")).Doughnut(data,savePngConfig); + break; + case "Radar": + new Chart(ctx.canvas.getContext("2d")).Radar(data,savePngConfig); + break; + case "PolarArea": + new Chart(ctx.canvas.getContext("2d")).PolarArea(data,savePngConfig); + break; + case "HorizontalBar": + new Chart(ctx.canvas.getContext("2d")).HorizontalBar(data,savePngConfig); + break; + case "StackedBar": + new Chart(ctx.canvas.getContext("2d")).StackedBar(data,savePngConfig); + break; + case "HorizontalStackedBar": + new Chart(ctx.canvas.getContext("2d")).HorizontalStackedBar(data,savePngConfig); + break; + case "Line": + new Chart(ctx.canvas.getContext("2d")).Line(data,savePngConfig); + break; + } + + + + if(config.savePngOuput=="NewWindow"){ + var image = ctx.canvas.toDataURL(); + ctx.putImageData(cvSave,0,0); + window.open(image,'_blank'); + } + if(config.savePngOuput=="CurrentWindow"){ + var image = ctx.canvas.toDataURL(); + ctx.putImageData(cvSave,0,0); + window.location.href = image; + } + if(config.savePngOuput=="Save"){ + document.location.href= ctx.canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); + ctx.putImageData(cvSave,0,0); + } + +} ; + + +//if (isIE() < 9 && isIE() != false) { + + if (typeof String.prototype.trim !== 'function') { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, ''); + } + }; +//}; + +var dynamicDisplay = new Array(); +var dynamicDisplayList = new Array(); + +function dynamicFunction(data,config,ctx,tpgraph){ + if(config.dynamicDisplay) + { + if(ctx.canvas.id=="") + { + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.canvas.id="Canvas_"+cvmillsec; + } + if(typeof(dynamicDisplay[ctx.canvas.id])=="undefined") + { + dynamicDisplayList[dynamicDisplayList["length"]]=ctx.canvas.id; + dynamicDisplay[ctx.canvas.id]=[ctx.canvas,false,false,data,config,ctx.canvas,tpgraph]; + dynamicDisplay[ctx.canvas.id][1]=isScrolledIntoView(ctx.canvas); + window.onscroll = scrollFunction; + } + if(dynamicDisplay[ctx.canvas.id][1]==false || dynamicDisplay[ctx.canvas.id][2]==true)return false; + dynamicDisplay[ctx.canvas.id][2]=true; + } + return true; +} ; + +function isScrolledIntoView(element){ + var xPosition = 0; + var yPosition = 0; + + elem=element; + while(elem) { + xPosition += (elem.offsetLeft - elem.scrollLeft + elem.clientLeft); + yPosition += (elem.offsetTop - elem.scrollTop + elem.clientTop); + elem = elem.offsetParent; + } + + if (xPosition+element.width/2 >= window.pageXOffset && + xPosition+element.width/2 <= window.pageXOffset + window.innerWidth && + yPosition+element.height/2 >= window.pageYOffset && + yPosition+element.height/2 <= window.pageYOffset+window.innerHeight + )return(true); + else return false; +}; + +function scrollFunction(){ + for (var i=0;i jsGraphAnnotate[ctx.ChartNewId][i][3] && distance < jsGraphAnnotate[ctx.ChartNewId][i][4]) { + + angle = Math.acos((canvas_pos.x - jsGraphAnnotate[ctx.ChartNewId][i][1]) / distance); + if (canvas_pos.y < jsGraphAnnotate[ctx.ChartNewId][i][2]) angle = -angle; + + while (angle < 0){angle+=2*Math.PI;} + while (angle > 2*Math.PI){angle-=2*Math.PI;} + if(angle jsGraphAnnotate[ctx.ChartNewId][i][5] && angle < jsGraphAnnotate[ctx.ChartNewId][i][6]) || (angle > jsGraphAnnotate[ctx.ChartNewId][i][5]-2*Math.PI && angle < jsGraphAnnotate[ctx.ChartNewId][i][6]-2*Math.PI)|| (angle > jsGraphAnnotate[ctx.ChartNewId][i][5]+2*Math.PI && angle < jsGraphAnnotate[ctx.ChartNewId][i][6]+2*Math.PI)) { + + v1 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][7],config.fmtV1); // V1=Label + v2 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][8],config.fmtV2); // V2=Data Value + v3 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][9],config.fmtV3); // V3=Cumulated Value + v4 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][10],config.fmtV4); // V4=Total Data Value + v5 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][11],config.fmtV5); // V5=Angle + + v6 = fmtChartJS(config,100 * jsGraphAnnotate[ctx.ChartNewId][i][8] / jsGraphAnnotate[ctx.ChartNewId][i][10],config.fmtV6); // v6=Percentage; + v6 = roundToWithThousands(config, v6, config.roundPct); + v7 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][1],config.fmtV7); // v7=midPointX of arc; + v8 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][2],config.fmtV8); // v8=midPointY of arc; + v9 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][3],config.fmtV9); // v9=radius Minimum; + v10 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][4],config.fmtV10); // v10=radius Maximum; + v11 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][5],config.fmtV11); // v11=start angle; + v12 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][6],config.fmtV12); // v12=stop angle; + v13 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][12],config.fmtV13); // v13=position in Data; + + graphPosX = canvas_pos.x; + graphPosY = canvas_pos.y; + + // create label text + dispString = tmplbis(config.annotateLabel, { config:config, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9, v10: v10, v11: v11, v12: v12, v13: v13, graphPosX: graphPosX, graphPosY: graphPosY} ); + annotateDIV.innerHTML = dispString; + show = true; + + + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + oCursor.moveIt(x + fromLeft, y + fromTop); + } + } + } else if (jsGraphAnnotate[ctx.ChartNewId][i][0] == "RECT") { + if (canvas_pos.x > jsGraphAnnotate[ctx.ChartNewId][i][1] && canvas_pos.x < jsGraphAnnotate[ctx.ChartNewId][i][3] && canvas_pos.y < jsGraphAnnotate[ctx.ChartNewId][i][2] && canvas_pos.y > jsGraphAnnotate[ctx.ChartNewId][i][4]) { + + v1 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][5],config.fmtV1); // V1=Label1 + v2 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][6],config.fmtV2); // V2=Label2 + v3 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][7],config.fmtV3); // V3=Data Value + v4 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][8],config.fmtV4); // V4=Cumulated Value + v5 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][9],config.fmtV5); // V5=Total Data Value + v6 = fmtChartJS(config,100 * jsGraphAnnotate[ctx.ChartNewId][i][7] / jsGraphAnnotate[ctx.ChartNewId][i][9],config.fmtV6); // v6=Percentage; + v6 = roundToWithThousands(config, v6, config.roundPct); + v7 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][1],config.fmtV7); // v7=top X of rectangle; + v8 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][2],config.fmtV8); // v8=top Y of rectangle; + v9 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][3],config.fmtV9); // v9=bottom X of rectangle; + v10 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][4],config.fmtV10); // v10=bottom Y of rectangle; + v11 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][10],config.fmtV11); // v11=position in Dataset; + v12 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][11],config.fmtV12); // v12=position in Dataset[v11].Data; + graphPosX = canvas_pos.x; + graphPosY = canvas_pos.y; + + dispString = tmplbis(config.annotateLabel, { config:config, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9, v10: v10, v11: v11, v12: v12, graphPosX: graphPosX, graphPosY: graphPosY, data:data }); + annotateDIV.innerHTML = dispString; + show = true; + + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + oCursor.moveIt(x + fromLeft, y + fromTop); + } + + } else if (jsGraphAnnotate[ctx.ChartNewId][i][0] == "POINT") { + distance = Math.sqrt((canvas_pos.x - jsGraphAnnotate[ctx.ChartNewId][i][1]) * (canvas_pos.x - jsGraphAnnotate[ctx.ChartNewId][i][1]) + (canvas_pos.y - jsGraphAnnotate[ctx.ChartNewId][i][2]) * (canvas_pos.y - jsGraphAnnotate[ctx.ChartNewId][i][2])); + if (distance < 10) { + + v1 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][3],config.fmtV1); // V1=Label1 + v2 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][4],config.fmtV2); // V2=Label2 + v3 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][5],config.fmtV3); // V3=Data Value + v4 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][6],config.fmtV4); // V4=Difference with Previous line + v5 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][7],config.fmtV5); // V5=Difference with next line; + v6 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][8],config.fmtV6); // V6=max; + v7 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][9],config.fmtV7); // V7=Total; + v8 = fmtChartJS(config,100 * jsGraphAnnotate[ctx.ChartNewId][i][5] / jsGraphAnnotate[ctx.ChartNewId][i][9],config.fmtV8); // v8=percentage; + v8 = roundToWithThousands(config, v8, config.roundPct); + v9 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][1],config.fmtV9); // v9=pos X of point; + v10 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][2],config.fmtV10); // v10=pos Y of point; + v11 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][10],config.fmtV11); // v11=position in Dataset; + v12 = fmtChartJS(config,jsGraphAnnotate[ctx.ChartNewId][i][11],config.fmtV12); // v12=position in Dataset[v11].Data; + + graphPosX = canvas_pos.x; + graphPosY = canvas_pos.y; + + dispString = tmplbis(config.annotateLabel, { config:config, v1: v1, v2: v2, v3: v3, v4: v4, v5: v5, v6: v6, v7: v7, v8: v8, v9: v9, v10: v10, v11: v11, v12: v12, graphPosX: graphPosX, graphPosY: graphPosY, data: data }); + annotateDIV.innerHTML = dispString; + show = true; + + x = bw.ns4 || bw.ns5 ? event.pageX : event.x; + y = bw.ns4 || bw.ns5 ? event.pageY : event.y; + if (bw.ie4 || bw.ie5) y = y + eval(scrolled); + oCursor.moveIt(x + fromLeft, y + fromTop); + + } + } + +annotateDIV.style.display = show ? '' : 'none'; + } + +} ; + + + + + + +///////// GRAPHICAL PART OF THE SCRIPT /////////////////////////////////////////// + + + +//Define the global Chart Variable as a class. +window.Chart = function (context) { + + 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; + } + }; + + //Variables global to the chart + var width = context.canvas.width; + var height = context.canvas.height; + + + //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 = { + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataPaddingAngle: 0, + inGraphDataTmpl: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + inGraphDataAlign : "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign : "off-center", // "bottom", "center", "top", "off-center" or "to-center" + inGraphDataRotate : 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition : 3, + inGraphDataAnglePosition : 2, + 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, + annotateLabel: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + startAngle : 90 + }; + chart.PolarArea.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.PolarArea.defaults); + chart.PolarArea.defaults = mergeChartConfig(chart.PolarArea.defaults, charJSPersonalDefaultOptions); + chart.PolarArea.defaults = mergeChartConfig(chart.PolarArea.defaults, charJSPersonalDefaultOptionsPolarArea); + + 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 = { + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign : "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataRotate : 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition : 3, + 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, + datasetFill : true, + datasetStrokeWidth: 2, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3%>", + startAngle: 90, + graphMaximized : false // if true, the graph will not be centered in the middle of the canvas + }; + + // merge annotate defaults + chart.Radar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Radar.defaults) ; + chart.Radar.defaults = mergeChartConfig(chart.Radar.defaults, charJSPersonalDefaultOptions); + chart.Radar.defaults = mergeChartConfig(chart.Radar.defaults, charJSPersonalDefaultOptionsRadar); + + 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 = { + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataPaddingAngle: 0, + inGraphDataTmpl: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + inGraphDataAlign : "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign : "off-center", // "bottom", "center", "top", "off-center" or "to-center" + inGraphDataRotate : 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition : 3, + inGraphDataAnglePosition : 2, + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeWidth: 2, + animation: true, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null, + annotateLabel: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + startAngle: 90, + radiusScale : 1 + }; + + // merge annotate defaults + chart.Pie.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Pie.defaults); + chart.Pie.defaults = mergeChartConfig(chart.Pie.defaults, charJSPersonalDefaultOptions); + chart.Pie.defaults = mergeChartConfig(chart.Pie.defaults, charJSPersonalDefaultOptionsPie); + 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 = { + inGraphDataShow: false, + inGraphDataPaddingRadius: 5, + inGraphDataPaddingAngle: 0, + inGraphDataTmpl: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + inGraphDataAlign : "off-center", // "right", "center", "left", "off-center" or "to-center" + inGraphDataVAlign : "off-center", // "bottom", "middle", "top", "off-center" or "to-center" + inGraphDataRotate : 0, // rotateAngle value (0->360) , "inRadiusAxis" or "inRadiusAxisRotateLabels" + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataRadiusPosition : 3, + inGraphDataAnglePosition : 2, + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeWidth: 2, + percentageInnerCutout: 50, + animation: true, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null, + annotateLabel: "<%=(v1 == ''? '' : v1+':')+ v2 + ' (' + v6 + ' %)'%>", + startAngle: 90, + radiusScale : 1 + }; + + // merge annotate defaults + chart.Doughnut.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Doughnut.defaults); + chart.Doughnut.defaults = mergeChartConfig(chart.Doughnut.defaults, charJSPersonalDefaultOptions); + chart.Doughnut.defaults = mergeChartConfig(chart.Doughnut.defaults, charJSPersonalDefaultOptionsDoughnut); + 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 = { + inGraphDataShow: false, + inGraphDataPaddingX: 3, + inGraphDataPaddingY: 3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "left", + inGraphDataVAlign : "bottom", + inGraphDataRotate : 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + 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, + scaleXGridLinesStep : 1, + scaleYGridLinesStep : 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + logarithmic: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + bezierCurve: true, + pointDot: true, + pointDotRadius: 4, + pointDotStrokeWidth: 2, + datasetStrokeWidth: 2, + datasetFill: true, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3%>" + + }; + + // merge annotate defaults + chart.Line.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Line.defaults); + chart.Line.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.Line.defaults); + chart.Line.defaults = mergeChartConfig(chart.Line.defaults, charJSPersonalDefaultOptions); + chart.Line.defaults = mergeChartConfig(chart.Line.defaults, charJSPersonalDefaultOptionsLine); + + var config = (options) ? mergeChartConfig(chart.Line.defaults, options) : chart.Line.defaults; + + return new Line(data, config, context); + }; + + this.StackedBar = function (data, options) { + + chart.StackedBar.defaults = { + inGraphDataShow: false, + inGraphDataPaddingX: 0, + inGraphDataPaddingY: -3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "center", + inGraphDataVAlign : "top", + inGraphDataRotate : 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition : 2, + inGraphDataYPosition : 3, + 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, + scaleXGridLinesStep : 1, + scaleYGridLinesStep : 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + barShowStroke: true, + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>" + }; + + + // merge annotate defaults + chart.StackedBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.StackedBar.defaults); + chart.StackedBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.StackedBar.defaults); + chart.StackedBar.defaults = mergeChartConfig(chart.StackedBar.defaults, charJSPersonalDefaultOptions); + chart.StackedBar.defaults = mergeChartConfig(chart.StackedBar.defaults, charJSPersonalDefaultOptionsStackedBar); + + var config = (options) ? mergeChartConfig(chart.StackedBar.defaults, options) : chart.StackedBar.defaults; + return new StackedBar(data, config, context); + } ; + + this.HorizontalStackedBar = function (data, options) { + + chart.HorizontalStackedBar.defaults = { + inGraphDataShow: false, + inGraphDataPaddingX: -3, + inGraphDataPaddingY: 0, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "right", + inGraphDataVAlign : "middle", + inGraphDataRotate : 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition : 3, + inGraphDataYPosition : 2, + 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, + scaleXGridLinesStep : 1, + scaleYGridLinesStep : 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + barShowStroke: true, + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>" + }; + + + // merge annotate defaults + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.HorizontalStackedBar.defaults); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.HorizontalStackedBar.defaults); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.HorizontalStackedBar.defaults, charJSPersonalDefaultOptions); + chart.HorizontalStackedBar.defaults = mergeChartConfig(chart.HorizontalStackedBar.defaults, charJSPersonalDefaultOptionsHorizontalStackedBar); + var config = (options) ? mergeChartConfig(chart.HorizontalStackedBar.defaults, options) : chart.HorizontalStackedBar.defaults; + return new HorizontalStackedBar(data, config, context); + } ; + + this.Bar = function (data, options) { + chart.Bar.defaults = { + inGraphDataShow: false, + inGraphDataPaddingX: 0, + inGraphDataPaddingY: 3, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "center", + inGraphDataVAlign : "bottom", + inGraphDataRotate : 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition : 2, + inGraphDataYPosition : 3, + 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, + scaleXGridLinesStep : 1, + scaleYGridLinesStep : 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + // you can force an integer value between 0 and 180 degres + logarithmic: false, // can be 'fuzzy',true and false ('fuzzy' => if the gap between min and maximum is big it's using a logarithmic y-Axis scale + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + barShowStroke: true, + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + barBorderRadius : 0, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>" + }; + + // merge annotate defaults + chart.Bar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.Bar.defaults); + chart.Bar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.Bar.defaults); + chart.Bar.defaults = mergeChartConfig(chart.Bar.defaults, charJSPersonalDefaultOptions); + chart.Bar.defaults = mergeChartConfig(chart.Bar.defaults, charJSPersonalDefaultOptionsBar); + var config = (options) ? mergeChartConfig(chart.Bar.defaults, options) : chart.Bar.defaults; + + return new Bar(data, config, context); + } ; + + this.HorizontalBar = function (data, options) { + chart.HorizontalBar.defaults = { + inGraphDataShow: false, + inGraphDataPaddingX: 3, + inGraphDataPaddingY: 0, + inGraphDataTmpl: "<%=v3%>", + inGraphDataAlign : "left", + inGraphDataVAlign : "middle", + inGraphDataRotate : 0, + inGraphDataFontFamily: "'Arial'", + inGraphDataFontSize: 12, + inGraphDataFontStyle: "normal", + inGraphDataFontColor: "#666", + inGraphDataXPosition : 3, + inGraphDataYPosition : 2, + 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, + scaleXGridLinesStep : 1, + scaleYGridLinesStep : 1, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + scaleTickSizeLeft: 5, + scaleTickSizeRight: 5, + scaleTickSizeBottom: 5, + scaleTickSizeTop: 5, + showYAxisMin: true, // Show the minimum value on Y axis (in original version, this minimum is not displayed - it can overlap the X labels) + rotateLabels: "smart", // smart <=> 0 degre if space enough; otherwise 45 degres if space enough otherwise90 degre; + barShowStroke: true, + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + barBorderRadius : 0, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null, + annotateLabel: "<%=(v1 == '' ? '' : v1) + (v1!='' && v2 !='' ? ' - ' : '')+(v2 == '' ? '' : v2)+(v1!='' || v2 !='' ? ':' : '') + v3 + ' (' + v6 + ' %)'%>" + + }; + + // merge annotate defaults + chart.HorizontalBar.defaults = mergeChartConfig(chart.defaults.commonOptions, chart.HorizontalBar.defaults); + chart.HorizontalBar.defaults = mergeChartConfig(chart.defaults.xyAxisCommonOptions, chart.HorizontalBar.defaults); + chart.HorizontalBar.defaults = mergeChartConfig(chart.HorizontalBar.defaults, charJSPersonalDefaultOptions); + chart.HorizontalBar.defaults = mergeChartConfig(chart.HorizontalBar.defaults, charJSPersonalDefaultOptionsHorizontalBar); + var config = (options) ? mergeChartConfig(chart.HorizontalBar.defaults, options) : chart.HorizontalBar.defaults; + + return new HorizontalBar(data, config, context); + } ; + + chart.defaults = {}; + chart.defaults.commonOptions = { + clearRect : true, // do not change clearRect options; for internal use only + dynamicDisplay : false, + graphSpaceBefore : 5, + graphSpaceAfter : 5, + canvasBorders: false, + canvasBackgroundColor : "none", + canvasBordersWidth: 3, + canvasBordersColor: "black", + graphTitle: "", + graphTitleFontFamily: "'Arial'", + graphTitleFontSize: 24, + graphTitleFontStyle: "bold", + graphTitleFontColor: "#666", + graphTitleSpaceBefore : 5, + graphTitleSpaceAfter : 5, + graphSubTitle: "", + graphSubTitleFontFamily: "'Arial'", + graphSubTitleFontSize: 18, + graphSubTitleFontStyle: "normal", + graphSubTitleFontColor: "#666", + graphSubTitleSpaceBefore : 5, + graphSubTitleSpaceAfter : 5, + footNote: "", + footNoteFontFamily: "'Arial'", + footNoteFontSize: 8, + footNoteFontStyle: "bold", + footNoteFontColor: "#666", + footNoteSpaceBefore : 5, + footNoteSpaceAfter : 5, + legend: false, + legendFontFamily: "'Arial'", + legendFontSize: 12, + legendFontStyle: "normal", + legendFontColor: "#666", + legendBlockSize: 15, + legendBorders: true, + legendBordersWidth: 1, + legendBordersColors: "#666", + legendBordersSpaceBefore : 5, + legendBordersSpaceAfter : 5, + legendBordersSpaceLeft : 5, + legendBordersSpaceRight : 5, + legendSpaceBeforeText : 5, + legendSpaceAfterText : 5, + legendSpaceLeftText : 5, + legendSpaceRightText : 5, + legendSpaceBetweenTextVertical : 5, + legendSpaceBetweenTextHorizontal : 5, + legendSpaceBetweenBoxAndText : 5, + annotateDisplay: false, + savePng : false, + savePngOuput : "NewWindow", // Allowed values : "NewWindow", "CurrentWindow", "Save" + savePngFunction: "mousedown right", + savePngBackgroundColor : 'WHITE', + annotateFunction: "mousemove", + annotateFontFamily: "'Arial'", + annotateBorder: 'none', + annotateBorderRadius: '2px', + annotateBackgroundColor: 'rgba(0,0,0,0.8)', + annotateFontSize: 12, + annotateFontColor: 'white', + annotateFontStyle: "normal", + annotatePadding: "3px", + annotateClassName : "", + crossText: [""], + crossTextIter: ["all"], + crossTextOverlay: [true], + crossTextFontFamily: ["'Arial'"], + crossTextFontSize: [12], + crossTextFontStyle: ["normal"], + crossTextFontColor: ["rgba(220,220,220,1)"], + crossTextRelativePosX: [2], + crossTextRelativePosY: [2], + crossTextBaseline: ["middle"], + crossTextAlign: ["center"], + crossTextPosX: [0], + crossTextPosY: [0], + crossTextAngle: [0], + crossTextFunction: null, + spaceTop: 0, + spaceBottom: 0, + spaceRight: 0, + spaceLeft: 0, + decimalSeparator : ".", + thousandSeparator : "", + roundNumber : "none", + roundPct : -1, + fmtV1 : "none", + fmtV2 : "none", + fmtV3 : "none", + fmtV4 : "none", + fmtV5 : "none", + fmtV6 : "none", + fmtV7 : "none", + fmtV8 : "none", + fmtV9 : "none", + fmtV10 : "none", + fmtV11 : "none", + fmtV12 : "none", + fmtV13 : "none", + fmtXLabel : "none", + fmtYLabel : "none", + fmtLegend : "none", + animationStartValue : 0, + animationStopValue : 1, + animationCount : 1, + animationPauseTime : 5, + animationBackward : false, + defaultStrokeColor : "rgba(220,220,220,1)", + defaultFillColor : "rgba(220,220,220,0.5)" + + }; + + chart.defaults.xyAxisCommonOptions = { + yAxisLeft: true, + yAxisRight: false, + xAxisBottom: true, + xAxisTop: false, + xAxisSpaceBetweenLabels : 5, + yAxisLabel: "", + yAxisFontFamily: "'Arial'", + yAxisFontSize: 16, + yAxisFontStyle: "normal", + yAxisFontColor: "#666", + yAxisLabelSpaceRight : 5, + yAxisLabelSpaceLeft : 5, + yAxisSpaceRight : 5, + yAxisSpaceLeft : 5, + xAxisLabel: "", + xAxisFontFamily: "'Arial'", + xAxisFontSize: 16, + xAxisFontStyle: "normal", + xAxisFontColor: "#666", + xAxisLabelSpaceBefore : 5, + xAxisLabelSpaceAfter : 5, + xAxisSpaceBefore : 5, + xAxisSpaceAfter : 5, + yAxisUnit: "", + yAxisUnitFontFamily: "'Arial'", + yAxisUnitFontSize: 8, + yAxisUnitFontStyle: "normal", + yAxisUnitFontColor: "#666", + yAxisUnitSpaceBefore : 5, + yAxisUnitSpaceAfter : 5 + }; + + + + var clear = function (c) { + c.clearRect(0, 0, width, height); + }; + + var PolarArea = function (data, config, ctx) { + + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, msr, midPosX, midPosY; + + if(typeof ctx.ChartNewId == undefined){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId = "PolarArea_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"PolarArea"))return; + + var realStartAngle=config.startAngle* (Math.PI / 180)+2*Math.PI; + + while (config.startAngle < 0){config.startAngle+=360;} + while (config.startAngle > 360){config.startAngle-=360;} + + while (realStartAngle < 0){realStartAngle+=2*Math.PI;} + while (realStartAngle > 2*Math.PI){realStartAngle-=2*Math.PI;} + + + config.logarithmic = false; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"PolarArea"); + + setRect(ctx,config); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, false, false,true); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + graphMax: config.scaleStartValue+config.scaleSteps*config.scaleStepWidth, + labels: [] + } + populateLabels(config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, calculatedScale.graphMax, config.scaleStepWidth); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, false, false,true); + } + + + midPosX = msr.leftNotUsableSize + (msr.availableWidth / 2); + midPosY = msr.topNotUsableSize + (msr.availableHeight / 2); + + + scaleHop = Math.floor(((Min([msr.availableHeight, msr.availableWidth]) / 2) - 5) / calculatedScale.steps); + + + //Wrap in an animation loop wrapper + animationLoop(config, drawScale, drawAllSegments, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPosX, midPosY, midPosX - ((Min([msr.availableHeight, msr.availableWidth]) / 2) - 5), midPosY + ((Min([msr.availableHeight, msr.availableWidth]) / 2) - 5), data); + + + function drawAllSegments(animationDecimal) { + var startAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI, + cumvalue = 0, + angleStep = 0, + scaleAnimation = 1, + rotateAnimation = 1; + angleStep=0; + + + + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))angleStep++; + + angleStep= (Math.PI * 2) / angleStep; + + while (startAngle < 0){startAngle+=2*Math.PI;} + while (startAngle > 2*Math.PI){startAngle-=2*Math.PI;} + + + if (config.animation) { + if (config.animateScale) { + scaleAnimation = animationDecimal; + } + if (config.animateRotate) { + rotateAnimation = animationDecimal; + } + } + if (animationDecimal >= 1) { + totvalue = 0; + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))totvalue += 1*data[i].value; + } + + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')){ + ctx.beginPath(); + ctx.arc(midPosX, midPosY, scaleAnimation * calculateOffset(config, 1*data[i].value, calculatedScale, scaleHop), startAngle, startAngle + rotateAnimation * angleStep, false); + ctx.lineTo(midPosX, midPosY); + ctx.closePath(); + if (typeof data[i].color == "function")ctx.fillStyle = data[i].color("COLOR",data,config,i,-1,animationDecimal,data[i].value); + else ctx.fillStyle = data[i].color; + ctx.fill(); + + startAngle += angleStep; + + if (config.segmentShowStroke) { + ctx.strokeStyle = config.segmentStrokeColor; + ctx.lineWidth = config.segmentStrokeWidth; + ctx.stroke(); + } + } + + } + if (animationDecimal >= 1) { + startAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI; + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')){ + cumvalue += 1*data[i].value; + startAngle += angleStep; + + if (typeof (data[i].title) == "string") lgtxt = data[i].title.trim(); + else lgtxt = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["ARC", midPosX, midPosY, 0, calculateOffset(config, 1*data[i].value, calculatedScale, scaleHop), startAngle - angleStep, startAngle, lgtxt, 1*data[i].value, cumvalue, totvalue, angleStep, i]; + + if (config.inGraphDataShow) { + + if(config.inGraphDataAnglePosition==1)posAngle=realStartAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==2)posAngle=realStartAngle-angleStep/2+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==3)posAngle=realStartAngle-angleStep+config.inGraphDataPaddingAngle*(Math.PI/180); + + if(config.inGraphDataRadiusPosition==1)labelRadius=0+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==2)labelRadius=calculateOffset(config, 1*data[i].value, calculatedScale, scaleHop)/2+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==3)labelRadius=calculateOffset(config, 1*data[i].value, calculatedScale, scaleHop)+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==4)labelRadius=scaleHop*calculatedScale.steps+config.inGraphDataPaddingRadius; + + + ctx.save() + + if(config.inGraphDataAlign=="off-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "left"; + else ctx.textAlign="right"; + } + else if(config.inGraphDataAlign=="to-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "right"; + else ctx.textAlign="left"; + } + else ctx.textAlign = config.inGraphDataAlign; + if(config.inGraphDataVAlign=="off-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } + else if(config.inGraphDataVAlign=="to-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } + else ctx.textBaseline = config.inGraphDataVAlign; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,1*data[i].value,config.fmtV2), v3 : fmtChartJS(config,cumvalue,config.fmtV3), v4 : fmtChartJS(config,totvalue,config.fmtV4), v5 : fmtChartJS(config,angleStep,config.fmtV5), v6 : roundToWithThousands(config,fmtChartJS(config,100 * data[i].value / totvalue,config.fmtV6),config.roundPct), v7 : fmtChartJS(config,midPosX,config.fmtV7),v8 : fmtChartJS(config,midPosY,config.fmtV8),v9 : fmtChartJS(config,0,config.fmtV9),v10 : fmtChartJS(config,calculateOffset(config, 1*data[i].value, calculatedScale, scaleHop),config.fmtV10),v11 : fmtChartJS(config,startAngle - angleStep,config.fmtV11),v12 : fmtChartJS(config,angleStep,config.fmtV12),v13 : fmtChartJS(config,i,config.fmtV13),data:data}); + ctx.translate(midPosX + labelRadius*Math.cos(posAngle), midPosY - labelRadius*Math.sin(posAngle)); + + if(config.inGraphDataRotate=="inRadiusAxis")ctx.rotate(2*Math.PI-posAngle); + else if(config.inGraphDataRotate=="inRadiusAxisRotateLabels") + { + if ((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI/2 && (posAngle+2*Math.PI)%(2*Math.PI)<3*Math.PI/2)ctx.rotate(3*Math.PI-posAngle); + else ctx.rotate(2*Math.PI-posAngle); + } + else ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + realStartAngle-=angleStep; + } } + } + } + + + } ; + + + + function drawScale() { + for (var i = 0; i < calculatedScale.steps; i++) { + //If the line object is there + if (config.scaleShowLine) { + ctx.beginPath(); + ctx.arc(midPosX, midPosY, scaleHop * (i + 1), 0, (Math.PI * 2), true); + ctx.strokeStyle = config.scaleLineColor; + ctx.lineWidth = config.scaleLineWidth; + ctx.stroke(); + } + + if (config.scaleShowLabels) { + ctx.textAlign = "center"; + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + var label = calculatedScale.labels[i + 1]; + //If the backdrop object is within the font object + if (config.scaleShowLabelBackdrop) { + var textWidth = ctx.measureTextMultiLine(label,config.scaleFontSize).textWidth; + ctx.fillStyle = config.scaleBackdropColor; + ctx.beginPath(); + ctx.rect( + Math.round(midPosX - textWidth / 2 - config.scaleBackdropPaddingX), //X + Math.round(midPosY - (scaleHop * (i + 1)) - config.scaleFontSize * 0.5 - config.scaleBackdropPaddingY),//Y + Math.round(textWidth + (config.scaleBackdropPaddingX * 2)), //Width + Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY * 2)) //Height + ); + ctx.fill(); + } + ctx.textBaseline = "middle"; + ctx.fillStyle = config.scaleFontColor; + ctx.fillTextMultiLine(label, midPosX, midPosY - (scaleHop * (i + 1)),ctx.textBaseline,config.scaleFontSize); + } + } + } ; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.length; i++) { + if (1*data[i].value > upperValue) { upperValue = 1*data[i].value; } + if (1*data[i].value < lowerValue) { lowerValue = 1*data[i].value; } + }; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + 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, msr, midPosX, midPosY; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="Radar_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"Radar"))return; + + while (config.startAngle < 0){config.startAngle+=360;} + while (config.startAngle > 360){config.startAngle-=360;} + + config.logarithmic = false; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"Radar"); + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + setRect(ctx,config); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + + if (!config.scaleOverride) { + + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, false, true,config.datasetFill); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + graphMax: config.scaleStartValue+config.scaleSteps*config.scaleStepWidth, + labels: [] + } + populateLabels(config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, calculatedScale.graphMax, config.scaleStepWidth); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, false, true,config.datasetFill); + } + + calculateDrawingSizes(); + + midPosY = msr.topNotUsableSize + (msr.availableHeight / 2); + scaleHop = maxSize / (calculatedScale.steps); + + //Wrap in an animation loop wrapper + animationLoop(config, drawScale, drawAllDataPoints, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPosX, midPosY, midPosX - maxSize, midPosY + maxSize, data); + + //Radar specific functions. + function drawAllDataPoints(animationDecimal) { + + var totvalue = new Array(); + var maxvalue = new Array(); + + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { totvalue[j] = 0; maxvalue[j] = -999999999; } } + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { if (!(typeof(data.datasets[i].data[j])=='undefined')){totvalue[j] += 1*data.datasets[i].data[j]; maxvalue[j] = Max([maxvalue[j], 1*data.datasets[i].data[j]]); } } } + + var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length; + + ctx.save(); + + //We accept multiple data sets for radar charts, so show loop through each set + for (var i = 0; i < data.datasets.length; i++) { + + if (animationDecimal >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + var fPt=-1; + + + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + if (fPt==-1) + { + ctx.beginPath(); + ctx.moveTo(midPosX + animationDecimal *(Math.cos(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop)), midPosY - animationDecimal *(Math.sin(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop))); + fPt=j; + } + else + { + ctx.lineTo(midPosX + animationDecimal *(Math.cos(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop)), midPosY - animationDecimal *(Math.sin(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop))); + } + + if (animationDecimal >= 1) { + if (i == 0) divprev = 0; + else divprev = data.datasets[i].data[j] - data.datasets[i - 1].data[j]; + if (i == data.datasets.length - 1) divnext = 0; + else divnext = data.datasets[i + 1].data[j] - data.datasets[i].data[j]; + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["POINT", midPosX + Math.cos(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop), midPosY - Math.sin(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop), lgtxt, lgtxt2, 1*data.datasets[i].data[j], divprev, divnext, maxvalue[j], totvalue[j], i, j]; + } + } + } + + ctx.closePath(); + + if(config.datasetFill){ + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,-1,animationDecimal,-1); + else if(typeof data.datasets[i].fillColor=="string")ctx.fillStyle = data.datasets[i].fillColor; + else ctx.fillStyle=config.defaultFillColor; + } + else ctx.fillStyle="rgba(0,0,0,0)"; + if (typeof data.datasets[i].strokeColor == "function")ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,-1,animationDecimal,-1); + else if(typeof data.datasets[i].strokeColor=="string")ctx.strokeStyle = data.datasets[i].strokeColor; + else ctx.strokeStyle=config.defaultStrokeColor; + ctx.lineWidth = config.datasetStrokeWidth; + ctx.fill(); + ctx.stroke(); + + if (config.pointDot) { + ctx.beginPath(); + + if (typeof data.datasets[i].pointColor == "function")ctx.fillStyle = data.datasets[i].pointColor("POINTCOLOR",data,config,i,-1,animationDecimal,-1); + else ctx.fillStyle = data.datasets[i].pointColor; + if (typeof data.datasets[i].pointStrokeColor == "function")ctx.strokeStyle = data.datasets[i].pointStrokeColor("POINTSTROKECOLOR",data,config,i,-1,animationDecimal,-1); + else ctx.strokeStyle = data.datasets[i].pointStrokeColor; + + ctx.lineWidth = config.pointDotStrokeWidth; + for (var k = 0; k < data.datasets[i].data.length; k++) { + if (!(typeof(data.datasets[i].data[k])=='undefined')) { + ctx.beginPath(); + ctx.arc(midPosX + animationDecimal *(Math.cos(config.startAngle*Math.PI/180 - k * rotationDegree) * calculateOffset(config, data.datasets[i].data[k], calculatedScale, scaleHop)), midPosY - animationDecimal * (Math.sin(config.startAngle*Math.PI/180 - k * rotationDegree) * calculateOffset(config, data.datasets[i].data[k], calculatedScale, scaleHop)), config.pointDotRadius, 2 * Math.PI, false); + ctx.fill(); + ctx.stroke(); + } + } + + } + } + ctx.restore(); + + if (animationDecimal >= 1 && config.inGraphDataShow) { + for (var i = 0; i < data.datasets.length; i++) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + + if (i == 0) divprev = 0; + else divprev = data.datasets[i].data[j] - data.datasets[i - 1].data[j]; + if (i == data.datasets.length - 1) divnext = 0; + else divnext = data.datasets[i + 1].data[j] - data.datasets[i].data[j]; + + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + + if(config.inGraphDataAlign=="off-center"){ + + if(config.inGraphDataRotate=="inRadiusAxis" || (config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI) <= Math.PI/2)ctx.textAlign = "left"; + else ctx.textAlign="right"; + } + else if(config.inGraphDataAlign=="to-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "right"; + else ctx.textAlign="left"; + } + else ctx.textAlign = config.inGraphDataAlign; + + if(config.inGraphDataVAlign=="off-center"){ + if((config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } + else if(config.inGraphDataVAlign=="to-center"){ + if((config.startAngle*Math.PI/180-j * rotationDegree+4*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } + else ctx.textBaseline = config.inGraphDataVAlign; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var radiusPrt; + if(config.inGraphDataRadiusPosition==1)radiusPrt=0+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==2)radiusPrt=(calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop))/2+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==3)radiusPrt=(calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop))+config.inGraphDataPaddingRadius; + + ctx.translate(midPosX + Math.cos(config.startAngle*Math.PI/180 - j * rotationDegree) * radiusPrt, midPosY - Math.sin(config.startAngle*Math.PI/180 - j * rotationDegree) * radiusPrt); + + if(config.inGraphDataRotate=="inRadiusAxis")ctx.rotate(j * rotationDegree); + else if(config.inGraphDataRotate=="inRadiusAxisRotateLabels"){ + if ((j * rotationDegree+2*Math.PI)%(2*Math.PI)>Math.PI/2 && (j * rotationDegree+2*Math.PI)%(2*Math.PI)<3*Math.PI/2)ctx.rotate(3*Math.PI+j * rotationDegree); + else ctx.rotate(2*Math.PI+j * rotationDegree); + } + else ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,lgtxt2,config.fmtV2), v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), v4 : fmtChartJS(config,divprev,config.fmtV4), v5 : fmtChartJS(config,divnext,config.fmtV5), v6 : fmtChartJS(config,maxvalue[j],config.fmtV6), v7 : fmtChartJS(config,totvalue[j],config.fmtV7), v8 : roundToWithThousands(config,fmtChartJS(config,100 * data.datasets[i].data[j] / totvalue[j],config.fmtV8),config.roundPct),v9 : fmtChartJS(config,midPosX + Math.cos(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop),config.fmtV9),v10 : fmtChartJS(config,midPosY - Math.sin(config.startAngle*Math.PI/180 - j * rotationDegree) * calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop),config.fmtV10),v11 : fmtChartJS(config,i,config.fmtV11), v12 : fmtChartJS(config,j,config.fmtV12),data:data}); + + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + + } + } + } + } + + + } ; + function drawScale() { + + var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length; + ctx.save(); + ctx.translate(midPosX, midPosY); + + ctx.rotate((90-config.startAngle)*Math.PI/180); + + if (config.angleShowLineOut) { + ctx.strokeStyle = config.angleLineColor; + ctx.lineWidth = config.angleLineWidth; + for (var h = 0; h < data.datasets[0].data.length; h++) { + + ctx.rotate(rotationDegree); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(0, -maxSize); + ctx.stroke(); + } + } + + for (var i = 0; i < calculatedScale.steps; i++) { + ctx.beginPath(); + + if (config.scaleShowLine) { + ctx.strokeStyle = config.scaleLineColor; + ctx.lineWidth = config.scaleLineWidth; + ctx.moveTo(0, -scaleHop * (i + 1)); + for (var j = 0; j < data.datasets[0].data.length; j++) { + ctx.rotate(rotationDegree); + ctx.lineTo(0, -scaleHop * (i + 1)); + } + ctx.closePath(); + ctx.stroke(); + + } + } + + ctx.rotate(-(90-config.startAngle)*Math.PI/180); + if (config.scaleShowLabels) { + for (var i = 0; i < calculatedScale.steps; i++) { + + ctx.textAlign = 'center'; + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + ctx.textBaseline = "middle"; + + if (config.scaleShowLabelBackdrop) { + var textWidth = ctx.measureTextMultiLine(calculatedScale.labels[i + 1],config.scaleFontSize).textWidth; + ctx.fillStyle = config.scaleBackdropColor; + ctx.beginPath(); + ctx.rect( + Math.round(Math.cos(config.startAngle*Math.PI/180)* (scaleHop * (i + 1))-textWidth / 2 - config.scaleBackdropPaddingX), //X + Math.round((-Math.sin(config.startAngle*Math.PI/180)*scaleHop * (i + 1)) - config.scaleFontSize * 0.5 - config.scaleBackdropPaddingY),//Y + Math.round(textWidth + (config.scaleBackdropPaddingX * 2)), //Width + Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY * 2)) //Height + ); + ctx.fill(); + } + ctx.fillStyle = config.scaleFontColor; + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], Math.cos(config.startAngle*Math.PI/180)* (scaleHop * (i + 1)), -Math.sin(config.startAngle*Math.PI/180)*scaleHop * (i + 1),ctx.textBaseline,config.scaleFontSize); + } + } + + for (var k = 0; k < data.labels.length; k++) { + ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize + "px " + config.pointLabelFontFamily; + ctx.fillStyle = config.pointLabelFontColor; + var opposite = Math.sin((90-config.startAngle)*Math.PI/180+rotationDegree * k) * (maxSize + config.pointLabelFontSize); + var adjacent = Math.cos((90-config.startAngle)*Math.PI/180+rotationDegree * k) * (maxSize + config.pointLabelFontSize); + + var vangle=(90-config.startAngle)*Math.PI/180+rotationDegree * k; + while(vangle<0)vangle=vangle+2*Math.PI; + while(vangle>2*Math.PI)vangle=vangle-2*Math.PI; + + + if (vangle == Math.PI || vangle == 0) { + ctx.textAlign = "center"; + } + else if (vangle > Math.PI) { + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillTextMultiLine(data.labels[k], opposite, -adjacent,ctx.textBaseline,config.pointLabelFontSize); + + } + ctx.restore(); + }; + + function calculateDrawingSizes() { + var midX, mxlb,maxL,maxR,iter,nbiter,prevMaxSize,prevMidX; + var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length; + var rotateAngle=config.startAngle*Math.PI/180; + + // Compute range for Mid Point of graph + ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize + "px " + config.pointLabelFontFamily; + if(!config.graphMaximized) { + maxR=msr.availableWidth/2; + maxL=msr.availableWidth/2; + nbiter=1; + } + else { + maxR=msr.availableWidth/2; + maxL=msr.availableWidth/2; + nbiter=40; + for (var i = 0; i < data.labels.length; i++) { + var textMeasurement = ctx.measureTextMultiLine(data.labels[i],config.scaleFontSize).textWidth+ctx.measureTextMultiLine(data.labels[i],config.scaleFontSize).textHeight; + mxlb=(msr.availableWidth-textMeasurement)/(1+Math.abs(Math.cos(rotateAngle))); + if((rotateAngle < Math.PI/2 && rotateAngle > -Math.PI/2) || rotateAngle > 3*Math.PI/2){ + if (mxlb -Math.PI/2) || rotateAngle > 3*Math.PI/2){ + mxlb=((msr.availableWidth-midX)- textMeasurement)/Math.abs(Math.cos(rotateAngle)); + } + else if (Math.cos(rotateAngle!=0)){ + mxlb=(midX- textMeasurement)/Math.abs(Math.cos(rotateAngle)); + } + if (mxlb < maxSize)maxSize=mxlb; + if(Math.sin(rotateAngle)*msr.availableHeight/2 > msr.availableHeight/2 - config.scaleFontSize*2){ + mxlb=Math.sin(rotateAngle)*msr.availableHeight/2-1.5*config.scaleFontSize; + if(mxlb < maxSize)maxSize=mxlb; + } + rotateAngle-=rotationDegree; + } + if(maxSize>prevMaxSize){ + prevMaxSize=maxSize; + midPosX=midX+msr.rightNotUsableSize; + } + } + + maxSize =prevMaxSize - config.scaleFontSize/2; + //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 < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (1*data.datasets[i].data[j] > upperValue) { upperValue = 1*data.datasets[i].data[j] } + if (1*data.datasets[i].data[j] < lowerValue) { lowerValue = 1*data.datasets[i].data[j] } + } + } + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + 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; + var msr, midPieX, midPieY,pieRadius; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="Pie_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"Pie"))return; + + while (config.startAngle < 0){config.startAngle+=360;} + while (config.startAngle > 360){config.startAngle-=360;} + + config.logarithmic = false; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"Pie"); + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + + setRect(ctx,config); + + msr = setMeasures(data, config, ctx, height, width, null, true, false, false, false,true); + +// midPieX = msr.leftNotUsableSize + (msr.availableWidth / 2); +// midPieY = msr.topNotUsableSize + (msr.availableHeight / 2); +// pieRadius = Min([msr.availableHeight / 2, msr.availableWidth / 2]) - 5; + + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined'))segmentTotal += 1*data[i].value; + } + + calculateDrawingSize(); + + animationLoop(config, null, drawPieSegments, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPieX, midPieY, midPieX - pieRadius, midPieY + pieRadius, data); + + + + function drawPieSegments(animationDecimal) { + + + + var cumulativeAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI , + cumvalue = 0, + scaleAnimation = 1, + rotateAnimation = 1; + + var realCumulativeAngle=config.startAngle* (Math.PI / 180)+2*Math.PI; + + while (cumulativeAngle < 0){cumulativeAngle+=2*Math.PI;} + while (cumulativeAngle > 2*Math.PI){cumulativeAngle-=2*Math.PI;} + + while (realCumulativeAngle < 0){realCumulativeAngle+=2*Math.PI;} + while (realCumulativeAngle > 2*Math.PI){realCumulativeAngle-=2*Math.PI;} + + if (config.animation) { + if (config.animateScale) { + scaleAnimation = animationDecimal; + } + if (config.animateRotate) { + rotateAnimation = animationDecimal; + } + } + if (animationDecimal >= 1) { + totvalue = 0; + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))totvalue += 1*data[i].value; + } + + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')){ + var segmentAngle = rotateAnimation * ((1*data[i].value / segmentTotal) * (Math.PI * 2)); + if(segmentAngle >= Math.PI*2)segmentAngle=Math.PI*2-0.001; // bug on Android when segmentAngle is >= 2*PI; + ctx.beginPath(); + ctx.arc(midPieX, midPieY, scaleAnimation * pieRadius, cumulativeAngle, cumulativeAngle+segmentAngle ); + + ctx.lineTo(midPieX, midPieY); + ctx.closePath(); + if (typeof data[i].color == "function")ctx.fillStyle = data[i].color("COLOR",data,config,i,-1,animationDecimal,data[i].value); + else ctx.fillStyle = data[i].color; + ctx.fill(); + cumulativeAngle += segmentAngle; + + cumvalue += 1*data[i].value; + + if (config.segmentShowStroke) { + ctx.lineWidth = config.segmentStrokeWidth; + ctx.strokeStyle = config.segmentStrokeColor; + ctx.stroke(); + } + + if (animationDecimal >= 1) { + if (typeof (data[i].title) == "string") lgtxt = data[i].title.trim(); + else lgtxt = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["ARC", midPieX, midPieY, 0, pieRadius, cumulativeAngle - segmentAngle, cumulativeAngle, lgtxt, 1*data[i].value, cumvalue, totvalue, segmentAngle, i]; + + + if (config.inGraphDataShow) { + + if(config.inGraphDataAnglePosition==1)posAngle=realCumulativeAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==2)posAngle=realCumulativeAngle-segmentAngle/2+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==3)posAngle=realCumulativeAngle-segmentAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + + if(config.inGraphDataRadiusPosition==1)labelRadius=0+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==2)labelRadius=pieRadius/2+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==3)labelRadius=pieRadius+config.inGraphDataPaddingRadius; + + realCumulativeAngle -= segmentAngle; + + + ctx.save(); + + if(config.inGraphDataAlign=="off-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "left"; + else ctx.textAlign="right"; + } + else if(config.inGraphDataAlign=="to-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "right"; + else ctx.textAlign="left"; + } + else ctx.textAlign = config.inGraphDataAlign; + if(config.inGraphDataVAlign=="off-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } + else if(config.inGraphDataVAlign=="to-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } + else ctx.textBaseline = config.inGraphDataVAlign; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,1*data[i].value,config.fmtV2), v3 : fmtChartJS(config,cumvalue,config.fmtV3), v4 : fmtChartJS(config,totvalue,config.fmtV4), v5 : fmtChartJS(config,segmentAngle,config.fmtV5), v6 : roundToWithThousands(config, fmtChartJS(config,100 * data[i].value / totvalue,config.fmtV6), config.roundPct), v7 : fmtChartJS(config,midPieX,config.fmtV7),v8 : fmtChartJS(config,midPieY,config.fmtV8),v9 : fmtChartJS(config,0,config.fmtV9),v10 : fmtChartJS(config,pieRadius,config.fmtV10),v11 : fmtChartJS(config,cumulativeAngle-segmentAngle,config.fmtV11),v12 : fmtChartJS(config,cumulativeAngle,config.fmtV12),v13 : fmtChartJS(config,i,config.fmtV13),data:data}); + ctx.translate(midPieX + labelRadius*Math.cos(posAngle), midPieY - labelRadius*Math.sin(posAngle)); + + if(config.inGraphDataRotate=="inRadiusAxis")ctx.rotate(2*Math.PI-posAngle); + else if(config.inGraphDataRotate=="inRadiusAxisRotateLabels") + { + if ((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI/2 && (posAngle+2*Math.PI)%(2*Math.PI)<3*Math.PI/2)ctx.rotate(3*Math.PI-posAngle); + else ctx.rotate(2*Math.PI-posAngle); + } + else ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + } + } + } + } + }; + + function calculateDrawingSize() { + + var lgtxt; + + var cumulativeAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI , + cumvalue = 0; + + while (cumulativeAngle < 0){cumulativeAngle+=2*Math.PI;} + while (cumulativeAngle > 2*Math.PI){cumulativeAngle-=2*Math.PI;} + + + midPieX = msr.leftNotUsableSize + (msr.availableWidth / 2); + midPieY = msr.topNotUsableSize + (msr.availableHeight / 2); + pieRadius = Min([msr.availableHeight / 2, msr.availableWidth / 2]) - 5; + + + // Computerange Pie Radius + + if(config.inGraphDataShow && config.inGraphDataRadiusPosition==3 && config.inGraphDataAlign=="off-center" && config.inGraphDataRotate==0) { + pieRadius = Min([msr.availableHeight / 2, msr.availableWidth / 2]) - config.inGraphDataFontSize - config.inGraphDataPaddingRadius -5; + + var realCumulativeAngle=config.startAngle* (Math.PI / 180)+2*Math.PI; + + while (realCumulativeAngle < 0){realCumulativeAngle+=2*Math.PI;} + while (realCumulativeAngle > 2*Math.PI){realCumulativeAngle-=2*Math.PI;} + + var totvalue = 0; + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))totvalue += 1*data[i].value; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + var cumvalue=0; + var posAngle; + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')) { + cumvalue += 1*data[i].value; + var segmentAngle = (1*data[i].value / segmentTotal) * (Math.PI * 2); + cumulativeAngle += segmentAngle; + + if(config.inGraphDataAnglePosition==1)posAngle=realCumulativeAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==2)posAngle=realCumulativeAngle-segmentAngle/2+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==3)posAngle=realCumulativeAngle-segmentAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + realCumulativeAngle -= segmentAngle; + + if (typeof (data[i].title) == "string") lgtxt = data[i].title.trim(); + else lgtxt = ""; + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,1*data[i].value,config.fmtV2), v3 : fmtChartJS(config,cumvalue,config.fmtV3), v4 : fmtChartJS(config,totvalue,config.fmtV4), v5 : fmtChartJS(config,segmentAngle,config.fmtV5), v6 : roundToWithThousands(config, fmtChartJS(config,100 * data[i].value / totvalue,config.fmtV6), config.roundPct), v7 : fmtChartJS(config,midPieX,config.fmtV7),v8 : fmtChartJS(config,midPieY,config.fmtV8),v9 : fmtChartJS(config,0,config.fmtV9),v10 : fmtChartJS(config,pieRadius,config.fmtV10),v11 : fmtChartJS(config,cumulativeAngle-segmentAngle,config.fmtV11),v12 : fmtChartJS(config,cumulativeAngle,config.fmtV12),v13 : fmtChartJS(config,i,config.fmtV13),data:data}); + var textMeasurement = ctx.measureText(dispString).width; + + var MaxRadiusX= Math.abs((msr.availableWidth / 2 - textMeasurement)/Math.cos(posAngle))-config.inGraphDataPaddingRadius -5; + if(MaxRadiusX 360){config.startAngle-=360;} + + while (realCumulativeAngle < 0){realCumulativeAngle+=2*Math.PI;} + while (realCumulativeAngle > 2*Math.PI){realCumulativeAngle-=2*Math.PI;} + + + config.logarithmic = false; + + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"Doughnut"); + + setRect(ctx,config); + msr = setMeasures(data, config, ctx, height, width, null, true, false, false, false,true); + + calculateDrawingSize(); + + var cutoutRadius = doughnutRadius * (config.percentageInnerCutout / 100); + + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined'))segmentTotal += 1*data[i].value; + } + + + animationLoop(config, null, drawPieSegments, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, midPieX, midPieY, midPieX - doughnutRadius, midPieY + doughnutRadius, data); + + function drawPieSegments(animationDecimal) { + var cumulativeAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI , + cumvalue = 0, + scaleAnimation = 1, + rotateAnimation = 1; + + while (cumulativeAngle < 0){cumulativeAngle+=2*Math.PI;} + while (cumulativeAngle > 2*Math.PI){cumulativeAngle-=2*Math.PI;} + + if (config.animation) { + if (config.animateScale) { + scaleAnimation = animationDecimal; + } + if (config.animateRotate) { + rotateAnimation = animationDecimal; + } + } + + if (animationDecimal >= 1) { + totvalue = 0; + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))totvalue += 1*data[i].value; + } + + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')){ + var segmentAngle = rotateAnimation * ((1*data[i].value / segmentTotal) * (Math.PI * 2)); + if(segmentAngle >= Math.PI*2)segmentAngle=Math.PI*2-0.001; // but on Android when segmentAngle is >= 2*PI; + ctx.beginPath(); + ctx.arc(midPieX, midPieY, scaleAnimation * doughnutRadius, cumulativeAngle, cumulativeAngle + segmentAngle, false); + ctx.arc(midPieX, midPieY, scaleAnimation * cutoutRadius, cumulativeAngle + segmentAngle, cumulativeAngle, true); + ctx.closePath(); + if (typeof data[i].color == "function")ctx.fillStyle = data[i].color("COLOR",data,config,i,-1,animationDecimal,data[i].value); + else ctx.fillStyle = data[i].color; + ctx.fill(); + + cumulativeAngle += segmentAngle; + cumvalue += 1*data[i].value; + + if (config.segmentShowStroke) { + ctx.lineWidth = config.segmentStrokeWidth; + ctx.strokeStyle = config.segmentStrokeColor; + ctx.stroke(); + } + + if (animationDecimal >= 1) { + if (typeof (data[i].title) == "string") lgtxt = data[i].title.trim(); + else lgtxt = ""; + + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["ARC", midPieX, midPieY, cutoutRadius, doughnutRadius, cumulativeAngle - segmentAngle, cumulativeAngle, lgtxt, 1*data[i].value, cumvalue, totvalue, segmentAngle, i]; + if (config.inGraphDataShow) { + + if(config.inGraphDataAnglePosition==1)posAngle=realCumulativeAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==2)posAngle=realCumulativeAngle-segmentAngle/2+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==3)posAngle=realCumulativeAngle-segmentAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + + if(config.inGraphDataRadiusPosition==1)labelRadius=cutoutRadius+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==2)labelRadius=cutoutRadius+(doughnutRadius-cutoutRadius)/2+config.inGraphDataPaddingRadius; + else if(config.inGraphDataRadiusPosition==3)labelRadius=doughnutRadius+config.inGraphDataPaddingRadius; + + realCumulativeAngle -= segmentAngle; + + + ctx.save(); + + if(config.inGraphDataAlign=="off-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "left"; + else ctx.textAlign="right"; + } + else if(config.inGraphDataAlign=="to-center"){ + if(config.inGraphDataRotate=="inRadiusAxis" || (posAngle+2*Math.PI)%(2*Math.PI) > 3*Math.PI/2 || (posAngle+2*Math.PI)%(2*Math.PI) < Math.PI/2)ctx.textAlign = "right"; + else ctx.textAlign="left"; + } + else ctx.textAlign = config.inGraphDataAlign; + if(config.inGraphDataVAlign=="off-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "top"; + else ctx.textBaseline = "bottom"; + } + else if(config.inGraphDataVAlign=="to-center"){ + if((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI)ctx.textBaseline = "bottom"; + else ctx.textBaseline = "top"; + } + else ctx.textBaseline = config.inGraphDataVAlign; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,1*data[i].value,config.fmtV2), v3 : fmtChartJS(config,cumvalue,config.fmtV3), v4 : fmtChartJS(config,totvalue,config.fmtV4), v5 : fmtChartJS(config,segmentAngle,config.fmtV5), v6 : roundToWithThousands(config, fmtChartJS(config,100 * data[i].value / totvalue,config.fmtV6), config.roundPct), v7 : fmtChartJS(config,midPieX,config.fmtV7),v8 : fmtChartJS(config,midPieY,config.fmtV8),v9 : fmtChartJS(config,cutoutRadius,config.fmtV9),v10 : fmtChartJS(config,doughnutRadius,config.fmtV10),v11 : fmtChartJS(config,cumulativeAngle-segmentAngle,config.fmtV11),v12 : fmtChartJS(config,cumulativeAngle,config.fmtV12),v13 : fmtChartJS(config,i,config.fmtV13)}); + ctx.translate(midPieX + labelRadius*Math.cos(posAngle), midPieY - labelRadius*Math.sin(posAngle)); + + if(config.inGraphDataRotate=="inRadiusAxis")ctx.rotate(2*Math.PI-posAngle); + else if(config.inGraphDataRotate=="inRadiusAxisRotateLabels") + { + if ((posAngle+2*Math.PI)%(2*Math.PI)>Math.PI/2 && (posAngle+2*Math.PI)%(2*Math.PI)<3*Math.PI/2)ctx.rotate(3*Math.PI-posAngle); + else ctx.rotate(2*Math.PI-posAngle); + } + else ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + } + + + } + } + } + } ; + + function calculateDrawingSize() { + + var lgtxt; + var cumulativeAngle = -config.startAngle * (Math.PI / 180)+2*Math.PI , + cumvalue = 0; + + while (cumulativeAngle < 0){cumulativeAngle+=2*Math.PI;} + while (cumulativeAngle > 2*Math.PI){cumulativeAngle-=2*Math.PI;} + + midPieX = msr.leftNotUsableSize + (msr.availableWidth / 2); + midPieY = msr.topNotUsableSize + (msr.availableHeight / 2); + doughnutRadius = Min([msr.availableHeight / 2, msr.availableWidth / 2]) - 5; + + + // Computerange Pie Radius + + if(config.inGraphDataShow && config.inGraphDataRadiusPosition==3 && config.inGraphDataAlign=="off-center" && config.inGraphDataRotate==0) { + doughnutRadius = Min([msr.availableHeight / 2, msr.availableWidth / 2]) - config.inGraphDataFontSize - config.inGraphDataPaddingRadius -5; + + var realCumulativeAngle=config.startAngle* (Math.PI / 180)+2*Math.PI; + + while (realCumulativeAngle < 0){realCumulativeAngle+=2*Math.PI;} + while (realCumulativeAngle > 2*Math.PI){realCumulativeAngle-=2*Math.PI;} + + var totvalue = 0; + for (var i = 0; i < data.length; i++) if (!(typeof(data[i].value)=='undefined'))totvalue += 1*data[i].value; + + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + var posAngle; + var cumulativeAngle=0; + for (var i = 0; i < data.length; i++) { + if (!(typeof(data[i].value)=='undefined')){ + cumvalue += 1*data[i].value; + var segmentAngle = (1*data[i].value / segmentTotal) * (Math.PI * 2); + cumulativeAngle += segmentAngle; + + if(config.inGraphDataAnglePosition==1)posAngle=realCumulativeAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==2)posAngle=realCumulativeAngle-segmentAngle/2+config.inGraphDataPaddingAngle*(Math.PI/180); + else if(config.inGraphDataAnglePosition==3)posAngle=realCumulativeAngle-segmentAngle+config.inGraphDataPaddingAngle*(Math.PI/180); + realCumulativeAngle -= segmentAngle; + + if (typeof (data[i].title) == "string") lgtxt = data[i].title.trim(); + else lgtxt = ""; + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,1*data[i].value,config.fmtV2), v3 : fmtChartJS(config,cumvalue,config.fmtV3), v4 : fmtChartJS(config,totvalue,config.fmtV4), v5 : fmtChartJS(config,segmentAngle,config.fmtV5), v6 : roundToWithThousands(config, fmtChartJS(config,100 * data[i].value / totvalue,config.fmtV6), config.roundPct), v7 : fmtChartJS(config,midPieX,config.fmtV7),v8 : fmtChartJS(config,midPieY,config.fmtV8),v9 : fmtChartJS(config,cutoutRadius,config.fmtV9),v10 : fmtChartJS(config,doughnutRadius,config.fmtV10),v11 : fmtChartJS(config,cumulativeAngle-segmentAngle,config.fmtV11),v12 : fmtChartJS(config,cumulativeAngle,config.fmtV12),v13 : fmtChartJS(config,i,config.fmtV13),data:data}); + var textMeasurement = ctx.measureText(dispString).width; + + var MaxRadiusX= Math.abs((msr.availableWidth / 2 - textMeasurement)/Math.cos(posAngle))-config.inGraphDataPaddingRadius - 5; + if(MaxRadiusX= 1) { + if (typeof drawMath == "function") { + drawMath(ctx,config,data,msr,{xAxisPosY:xAxisPosY,yAxisPosX:yAxisPosX,valueHop:valueHop,scaleHop:scaleHop, + zeroY:zeroY,calculatedScale:calculatedScale,calculateOffset:calculateOffset}); + } + } + } ; + + function drawScale() { + + //X axis line + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY); + + ctx.stroke(); + + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + + //Check i isnt 0, so we dont go over the Y axis twice. + + if (config.scaleShowGridLines && i > 0 && i % config.scaleXGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + } + else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + + //Y axis + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + ctx.stroke(); + + for (var j = 0 ; j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && j % config.scaleYGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY - ((j + 1) * scaleHop)); + } + else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + } ; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + + //X Labels + if(config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } + else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "center"; + + } + ctx.fillStyle = config.scaleFontColor; + + if(config.xAxisBottom){ + for (var i = 0; i < data.labels.length; i++) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + i * valueHop - msr.highestXLabel/2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), 0, 0,ctx.textBaseline,config.scaleFontSize); + } + else { + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), yAxisPosX + i * valueHop, msr.xLabelPos,ctx.textBaseline,config.scaleFontSize); + } + ctx.restore(); + } + } + } + + //Y Labels + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + + for (var j = ((config.showYAxisMin) ? -1 : 0) ; j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (config.scaleTickSizeLeft + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (config.scaleTickSizeRight + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + } + } + } ; + + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.datasets.length; i++) { + var mathFctName = data.datasets[i].drawMathDeviation; + var mathValueHeight = 0; + if (typeof eval(mathFctName) == "function") { + var parameter = {data:data,datasetNr: i}; + mathValueHeight = window[mathFctName](parameter); + } + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (1*data.datasets[i].data[j]+mathValueHeight > upperValue) { upperValue = 1*data.datasets[i].data[j]+mathValueHeight }; + if (1*data.datasets[i].data[j]-mathValueHeight < lowerValue) { lowerValue = 1*data.datasets[i].data[j]-mathValueHeight }; + + } + }; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + // AJOUT CHANGEMENT + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + labelHeight = config.scaleFontSize; + scaleHeight = msr.availableHeight; + + 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 StackedBar = function (data, config, ctx) { + + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop, widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, barWidth, rotateLabels = 0, msr; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="StackedBar_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"StackedBar"))return; + + config.logarithmic = false; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"StackedBar"); + + setRect(ctx,config); + + msr = setMeasures(data, config, ctx, height, width, [""], true, false, true, true,true); + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + if (!config.scaleOverride) { + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, true, true,true); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + labels: [] + } + for (var i = 0; i < calculatedScale.steps; i++) { + if (labelTemplateString) { + calculatedScale.labels.push(tmpl(labelTemplateString, { value: fmtChartJS(config,1 * ((config.scaleStartValue + (config.scaleStepWidth * (i + 1))).toFixed(getDecimalPlaces(config.scaleStepWidth))),config.fmtYLabel) })); + } + } + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, true, true,true); + } + + msr.availableHeight = msr.availableHeight - config.scaleTickSizeBottom - config.scaleTickSizeTop; + msr.availableWidth = msr.availableWidth - config.scaleTickSizeLeft - config.scaleTickSizeRight; + + scaleHop = Math.floor(msr.availableHeight / calculatedScale.steps); + valueHop = Math.floor(msr.availableWidth / (data.labels.length)); + if(valueHop ==0)valueHop = (msr.availableWidth / (data.labels.length - 1)); + + msr.clrwidth=msr.clrwidth - (msr.availableWidth - ((data.labels.length) * valueHop)); + msr.availableWidth = (data.labels.length) * valueHop; + msr.availableHeight = (calculatedScale.steps) * scaleHop; + + yAxisPosX = msr.leftNotUsableSize + config.scaleTickSizeLeft; + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + config.scaleTickSizeTop; + + barWidth = (valueHop - config.scaleGridLineWidth * 2 - (config.barValueSpacing * 2) - (config.barDatasetSpacing * data.datasets.length - 1) - (config.barStrokeWidth / 2) - 1); + + drawLabels(); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data); + + function drawBars(animPc) { + ctx.lineWidth = config.barStrokeWidth; + var yStart = new Array(data.datasets.length); + var yFpt = new Array(data.datasets.length); + + var cumvalue = new Array(); + var totvalue = new Array(); + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; totvalue[j] = 0; } } + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) if (!(typeof(data.datasets[i].data[j])=='undefined')) { totvalue[j] += 1*data.datasets[i].data[j]; } } + + for (var i = 0; i < data.datasets.length; i++) { +// ctx.fillStyle = data.datasets[i].fillColor; +// ctx.strokeStyle = data.datasets[i].strokeColor; + if (animPc >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + + for (var j = 0; j < data.datasets[i].data.length; j++) { + ctx.fillStyle=config.defaultFillColor; + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].fillColor)=="string"){ctx.fillStyle = data.datasets[i].fillColor;} + else if(typeof(data.datasets[i].fillColor)=="object"){if(typeof(data.datasets[i].fillColor[0])=="string"){ctx.fillStyle = data.datasets[i].fillColor[Min([data.datasets[i].fillColor.length-1,j])];} } + + ctx.strokeStyle=config.defaultStrokeColor; + if (typeof data.datasets[i].strokeColor == "function")ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].strokeColor)=="string"){ctx.strokeStyle = data.datasets[i].strokeColor;} + else if(typeof(data.datasets[i].strokeColor)=="object"){if(typeof(data.datasets[i].strokeColor[0])=="string"){ctx.strokeStyle = data.datasets[i].strokeColor[Min([data.datasets[i].strokeColor.length-1,j])];} } + + if(i==0) {yStart[j]=0;yFpt[j]=-1;} + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + var barOffset = yAxisPosX + config.barValueSpacing + valueHop * j; + ctx.beginPath(); + ctx.moveTo(barOffset, xAxisPosY - yStart[j] + 1); + ctx.lineTo(barOffset, xAxisPosY - animPc * calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2) - yStart[j]); + ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc * calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2) - yStart[j]); + ctx.lineTo(barOffset + barWidth, xAxisPosY - yStart[j] + 1); + if (config.barShowStroke) ctx.stroke(); + ctx.closePath(); + ctx.fill(); + cumvalue[j] += 1*data.datasets[i].data[j]; + if (animPc >= 1) { + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["RECT", barOffset, xAxisPosY - yStart[j] + 1, barOffset + barWidth, xAxisPosY - calculateOffset(config, (yFpt[j]>=0)* calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2) - yStart[j], lgtxt, lgtxt2, 1*data.datasets[i].data[j], cumvalue[j], totvalue[j], i, j]; + } + yStart[j] += animPc * calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) - (config.barStrokeWidth / 2); + if (yFpt[j]==-1)yFpt[j]=i; + } + } + } + + if(animPc >=1 && config.inGraphDataShow) { + + var yPos =0, xPos=0; + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; } } + + for (var i = 0; i < data.datasets.length; i++) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(i==0) {yStart[j]=0;yFpt[j]=-1;} + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + + cumvalue[j] += 1+data.datasets[i].data[j]; + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,lgtxt2,config.fmtV2), v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), v4 : fmtChartJS(config,cumvalue[j],config.fmtV4), v5 : fmtChartJS(config,totvalue[j],config.fmtV5), v6 : roundToWithThousands(config,fmtChartJS(config,100 * data.datasets[i].data[j] / totvalue[j],config.fmtV6),config.roundPct),v7 : fmtChartJS(config,barOffset,config.fmtV7),v8 : fmtChartJS(config,xAxisPosY,config.fmtV8),v9 : fmtChartJS(config,barOffset + barWidth,config.fmtV9),v10 : fmtChartJS(config,xAxisPosY - calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2),config.fmtV10),v11 : fmtChartJS(config,i,config.fmtV11), v12 : fmtChartJS(config,j,config.fmtV12),data:data}); + + var barOffset = yAxisPosX + config.barValueSpacing + valueHop * j; + ctx.beginPath(); + + ctx.beginPath(); + yPos =0; + xPos=0; + + if(config.inGraphDataXPosition==1) { xPos=barOffset+config.inGraphDataPaddingX; } + else if(config.inGraphDataXPosition==2) { xPos=barOffset+barWidth/2+config.inGraphDataPaddingX ;} + else if(config.inGraphDataXPosition==3) { xPos=barOffset+barWidth+config.inGraphDataPaddingX;} + if(config.inGraphDataYPosition==1) { yPos=xAxisPosY - yStart[j] - config.inGraphDataPaddingY; } + else if(config.inGraphDataYPosition==2) { yPos=xAxisPosY -(calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin +1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2) )/2 - yStart[j] - config.inGraphDataPaddingY; } + else if(config.inGraphDataYPosition==3) { yPos=xAxisPosY -calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2) - yStart[j] - config.inGraphDataPaddingY; } + + ctx.translate(xPos,yPos); + + ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + + yStart[j] += animPc * calculateOffset(config, (yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, scaleHop) - (config.barStrokeWidth / 2); + if (yFpt[j]==-1)yFpt[j]=i; + } + } + } + } + } ; + + function drawScale() { + + //X axis line + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY); + ctx.stroke(); + + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i>0 && i % config.scaleXGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + } + else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + + //Y axis + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + ctx.stroke(); + + for (var j = ((config.showYAxisMin) ? -1 : 0) ; j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && j % config.scaleYGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY - ((j + 1) * scaleHop)); + } + else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + } ; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + + //X axis labels + + if(config.xAxisTop || config.xAxisBottom) { + + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } + else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + + if(config.xAxisBottom){ + for (var i = 0; i < data.labels.length; i++) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + i * valueHop + (barWidth / 2)- msr.highestXLabel/2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), 0, 0,ctx.textBaseline,config.scaleFontSize); + } + else { + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), yAxisPosX + i * valueHop + (barWidth / 2), msr.xLabelPos,ctx.textBaseline,config.scaleFontSize); + } + ctx.restore(); + } + } + } + + //Y axis + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = ((config.showYAxisMin) ? -1 : 0) ; j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (config.scaleTickSizeLeft + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (config.scaleTickSizeRight + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + } + } + } ; + + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + var minvl = new Array(data.datasets.length); + var maxvl = new Array(data.datasets.length); + + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + var k = i; + var temp=0; + if (!(typeof(data.datasets[0].data[j])=='undefined')){ + temp += 1*data.datasets[0].data[j]; + if (temp > upperValue) { upperValue = temp; }; + if (temp < lowerValue) { lowerValue = temp; }; + } + while (k > 0) { //get max of stacked data + if (!(typeof(data.datasets[k].data[j])=='undefined')) { + temp += 1*data.datasets[k].data[j]; + if (temp > upperValue) { upperValue = temp; }; + if (temp < lowerValue) { lowerValue = temp; }; + } + k--; + } + } + }; + + + // AJOUT CHANGEMENT + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + labelHeight = config.scaleFontSize; + scaleHeight = msr.availableHeight; + + 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 HorizontalStackedBar = function (data, config, ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop, widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, barWidth, rotateLabels = 0, msr; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="HorizontalStackedBar_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"HorizontalStackedBar"))return; + + config.logarithmic = false; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"HorizontalStackedBar"); + + setRect(ctx,config); + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, true, true, true,true); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + labels: [] + } + + for (var i = 0; i < calculatedScale.steps; i++) { + if (labelTemplateString) { + calculatedScale.labels.push(tmpl(labelTemplateString, { value: fmtChartJS(config,1 * ((config.scaleStartValue + (config.scaleStepWidth * (i + 1))).toFixed(getDecimalPlaces(config.scaleStepWidth))),config.fmtYLabel) })); + } + } + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, true, true, true,true); + } + + msr.availableHeight = msr.availableHeight - config.scaleTickSizeBottom - config.scaleTickSizeTop; + msr.availableWidth = msr.availableWidth - config.scaleTickSizeLeft - config.scaleTickSizeRight; + + scaleHop = Math.floor(msr.availableHeight / data.labels.length); + valueHop = Math.floor(msr.availableWidth / (calculatedScale.steps)); + if(valueHop ==0)valueHop = (msr.availableWidth / (data.labels.length - 1)); + + msr.clrwidth=msr.clrwidth - (msr.availableWidth - (calculatedScale.steps * valueHop)); + msr.availableWidth = (calculatedScale.steps) * valueHop; + msr.availableHeight = (data.labels.length) * scaleHop; + + yAxisPosX = msr.leftNotUsableSize + config.scaleTickSizeLeft; + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + config.scaleTickSizeTop; + + barWidth = (scaleHop - config.scaleGridLineWidth * 2 - (config.barValueSpacing * 2) - (config.barDatasetSpacing * data.datasets.length - 1) - (config.barStrokeWidth / 2) - 1); + + drawLabels(); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data); + + function HorizontalCalculateOffset(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 drawBars(animPc) { + ctx.lineWidth = config.barStrokeWidth; + var yStart = new Array(data.datasets.length); + var yFpt = new Array(data.datasets.length); + + var cumvalue = new Array(); + var totvalue = new Array(); + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; totvalue[j] = 0; } } + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) if (!(typeof(data.datasets[i].data[j])=='undefined')) { totvalue[j] += 1*data.datasets[i].data[j]; } } + + for (var i = 0; i < data.datasets.length; i++) { +// ctx.fillStyle = data.datasets[i].fillColor; +// ctx.strokeStyle = data.datasets[i].strokeColor; + if (animPc >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + for (var j = 0; j < data.datasets[i].data.length; j++) { + ctx.fillStyle=config.defaultFillColor; + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].fillColor)=="string"){ctx.fillStyle = data.datasets[i].fillColor;} + else if(typeof(data.datasets[i].fillColor)=="object"){if(typeof(data.datasets[i].fillColor[0])=="string"){ctx.fillStyle = data.datasets[i].fillColor[Min([data.datasets[i].fillColor.length-1,j])];} } + + ctx.strokeStyle=config.defaultStrokeColor; + if (typeof data.datasets[i].strokeColor == "function")ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].strokeColor)=="string"){ctx.strokeStyle = data.datasets[i].strokeColor;} + else if(typeof(data.datasets[i].strokeColor)=="object"){if(typeof(data.datasets[i].strokeColor[0])=="string"){ctx.strokeStyle = data.datasets[i].strokeColor[Min([data.datasets[i].strokeColor.length-1,j])];} } + + if(i==0) {yStart[j]=0;yFpt[j]=-1;} + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + + var barOffset = xAxisPosY + config.barValueSpacing - scaleHop * (j + 1); + ctx.beginPath(); + ctx.moveTo(yAxisPosX + yStart[j] + 1, barOffset); + ctx.lineTo(yAxisPosX + yStart[j] + animPc * HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2), barOffset); + ctx.lineTo(yAxisPosX + yStart[j] + animPc * HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2), barOffset + barWidth); + ctx.lineTo(yAxisPosX + yStart[j] + 1, barOffset + barWidth); + ctx.lineTo(yAxisPosX + yStart[j] + 1, barOffset); + + if (config.barShowStroke) ctx.stroke(); + ctx.closePath(); + ctx.fill(); + + cumvalue[j] += 1*data.datasets[i].data[j]; + if (animPc >= 1) { + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["RECT", yAxisPosX + yStart[j] + 1, barOffset + barWidth, yAxisPosX + yStart[j] + HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2), barOffset, lgtxt, lgtxt2, 1*data.datasets[i].data[j], cumvalue[j], totvalue[j], i, j]; + } + yStart[j] += animPc * HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2); + if (yFpt[j]==-1)yFpt[j]=i; + } + } + } + if(animPc >=1 && config.inGraphDataShow) { + + var yPos =0, xPos=0; + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; } } + + for (var i = 0; i < data.datasets.length; i++) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + for (var j = 0; j < data.datasets[i].data.length; j++) { + if(i==0) {yStart[j]=0;yFpt[j]=-1;} + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + var barOffset = xAxisPosY + config.barValueSpacing - scaleHop * (j + 1); + cumvalue[j] += data.datasets[i].data[j]; + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,lgtxt2,config.fmtV2), v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), v4 : fmtChartJS(config,cumvalue[j],config.fmtV4), v5 : fmtChartJS(config,totvalue[j],config.fmtV5), v6 : roundToWithThousands(config,fmtChartJS(config,100 * data.datasets[i].data[j] / totvalue[j],config.fmtV6),config.roundPct),v7 : fmtChartJS(config,yAxisPosX,config.fmtV7),v8 : fmtChartJS(config,barOffset + barWidth,config.fmtV8),v9 : fmtChartJS(config,yAxisPosX + HorizontalCalculateOffset(data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2),config.fmtV9),v10 : fmtChartJS(config,barOffset,config.fmtV10),v11 : fmtChartJS(config,i,config.fmtV11), v12 : fmtChartJS(config,j,config.fmtV12),data:data}); + + ctx.beginPath(); + yPos =0; + xPos=0; + + if(config.inGraphDataXPosition==1) { xPos=yAxisPosX + yStart[j] + 1 +config.inGraphDataPaddingX; } + else if(config.inGraphDataXPosition==2) { xPos=yAxisPosX + yStart[j] + (HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin+1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2))/2+config.inGraphDataPaddingX ;} + else if(config.inGraphDataXPosition==3) { xPos=yAxisPosX + yStart[j] + HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin+1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2) +config.inGraphDataPaddingX ;} + if(config.inGraphDataYPosition==1) { yPos=barOffset + barWidth - config.inGraphDataPaddingY; } + else if(config.inGraphDataYPosition==2) { yPos=barOffset + barWidth/2- config.inGraphDataPaddingY; } + else if(config.inGraphDataYPosition==3) { yPos=barOffset- config.inGraphDataPaddingY; } + + ctx.translate(xPos,yPos); + + ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + + + yStart[j] += animPc * HorizontalCalculateOffset((yFpt[j]>=0)*calculatedScale.graphMin + 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2); + if (yFpt[j]==-1)yFpt[j]=i; + } + } + } + } + + } ; + + function drawScale() { + + //X axis line + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth, xAxisPosY); + ctx.stroke(); + + for (var i = ((config.showYAxisMin) ? -1 : 0) ; i < calculatedScale.steps; i++) { + if (i >= 0) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i>0 && i % config.scaleXGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + } + else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + } + + //Y axis + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + ctx.stroke(); + + for (var j = 0; j < data.labels.length; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && j % config.scaleYGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + msr.availableWidth, xAxisPosY - ((j + 1) * scaleHop)); + } + else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + } ; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + + //X axis line + + if(config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } + else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + + if(config.xAxisBottom){ + for (var i = ((config.showYAxisMin) ? -1 : 0) ; i < calculatedScale.steps; i++) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + (i + 1) * valueHop- msr.highestXLabel/2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], 0, 0,ctx.textBaseline,config.scaleFontSize); + } + else { + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], yAxisPosX + ((i + 1) * valueHop), msr.xLabelPos,ctx.textBaseline,config.scaleFontSize); + } + ctx.restore(); + } + } + } + + //Y axis + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = 0; j < data.labels.length; j++) { + if (config.scaleShowLabels) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[j],config.fmtXLabel), yAxisPosX - (config.scaleTickSizeLeft + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop) + barWidth / 2,ctx.textBaseline,config.scaleFontSize); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[j],config.fmtXLabel), yAxisPosX + msr.availableWidth + (config.scaleTickSizeRight + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop) + barWidth / 2,ctx.textBaseline,config.scaleFontSize); + } + } + } + } ; + + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + var minvl = new Array(data.datasets.length); + var maxvl = new Array(data.datasets.length); + + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + var k = i; + var temp=0; + if (!(typeof(data.datasets[0].data[j])=='undefined')){ + temp += 1*data.datasets[0].data[j]; + if (temp > upperValue) { upperValue = temp; }; + if (temp < lowerValue) { lowerValue = temp; }; + } + while (k > 0) { //get max of stacked data + if (!(typeof(data.datasets[k].data[j])=='undefined')) { + temp += 1*data.datasets[k].data[j]; + if (temp > upperValue) { upperValue = temp; }; + if (temp < lowerValue) { lowerValue = temp; }; + } + k--; + } + } + }; + + + // AJOUT CHANGEMENT + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + + 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, msr; + var annotateCnt = 0; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="Bar_"+cvmillsec; + } + + // for BarLineCharts + var nrOfBars = data.datasets.length; + + var nrOfLines = 0; + var lineDatasets = []; + var barDatasets = []; + for (var i = 0; i < data.datasets.length; i++) { + if (data.datasets[i].type == "Line") { + nrOfLines++; + lineDatasets.push(i); + } else { + barDatasets.push(i); + } + } + + // change the order (at first all bars then the lines) (form of BubbleSort) + var bufferDataset,l = 0; + for (var i = data.datasets.length-1; i >= 0; i--) { + if (lineDatasets.indexOf(i) >= 0) { + l++; + for (var b = i; b < data.datasets.length-l; b++) { + bufferDataset = data.datasets[b+1]; + data.datasets[b+1] = data.datasets[b]; + data.datasets[b] = bufferDataset; + } + + } + } + + + nrOfBars -= nrOfLines; + + + + if (!dynamicFunction(data,config,ctx,"Bar"))return; + + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"Bar"); + + setRect(ctx,config); + + + msr = setMeasures(data, config, ctx, height, width, [""], true, false, true, true,true); + valueBounds = getValueBounds(); + + + // true or fuzzy (error for negativ values (included 0)) + if (config.logarithmic !== false) { + if (valueBounds.minValue <= 0) { + config.logarithmic = false; + } + } + + // Check if logarithmic is meanigful + var OrderOfMagnitude = calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.maxValue) + 1)) - calculateOrderOfMagnitude(Math.pow(10, calculateOrderOfMagnitude(valueBounds.minValue))); + if ((config.logarithmic == 'fuzzy' && OrderOfMagnitude < 4) || config.scaleOverride) { + config.logarithmic = false; + } + + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, true, true,true); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + graphMax: config.scaleStartValue+config.scaleSteps*config.scaleStepWidth, + labels: [] + } + populateLabels(config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, calculatedScale.graphMax, config.scaleStepWidth); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, false, true, true,true); + } + + msr.availableHeight = msr.availableHeight - config.scaleTickSizeBottom - config.scaleTickSizeTop; + msr.availableWidth = msr.availableWidth - config.scaleTickSizeLeft - config.scaleTickSizeRight; + var inGraphDataHeight = 0; + if (config.inGraphDataShow) { + // values are at the top of the bars and must be visible padding-top:2px + inGraphDataHeight = (config.inGraphDataTmpl.split("\n").length)*config.inGraphDataFontSize+2; + msr.availableHeight -= inGraphDataHeight; + } + + scaleHop = Math.floor(msr.availableHeight / calculatedScale.steps); + valueHop = Math.floor(msr.availableWidth / (data.labels.length)); + if(valueHop ==0)valueHop = (msr.availableWidth / (data.labels.length - 1)); + + msr.clrwidth=msr.clrwidth - (msr.availableWidth - ((data.labels.length) * valueHop)); + msr.availableWidth = (data.labels.length) * valueHop; + msr.availableHeight = (calculatedScale.steps) * scaleHop; + + + yAxisPosX = msr.leftNotUsableSize + config.scaleTickSizeLeft; + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + inGraphDataHeight + config.scaleTickSizeTop; + + + barWidth = (valueHop - config.scaleGridLineWidth * 2 - (config.barValueSpacing * 2) - (config.barDatasetSpacing * nrOfBars - 1) - ((config.barStrokeWidth / 2) * nrOfBars - 1)) / nrOfBars; + + var zeroY = 0; + if (valueBounds.minValue < 0) { + var zeroY = calculateOffset(config, 0, calculatedScale, scaleHop); + } + + drawLabels(); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data); + + function drawBars(animPc) { + var t1, t2, t3; + + var cumvalue = new Array(); + var totvalue = new Array(); + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + cumvalue[j] = 0; + totvalue[j] = 0; + } + } + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + totvalue[j] += 1*data.datasets[i].data[j]; + } + } + } + + ctx.lineWidth = config.barStrokeWidth; + for (var i = 0; i < data.datasets.length; i++) { + if (data.datasets[i].type == "Line") { + var lineData = {datasets:[],labels:data.labels}; + lineData.datasets.push(data.datasets[i]); + lineConfig = mergeChartConfig(config, {datasetFill: data.datasets[i].fill}) + drawLinesDataset(animPc,lineData,lineConfig,ctx, + {xAxisPosY:xAxisPosY, + yAxisPosX:yAxisPosX + config.barValueSpacing+ + (barWidth + config.barDatasetSpacing/2 + config.barStrokeWidth)*nrOfBars/2, + valueHop:valueHop,scaleHop:scaleHop, + zeroY:zeroY,calculatedScale:calculatedScale,annotateCnt:annotateCnt}); + continue; // next dataset + } + + if (animPc >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + + for (var j = 0; j < data.datasets[i].data.length; j++) { + + ctx.fillStyle=config.defaultFillColor; + if (typeof data.datasets[i].fillColor == "function") { + ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + } + else if(typeof(data.datasets[i].fillColor)=="string") { + ctx.fillStyle = data.datasets[i].fillColor; + } + else if(typeof(data.datasets[i].fillColor)=="object") { + if(typeof(data.datasets[i].fillColor[0])=="string") { + ctx.fillStyle = data.datasets[i].fillColor[Min([data.datasets[i].fillColor.length-1,j])]; + } + } + + ctx.strokeStyle=config.defaultStrokeColor; + if (typeof data.datasets[i].strokeColor == "function") { + ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + } + else if(typeof(data.datasets[i].strokeColor)=="string"){ + ctx.strokeStyle = data.datasets[i].strokeColor; + } + else if(typeof(data.datasets[i].strokeColor)=="object"){ + if(typeof(data.datasets[i].strokeColor[0])=="string"){ + ctx.strokeStyle = data.datasets[i].strokeColor[Min([data.datasets[i].strokeColor.length-1,j])]; + } + } + + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + var barOffset = yAxisPosX + config.barValueSpacing + valueHop * j + barWidth * i + config.barDatasetSpacing * i + config.barStrokeWidth * i; + + var barHeight = animPc*(calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, scaleHop)-zeroY) + (config.barStrokeWidth / 2); + roundRect( ctx, barOffset, xAxisPosY-zeroY, barWidth, barHeight, config.barShowStroke, config.barBorderRadius); + + cumvalue[j] += 1*data.datasets[i].data[j]; + if (animPc >= 1) { + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + t1 = xAxisPosY - zeroY; + t2 = xAxisPosY - calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2); + if (t1 < t2) { t3 = t1; t1 = t2; t2 = t3 } + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["RECT", barOffset, t1, barOffset + barWidth, t2, lgtxt, lgtxt2, + 1*data.datasets[i].data[j], cumvalue[j], totvalue[j], i, j]; + } + } + } + } + + if(animPc >=1 && config.inGraphDataShow) { + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; } } + + for (var i = 0; i < data.datasets.length; i++) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (data.datasets[i].type == "Line") { // no inGraphDataShow for lines again (is inside drawLinesDataset) + continue; + } + + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var barOffset = yAxisPosX + config.barValueSpacing + valueHop * j + barWidth * i + config.barDatasetSpacing * i + config.barStrokeWidth * i; + t1 = xAxisPosY - zeroY; + t2 = xAxisPosY - calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2); + + ctx.beginPath(); + var yPos =0, xPos=0; + + if(config.inGraphDataXPosition==1) { xPos=barOffset+config.inGraphDataPaddingX; } + else if(config.inGraphDataXPosition==2) { xPos=barOffset+barWidth/2+config.inGraphDataPaddingX ;} + else if(config.inGraphDataXPosition==3) { xPos=barOffset+barWidth+config.inGraphDataPaddingX;} + if(config.inGraphDataYPosition==1) { yPos=xAxisPosY - zeroY- config.inGraphDataPaddingY; } + else if(config.inGraphDataYPosition==2) { + yPos=xAxisPosY -(calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, scaleHop) + + (config.barStrokeWidth / 2))/2- config.inGraphDataPaddingY; + } + else if(config.inGraphDataYPosition==3) { + yPos=xAxisPosY -calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, scaleHop) + + (config.barStrokeWidth / 2)- config.inGraphDataPaddingY; + } + + ctx.translate(xPos,yPos); + + cumvalue[j] += 1*data.datasets[i].data[j]; + + var dispString = tmplbis(config.inGraphDataTmpl, + { config:config, + v1 : fmtChartJS(config,lgtxt,config.fmtV1), + v2 : fmtChartJS(config,lgtxt2,config.fmtV2), + v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), + v4 : fmtChartJS(config,cumvalue[j],config.fmtV4), + v5 : fmtChartJS(config,totvalue[j],config.fmtV5), + v6 : roundToWithThousands(config,fmtChartJS(config, + 100 * data.datasets[i].data[j] / totvalue[j],config.fmtV6),config.roundPct), + v7 : fmtChartJS(config,barOffset,config.fmtV7), + v8 : fmtChartJS(config,t1,config.fmtV8), + v9 : fmtChartJS(config,barOffset + barWidth,config.fmtV9), + v10 : fmtChartJS(config,t2,config.fmtV10), + v11 : fmtChartJS(config,i,config.fmtV11), + v12 : fmtChartJS(config,j,config.fmtV12), data: data}); + ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + + } + } + } + + } + + if (animPc >= 1) { + if (typeof drawMath == "function") { + drawMath(ctx,config,data,msr,{xAxisPosY:xAxisPosY,yAxisPosX:yAxisPosX,valueHop:valueHop,scaleHop:scaleHop, + zeroY:zeroY,calculatedScale:calculatedScale,calculateOffset:calculateOffset,barWidth:barWidth}); + } + } + + } ; + + function roundRect(ctx, x, y, w, h, stroke, radius ) { + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + w - radius, y); + ctx.quadraticCurveTo(x + w, y, x + w, y); + ctx.lineTo(x + w, y - h + radius); + ctx.quadraticCurveTo(x + w, y - h, x + w - radius, y - h); + ctx.lineTo(x + radius, y - h); + ctx.quadraticCurveTo(x, y - h, x, y - h + radius); + ctx.lineTo(x, y ); + ctx.quadraticCurveTo(x, y, x + radius, y); + if(stroke)ctx.stroke(); + ctx.closePath(); + ctx.fill(); + } ; + + function drawScale() { + + //X axis line + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY); + ctx.stroke(); + + for (var i = 0; i < data.labels.length; i++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i>0 && i % config.scaleXGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + } + else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + + //Y axis + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + ctx.stroke(); + + for (var j = 0 ; j < calculatedScale.steps; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && j % config.scaleYGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY - ((j + 1) * scaleHop)); + } + else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + } ; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + + //X axis line + if(config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } + else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + + if(config.xAxisBottom){ + for (var i = 0; i < data.labels.length; i++) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + i * valueHop + (valueHop / 2)- msr.highestXLabel/2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), 0, 0,ctx.textBaseline,config.scaleFontSize); + } + else { + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel), yAxisPosX + i * valueHop + (valueHop / 2), msr.xLabelPos,ctx.textBaseline,config.scaleFontSize); + } + ctx.restore(); + } + } + } + + //Y axis + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = ((config.showYAxisMin) ? -1 : 0) ; j < calculatedScale.steps; j++) { + if (config.scaleShowLabels) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX - (config.scaleTickSizeLeft + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(calculatedScale.labels[j + 1], yAxisPosX + msr.availableWidth + (config.scaleTickSizeRight + config.yAxisSpaceRight), xAxisPosY - ((j + 1) * scaleHop),ctx.textBaseline,config.scaleFontSize); + } + } + } + } ; + + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.datasets.length; i++) { + var mathFctName = data.datasets[i].drawMathDeviation; + var mathValueHeight = 0; + if (typeof eval(mathFctName) == "function") { + var parameter = {data:data,datasetNr: i}; + mathValueHeight = window[mathFctName](parameter); + } + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (1*data.datasets[i].data[j]+mathValueHeight > upperValue) { upperValue = 1*data.datasets[i].data[j]+mathValueHeight }; + if (1*data.datasets[i].data[j]-mathValueHeight < lowerValue) { lowerValue = 1*data.datasets[i].data[j]-mathValueHeight }; + + } + }; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + // AJOUT CHANGEMENT + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + labelHeight = config.scaleFontSize; + scaleHeight = msr.availableHeight; + + 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 HorizontalBar = function (data, config, ctx) { + + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop, widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, barWidth, rotateLabels = 0, msr; + + if(typeof ctx.ChartNewId == "undefined"){ + var cvdate = new Date(); + var cvmillsec = cvdate.getTime(); + ctx.ChartNewId="HorizontalBar_"+cvmillsec; + } + + if (!dynamicFunction(data,config,ctx,"HorizontalBar"))return; + + var annotateCnt = 0; + jsGraphAnnotate[ctx.ChartNewId] = new Array(); + + defMouse(ctx,data,config,"HorizontalBar"); + + setRect(ctx,config); + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : ""; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(config, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, true, true, true,true); + } + else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + graphMax: config.scaleStartValue+config.scaleSteps*config.scaleStepWidth, + labels: [] + } + populateLabels(config, labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, calculatedScale.graphMax, config.scaleStepWidth); + msr = setMeasures(data, config, ctx, height, width, calculatedScale.labels, true, true, true, true,true); + } + + msr.availableHeight = msr.availableHeight - config.scaleTickSizeBottom - config.scaleTickSizeTop; + msr.availableWidth = msr.availableWidth - config.scaleTickSizeLeft - config.scaleTickSizeRight; + + scaleHop = Math.floor(msr.availableHeight / data.labels.length); + valueHop = Math.floor(msr.availableWidth / (calculatedScale.steps)); + if(valueHop ==0)valueHop = (msr.availableWidth / (data.labels.length - 1)); + + msr.clrwidth=msr.clrwidth - (msr.availableWidth - (calculatedScale.steps * valueHop)); + msr.availableWidth = (calculatedScale.steps) * valueHop; + msr.availableHeight = (data.labels.length) * scaleHop; + + yAxisPosX = msr.leftNotUsableSize + config.scaleTickSizeLeft; + xAxisPosY = msr.topNotUsableSize + msr.availableHeight + config.scaleTickSizeTop; + + barWidth = (scaleHop - config.scaleGridLineWidth * 2 - (config.barValueSpacing * 2) - (config.barDatasetSpacing * data.datasets.length - 1) - ((config.barStrokeWidth / 2) * data.datasets.length - 1)) / data.datasets.length; + + var zeroY = 0; + if (valueBounds.minValue < 0) { + var zeroY = calculateOffset(config, 0, calculatedScale, valueHop); + } + + drawLabels(); + animationLoop(config, drawScale, drawBars, ctx, msr.clrx, msr.clry, msr.clrwidth, msr.clrheight, yAxisPosX + msr.availableWidth / 2, xAxisPosY - msr.availableHeight / 2, yAxisPosX, xAxisPosY, data); + + function drawBars(animPc) { + var cumvalue = new Array(); + var totvalue = new Array(); + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; totvalue[j] = 0; } } + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) if (!(typeof(data.datasets[i].data[j])=='undefined'))totvalue[j] += 1*data.datasets[i].data[j]; } + + ctx.lineWidth = config.barStrokeWidth; + for (var i = 0; i < data.datasets.length; i++) { +// ctx.fillStyle = data.datasets[i].fillColor; +// ctx.strokeStyle = data.datasets[i].strokeColor; + if (animPc >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + for (var j = 0; j < data.datasets[i].data.length; j++) { + ctx.fillStyle=config.defaultFillColor; + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].fillColor)=="string"){ctx.fillStyle = data.datasets[i].fillColor;} + else if(typeof(data.datasets[i].fillColor)=="object"){if(typeof(data.datasets[i].fillColor[0])=="string"){ctx.fillStyle = data.datasets[i].fillColor[Min([data.datasets[i].fillColor.length-1,j])];} } + + ctx.strokeStyle=config.defaultStrokeColor; + if (typeof data.datasets[i].strokeColor == "function")ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,j,animPc,1*data.datasets[i].data[j]); + else if(typeof(data.datasets[i].strokeColor)=="string"){ctx.strokeStyle = data.datasets[i].strokeColor;} + else if(typeof(data.datasets[i].strokeColor)=="object"){if(typeof(data.datasets[i].strokeColor[0])=="string"){ctx.strokeStyle = data.datasets[i].strokeColor[Min([data.datasets[i].strokeColor.length-1,j])];} } + + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + var barOffset = xAxisPosY + config.barValueSpacing - scaleHop * (j + 1) + barWidth * i + config.barDatasetSpacing * i + config.barStrokeWidth * i; + + var barHeight = animPc * calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2); + roundRect( ctx, barOffset, yAxisPosX, barWidth, barHeight, config.barShowStroke, config.barBorderRadius,zeroY ); + + + cumvalue[j] += 1*data.datasets[i].data[j]; + if (animPc >= 1) { + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + t1 = yAxisPosX + zeroY; + t2 = yAxisPosX + calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2) + if (t1 > t2) { t3 = t1; t1 = t2; t2 = t3 } + + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["RECT", t1, barOffset + barWidth, t2, barOffset, lgtxt, lgtxt2, 1*data.datasets[i].data[j], cumvalue[j], totvalue[j], i, j]; + } + } + } + } + + if(animPc >=1 && config.inGraphDataShow) { + for (var i = 0; i < data.datasets.length; i++) { for (var j = 0; j < data.datasets[i].data.length; j++) { cumvalue[j] = 0; } } + + for (var i = 0; i < data.datasets.length; i++) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + + var barOffset = xAxisPosY + config.barValueSpacing - scaleHop * (j + 1) + barWidth * i + config.barDatasetSpacing * i + config.barStrokeWidth * i; + t1 = yAxisPosX + zeroY; + t2 = yAxisPosX + calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2) + if (t1 > t2) { t3 = t1; t1 = t2; t2 = t3 } + + ctx.beginPath(); + var yPos =0, xPos=0; + + if(config.inGraphDataYPosition==1) { yPos=barOffset-config.inGraphDataPaddingY+barWidth; } + else if(config.inGraphDataYPosition==2) { yPos=barOffset+barWidth/2-config.inGraphDataPaddingY ;} + else if(config.inGraphDataYPosition==3) { yPos=barOffset-config.inGraphDataPaddingY;} + + if(config.inGraphDataXPosition==1) { xPos=yAxisPosX + zeroY +config.inGraphDataPaddingX; } + else if(config.inGraphDataXPosition==2) { xPos=yAxisPosX + (calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2))/2 + config.inGraphDataPaddingX; } + else if(config.inGraphDataXPosition==3) { xPos=yAxisPosX + calculateOffset(config, 1*data.datasets[i].data[j], calculatedScale, valueHop) + (config.barStrokeWidth / 2) + config.inGraphDataPaddingX; } + + ctx.translate(xPos,yPos); + + cumvalue[j] += 1*data.datasets[i].data[j]; + + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,lgtxt2,config.fmtV2), v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), v4 : fmtChartJS(config,cumvalue[j],config.fmtV4), v5 : fmtChartJS(config,totvalue[j],config.fmtV5), v6 : roundToWithThousands(config,fmtChartJS(config,100 * data.datasets[i].data[j] / totvalue[j],config.fmtV6),config.roundPct),v7 : fmtChartJS(config,t1,config.fmtV7),v8 : fmtChartJS(config,barOffset + barWidth,config.fmtV8),v9 : fmtChartJS(config,t2,config.fmtV9),v10 : fmtChartJS(config,barOffset,config.fmtV10),v11 : fmtChartJS(config,i,config.fmtV11), v12 : fmtChartJS(config,j,config.fmtV12),data:data}); + ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + + } + } + } + + } + + + } ; + + function roundRect(ctx, x, y, w, h, stroke, radius,zeroY ) { + ctx.beginPath(); + ctx.moveTo(y +zeroY, x + radius ); + ctx.lineTo(y +zeroY, x + w - radius ); + ctx.quadraticCurveTo(y + zeroY, x + w, y + zeroY, x + w); + ctx.lineTo(y + h - radius, x + w ); + ctx.quadraticCurveTo(y + h, x + w, y + h, x + w - radius); + ctx.lineTo(y + h , x + radius); + ctx.quadraticCurveTo(y + h, x , y + h - radius , x ); + ctx.lineTo(y+zeroY, x ); + ctx.quadraticCurveTo(y+zeroY, x , y+zeroY, x+radius); + + if(stroke)ctx.stroke(); + ctx.closePath(); + ctx.fill(); + } ; + + + function drawScale() { + + //X axis line + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY); + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY); + ctx.stroke(); + + for (var i = ((config.showYAxisMin) ? -1 : 0) ; i < calculatedScale.steps; i++) { + if (i >= 0) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + + //Check i isnt 0, so we dont go over the Y axis twice. + if (config.scaleShowGridLines && i>0 && i % config.scaleXGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + } + else { + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY); + } + ctx.stroke(); + } + } + + //Y axis + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX, xAxisPosY + config.scaleTickSizeBottom); + ctx.lineTo(yAxisPosX, xAxisPosY - msr.availableHeight - config.scaleTickSizeTop); + ctx.stroke(); + + for (var j = 0; j < data.labels.length; j++) { + ctx.beginPath(); + ctx.moveTo(yAxisPosX - config.scaleTickSizeLeft, xAxisPosY - ((j + 1) * scaleHop)); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + if (config.scaleShowGridLines && j % config.scaleYGridLinesStep==0 ) { + ctx.lineTo(yAxisPosX + msr.availableWidth + config.scaleTickSizeRight, xAxisPosY - ((j + 1) * scaleHop)); + } + else { + ctx.lineTo(yAxisPosX, xAxisPosY - ((j + 1) * scaleHop)); + } + ctx.stroke(); + } + } ; + + function drawLabels() { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + + //X axis line + if(config.xAxisTop || config.xAxisBottom) { + ctx.textBaseline = "top"; + if (msr.rotateLabels > 90) { + ctx.save(); + ctx.textAlign = "left"; + } + else if (msr.rotateLabels > 0) { + ctx.save(); + ctx.textAlign = "right"; + } + else { + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + + if(config.xAxisBottom){ + for (var i = ((config.showYAxisMin) ? -1 : 0) ; i < calculatedScale.steps; i++) { + ctx.save(); + if (msr.rotateLabels > 0) { + ctx.translate(yAxisPosX + (i + 1) * valueHop - msr.highestXLabel/2, msr.xLabelPos); + ctx.rotate(-(msr.rotateLabels * (Math.PI / 180))); + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], 0, 0,ctx.textBaseline,config.scaleFontSize); + } + else { + ctx.fillTextMultiLine(calculatedScale.labels[i + 1], yAxisPosX + (i + 1) * valueHop, msr.xLabelPos,ctx.textBaseline,config.scaleFontSize); + } + ctx.restore(); + } + } + } + + //Y axis + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j = 0; j < data.labels.length; j++) { + if (config.scaleShowLabels) { + if (config.yAxisLeft) { + ctx.textAlign = "right"; + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[j],config.fmtXLabel), yAxisPosX - (config.scaleTickSizeLeft + config.yAxisSpaceRight), xAxisPosY - (j * scaleHop) - scaleHop / 2,ctx.textBaseline,config.scaleFontSize); + } + if (config.yAxisRight) { + ctx.textAlign = "left"; + ctx.fillTextMultiLine(fmtChartJS(config,data.labels[j],config.fmtXLabel), yAxisPosX + msr.availableWidth + (config.scaleTickSizeRight + config.yAxisSpaceRight), xAxisPosY - (j * scaleHop) - scaleHop / 2,ctx.textBaseline,config.scaleFontSize); + } + } + } + } ; + + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (1*data.datasets[i].data[j] > upperValue) { upperValue = 1*data.datasets[i].data[j] }; + if (1*data.datasets[i].data[j] < lowerValue) { lowerValue = 1*data.datasets[i].data[j] }; + } + }; + + if (Math.abs(upperValue - lowerValue)<0.00000001) { + upperValue = Max([upperValue*2,1]); + lowerValue = 0; + } + + // AJOUT CHANGEMENT + if (!isNaN(config.graphMin)) lowerValue = config.graphMin; + if (!isNaN(config.graphMax)) upperValue = config.graphMax; + + 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(config, val, calculatedScale, scaleHop) { + if (!config.logarithmic) { // no logarithmic scale + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue / outerValue, 1, 0); + return (scaleHop * calculatedScale.steps) * scalingFactor; + } else { // logarithmic scale + return CapValue(log10(val) * scaleHop - calculateOrderOfMagnitude(calculatedScale.graphMin) * scaleHop, undefined, 0); + } + } ; + + function animationLoop(config, drawScale, drawData, ctx, clrx, clry, clrwidth, clrheight, midPosX, midPosY, borderX, borderY, data) { + + var cntiter=0; + var animationCount=1; + var multAnim=1; + + if(config.animationStartValue <0 || config.animationStartValue>1)config.animation.StartValue=0; + if(config.animationStopValue <0 || config.animationStopValue>1)config.animation.StopValue=1; + if(config.animationStopValue0 && config.animationStartValue <=1) + { + while(percentAnimComplete < config.animationStartValue){cntiter++;percentAnimComplete+=animFrameAmount;} + } + var beginAnim=cntiter; + var beginAnimPct=percentAnimComplete; + + if (typeof drawScale !== "function") drawScale = function () { }; + + if(config.clearRect)requestAnimFrame(animLoop); + else animLoop(); + + + function animateFrame() { + var easeAdjustedAnimationPercent = (config.animation) ? CapValue(easingFunction(percentAnimComplete), null, 0) : 1; + + if(1*cntiter>=1*CapValue(config.animationSteps, Number.MAX_VALUE, 1) || config.animation==false)easeAdjustedAnimationPercent=1; + else if(easeAdjustedAnimationPercent>=1)easeAdjustedAnimationPercent=0.9999; + + if (!(isIE() < 9 && isIE() != false) && config.clearRect) ctx.clearRect(clrx, clry, clrwidth, clrheight); + + dispCrossText(ctx, config, midPosX, midPosY, borderX, borderY, false, data, easeAdjustedAnimationPercent,cntiter); + + if (config.scaleOverlay) { + drawData(easeAdjustedAnimationPercent); + drawScale(); + } else { + drawScale(); + drawData(easeAdjustedAnimationPercent); + } + dispCrossText(ctx, config, midPosX, midPosY, borderX, borderY, true, data, easeAdjustedAnimationPercent,cntiter); + }; + function animLoop() { + //We need to check if the animation is incomplete (less than 1), or complete (1). + cntiter+=multAnim; + + percentAnimComplete += multAnim*animFrameAmount; + + if(cntiter==config.animationSteps || config.animation==false )percentAnimComplete=1; + else if(percentAnimComplete>=1)percentAnimComplete=0.999; + + animateFrame(); + //Stop the loop continuing forever + + if(multAnim==-1 && cntiter<=beginAnim) + { + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(ctx,config,data,0,animationCount+1); + multAnim=1; + requestAnimFrame(animLoop); + } + else if (percentAnimComplete < config.animationStopValue) { + requestAnimFrame(animLoop); + } + else { + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(ctx,config,data,1,animationCount+1); + // stop animation ? + if(animationCount maxSteps) { + if (numberOfSteps < minSteps) { + stepValue /= 2; + numberOfSteps = Math.round(graphRange / stepValue); + } + else { + stepValue *= 2; + numberOfSteps = Math.round(graphRange / stepValue); + } + } + } else { // logarithmic scale + numberOfSteps = rangeOrderOfMagnitude; // so scale is 10,100,1000,... + } + + var labels = []; + populateLabels(config, labelTemplateString, labels, numberOfSteps, graphMin, graphMax, stepValue); + + + return { + steps: numberOfSteps, + stepValue: stepValue, + graphMin: graphMin, + labels: labels, + maxValue: maxValue + } + } ; + + function calculateOrderOfMagnitude(val) { + return Math.floor(Math.log(val) / Math.LN10); + } ; + + //Populate an array of all the labels by interpolating the string. + function populateLabels(config, labelTemplateString, labels, numberOfSteps, graphMin, graphMax, stepValue) { + if (labelTemplateString) { + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + if (!config.logarithmic) { // no logarithmic scale + for (var i = 0; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, { value: fmtChartJS(config,1*((graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))),config.fmtYLabel) })); + } + } else { // logarithmic scale 10,100,1000,... + var value = graphMin; + while (value < graphMax) { + labels.push(tmpl(labelTemplateString, { value: fmtChartJS(config,1*value.toFixed(getDecimalPlaces(stepValue)),config.fmtYLabel) })); + value *= 10; + } + } + } + } ; + + //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; + } + }; + + //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 dispCrossText(ctx, config, posX, posY, borderX, borderY, overlay, data, animPC,cntiter) { + + var i, disptxt, txtposx, txtposy, txtAlign, txtBaseline; + + for (i = 0; i < config.crossText.length; i++) { + + if (config.crossText[i] != "" && config.crossTextOverlay[Min([i, config.crossTextOverlay.length - 1])] == overlay && ((cntiter==1 && config.crossTextIter[Min([i, config.crossTextIter.length - 1])]=="first") || config.crossTextIter[Min([i, config.crossTextIter.length - 1])]==cntiter || config.crossTextIter[Min([i, config.crossTextIter.length - 1])]=="all" || (animPC==1 && config.crossTextIter[Min([i, config.crossTextIter.length - 1])]=="last")) ) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.crossTextFontStyle[Min([i, config.crossTextFontStyle.length - 1])] + " " + config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])] + "px " + config.crossTextFontFamily[Min([i, config.crossTextFontFamily.length - 1])]; + ctx.fillStyle = config.crossTextFontColor[Min([i, config.crossTextFontColor.length - 1])]; + + textAlign = config.crossTextAlign[Min([i, config.crossTextAlign.length - 1])]; + textBaseline = config.crossTextBaseline[Min([i, config.crossTextBaseline.length - 1])]; + + txtposx = 1 * config.crossTextPosX[Min([i, config.crossTextPosX.length - 1])]; + txtposy = 1 * config.crossTextPosY[Min([i, config.crossTextPosY.length - 1])]; + + switch (1 * config.crossTextRelativePosX[Min([i, config.crossTextRelativePosX.length - 1])]) { + case 0: + if (textAlign == "default") textAlign = "left"; + break; + case 1: + txtposx += borderX; + if (textAlign == "default") textAlign = "right"; + break; + case 2: + txtposx += posX; + if (textAlign == "default") textAlign = "center"; + break; + case -2: + txtposx += context.canvas.width / 2; + if (textAlign == "default") textAlign = "center"; + break; + case 3: + txtposx += txtposx + 2 * posX - borderX; + if (textAlign == "default") textAlign = "left"; + break; + case 4: + // posX=width; + txtposx += context.canvas.width; + if (textAlign == "default") textAlign = "right"; + break; + default: + txtposx += posX; + if (textAlign == "default") textAlign = "center"; + break; + } + + switch (1 * config.crossTextRelativePosY[Min([i, config.crossTextRelativePosY.length - 1])]) { + case 0: + if (textBaseline == "default") textBaseline = "top"; + break; + case 3: + txtposy += borderY; + if (textBaseline == "default") textBaseline = "top"; + break; + case 2: + txtposy += posY; + if (textBaseline == "default") textBaseline = "middle"; + break; + case -2: + txtposy += context.canvas.height / 2; + if (textBaseline == "default") textBaseline = "middle"; + break; + case 1: + txtposy += txtposy + 2 * posY - borderY; + if (textBaseline == "default") textBaseline = "bottom"; + break; + case 4: + txtposy += context.canvas.height; + if (textBaseline == "default") textBaseline = "bottom"; + break; + default: + txtposy += posY; + if (textBaseline == "default") textBaseline = "middle"; + break; + } + + ctx.textAlign = textAlign; + ctx.textBaseline = textBaseline; + + ctx.translate(1 * txtposx, 1 * txtposy); + + ctx.rotate(config.crossTextAngle[Min([i, config.crossTextAngle.length - 1])]); + + if (config.crossText[i].substring(0, 1) == "%") { + if (typeof config.crossTextFunction == "function") disptxt = config.crossTextFunction(i, config.crossText[i], ctx, config, posX, posY, borderX, borderY, overlay, data, animPC); + } + else disptxt = config.crossText[i]; + + ctx.fillTextMultiLine(disptxt,0,0,ctx.textBaseline,config.crossTextFontSize[Min([i, config.crossTextFontSize.length - 1])]); + ctx.stroke(); + ctx.restore(); + } + } + }; + + //**************************************************************************************** + function setMeasures(data, config, ctx, height, width, ylabels, reverseLegend, reverseAxis, drawAxis, drawLegendOnData,legendBox) { + + if(config.canvasBackgroundColor != "none") ctx.canvas.style.background =config.canvasBackgroundColor; + + var borderWidth = 0; + + var yAxisLabelWidth = 0; + var yAxisLabelPos = 0; + + var graphTitleHeight = 0; + var graphTitlePosY = 0; + + var graphSubTitleHeight = 0; + var graphSubTitlePosY = 0; + + var footNoteHeight = 0; + var footNotePosY = 0; + + var yAxisUnitHeight = 0; + var yAxisUnitPosY = 0; + + var widestLegend = 0; + var nbeltLegend = 0; + var nbLegendLines = 0; + var nbLegendCols = 0; + var spaceLegendHeight = 0; + var xFirstLegendTextPos = 0; + var yFirstLegendTextPos = 0; + var xLegendBorderPos = 0; + var yLegendBorderPos = 0; + + var yAxisLabelWidth = 0; + var yAxisLabelPos = 0; + + var xAxisLabelHeight = 0; + var xLabelHeight = 0; + + var widestXLabel = 1; + var highestXLabel = 1; + + var widestYLabel = 0; + var highestYLabel = 1; + + var leftNotUsableSize = 0; + var rightNotUsableSize = 0; + + var rotateLabels = 0; + var xLabelPos = 0; + + // Borders + + if (config.canvasBorders) borderWidth = config.canvasBordersWidth; + + // compute widest X label + + if (drawAxis) { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + for (var i = 0; i < data.labels.length; i++) { + var textMsr = ctx.measureTextMultiLine(fmtChartJS(config,data.labels[i],config.fmtXLabel),config.scaleFontSize); + //If the text length is longer - make that equal to longest text! + widestXLabel = (textMsr.textWidth > widestXLabel) ? textMsr.textWidth : widestXLabel; + highestXLabel= (textMsr.textHeight > highestXLabel) ? textMsr.textHeight : highestXLabel; + } + } + + // compute Y Label Width + + + if (drawAxis) { + widestYLabel = 1; + if (ylabels != null) { + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily; + for (var i = ylabels.length - 1; i >= 0; i--) { + if (typeof (ylabels[i]) == "string") { + if (ylabels[i].trim() != "") { + var textMsr = ctx.measureTextMultiLine(fmtChartJS(config,ylabels[i],config.fmtYLabel),config.scaleFontSize); + //If the text length is longer - make that equal to longest text! + widestYLabel = (textMsr.textWidth > widestYLabel) ? textMsr.textWidth : widestYLabel; + highestYLabel= (textMsr.textHeight > highestYLabel) ? textMsr.textHeight : highestYLabel; + } + } + } + } + } + + // yAxisLabel + leftNotUsableSize = borderWidth + config.spaceLeft + rightNotUsableSize = borderWidth + config.spaceRight; + + if (drawAxis) { + if (typeof (config.yAxisLabel) != "undefined") { + if (config.yAxisLabel.trim() != "") { + yAxisLabelWidth = (config.yAxisFontSize + config.yAxisLabelSpaceLeft + config.yAxisLabelSpaceRight); + yAxisLabelPosLeft = borderWidth + config.spaceLeft + config.yAxisLabelSpaceLeft + config.yAxisFontSize; + yAxisLabelPosRight = width - borderWidth - config.spaceRight - config.yAxisLabelSpaceLeft - config.yAxisFontSize; + } + } + + if (config.yAxisLeft) { + if (reverseAxis == false) leftNotUsableSize = borderWidth + config.spaceLeft + yAxisLabelWidth + widestYLabel + config.yAxisSpaceLeft + config.yAxisSpaceRight; + else leftNotUsableSize = borderWidth + config.spaceLeft + yAxisLabelWidth + widestXLabel + config.yAxisSpaceLeft + config.yAxisSpaceRight; + } + + if (config.yAxisRight) { + if (reverseAxis == false) rightNotUsableSize = borderWidth + config.spaceRight + yAxisLabelWidth + widestYLabel + config.yAxisSpaceLeft + config.yAxisSpaceRight; + else rightNotUsableSize = borderWidth + config.spaceRight + yAxisLabelWidth + widestXLabel + config.yAxisSpaceLeft + config.yAxisSpaceRight; + } + } + + availableWidth = width - leftNotUsableSize - rightNotUsableSize; + + // Title + + if (config.graphTitle.trim() != "") { + graphTitleHeight = (config.graphTitleFontSize + config.graphTitleSpaceBefore + config.graphTitleSpaceAfter); + graphTitlePosY = borderWidth + config.spaceTop + graphTitleHeight - config.graphTitleSpaceAfter; + } + + // subTitle + + if (config.graphSubTitle.trim() != "") { + graphSubTitleHeight = (config.graphSubTitleFontSize + config.graphSubTitleSpaceBefore + config.graphSubTitleSpaceAfter); + graphSubTitlePosY = borderWidth + config.spaceTop + graphTitleHeight + graphSubTitleHeight - config.graphSubTitleSpaceAfter; + } + + // yAxisUnit + + if (drawAxis) { + if (typeof (config.yAxisUnit) != "undefined") { + if (config.yAxisUnit.trim() != "") { + yAxisUnitHeight = (config.yAxisUnitFontSize + config.yAxisUnitSpaceBefore + config.yAxisUnitSpaceAfter); + yAxisUnitPosY = borderWidth + config.spaceTop + graphTitleHeight + graphSubTitleHeight + yAxisUnitHeight - config.yAxisUnitSpaceAfter; + } + } + } + + topNotUsableSize = borderWidth + config.spaceTop + graphTitleHeight + graphSubTitleHeight + yAxisUnitHeight + config.graphSpaceBefore; + + + // footNote + + if (typeof (config.footNote) != "undefined") { + if (config.footNote.trim() != "") { + footNoteHeight = (config.footNoteFontSize + config.footNoteSpaceBefore + config.footNoteSpaceAfter); + footNotePosY = height - config.spaceBottom - borderWidth - config.footNoteSpaceAfter; + } + } + + // compute space for Legend + if (typeof (config.legend) != "undefined") { + if (config.legend == true) { + ctx.font = config.legendFontStyle + " " + config.legendFontSize + "px " + config.legendFontFamily; + if (drawLegendOnData) { + for (var i = data.datasets.length - 1; i >= 0; i--) { + if (typeof (data.datasets[i].title) == "string") { + + if (data.datasets[i].title.trim() != "") { + nbeltLegend++; + var textLength = ctx.measureText(fmtChartJS(config,data.datasets[i].title,config.fmtLegend)).width; + //If the text length is longer - make that equal to longest text! + widestLegend = (textLength > widestLegend) ? textLength : widestLegend; + } + } + } + } else { + for (var i = data.length - 1; i >= 0; i--) { + if (typeof (data[i].title) == "string") { + if (data[i].title.trim() != "") { + nbeltLegend++; + var textLength = ctx.measureText(fmtChartJS(config,data[i].title,config.fmtLegend)).width; + //If the text length is longer - make that equal to longest text! + widestLegend = (textLength > widestLegend) ? textLength : widestLegend; + } + } + } + } + + if (nbeltLegend > 1) { + widestLegend += config.legendBlockSize + config.legendSpaceBetweenBoxAndText; + + availableLegendWidth = width - config.spaceLeft - config.spaceRight - 2 * (borderWidth) - config.legendSpaceLeftText - config.legendSpaceRightText; + if (config.legendBorders == true) availableLegendWidth -= 2 * (config.legendBordersWidth) - config.legendBordersSpaceLeft - config.legendBordersSpaceRight; + + maxLegendOnLine = Math.floor((availableLegendWidth + config.legendSpaceBetweenTextHorizontal )/ (widestLegend + config.legendSpaceBetweenTextHorizontal )); + nbLegendLines = Math.ceil(nbeltLegend / maxLegendOnLine); + + nbLegendCols = Math.ceil(nbeltLegend / nbLegendLines); + + spaceLegendHeight = nbLegendLines * (config.legendFontSize + config.legendSpaceBetweenTextVertical) - config.legendSpaceBetweenTextVertical + config.legendSpaceBeforeText + config.legendSpaceAfterText; + + yFirstLegendTextPos = height - borderWidth - config.spaceBottom - footNoteHeight - spaceLegendHeight + config.legendSpaceBeforeText + config.legendFontSize; + + xFirstLegendTextPos = config.spaceLeft + (width - config.spaceLeft - config.spaceRight - nbLegendCols * (widestLegend + config.legendSpaceBetweenTextHorizontal) + config.legendSpaceBetweenTextHorizontal ) / 2 ; + if (config.legendBorders == true) { + spaceLegendHeight += 2 * config.legendBordersWidth + config.legendBordersSpaceBefore + config.legendBordersSpaceAfter; + yFirstLegendTextPos -= (config.legendBordersWidth + config.legendBordersSpaceAfter); + yLegendBorderPos = Math.floor(height - borderWidth - config.spaceBottom - footNoteHeight - spaceLegendHeight + (config.legendBordersWidth / 2) + config.legendBordersSpaceBefore); + xLegendBorderPos = Math.floor(xFirstLegendTextPos - config.legendSpaceLeftText - (config.legendBordersWidth / 2)); + legendBorderHeight = Math.ceil(spaceLegendHeight - config.legendBordersWidth) - config.legendBordersSpaceBefore - config.legendBordersSpaceAfter; + legendBorderWidth = Math.ceil(nbLegendCols * (widestLegend + config.legendSpaceBetweenTextHorizontal)) - config.legendSpaceBetweenTextHorizontal + config.legendBordersWidth + config.legendSpaceRightText + config.legendSpaceLeftText; + } + } + } + } + + // xAxisLabel + + if (drawAxis) { + if (typeof (config.xAxisLabel) != "undefined") { + if (config.xAxisLabel.trim() != "") { + xAxisLabelHeight = (config.xAxisFontSize + config.xAxisLabelSpaceBefore + config.xAxisLabelSpaceAfter); + xAxisLabelPos = height - borderWidth - config.spaceBottom - footNoteHeight - spaceLegendHeight - config.xAxisLabelSpaceAfter; + } + } + } + + xLabelWidth = 0; + + if (drawAxis && (config.xAxisBottom || config.xAxisTop)) { + if (reverseAxis == false) { var widestLabel = widestXLabel; var highestLabel=highestXLabel;nblab = data.labels.length; } + else { var widestLabel = widestYLabel; var highestLabel=highestYLabel; nblab = ylabels.length; } + if (config.rotateLabels == "smart") { + rotateLabels = 0; + if ((availableWidth + config.xAxisSpaceBetweenLabels) / nblab < (widestLabel + config.xAxisSpaceBetweenLabels)) { + rotateLabels = 45; + if (availableWidth / nblab < Math.abs(Math.cos(rotateLabels * Math.PI / 180) * widestLabel)) { + rotateLabels = 90; + } + } + } else { + rotateLabels = config.rotateLabels + if (rotateLabels < 0) rotateLabels = 0; + if (rotateLabels > 180) rotateLabels = 180; + } + + if (rotateLabels > 90) rotateLabels += 180; + xLabelHeight = Math.abs(Math.sin(rotateLabels * Math.PI / 180) * widestLabel) + Math.abs(Math.sin((rotateLabels + 90) * Math.PI / 180) * highestLabel) + config.xAxisSpaceBefore + config.xAxisSpaceAfter; + xLabelPos = height - borderWidth - config.spaceBottom - footNoteHeight - spaceLegendHeight - xAxisLabelHeight - (xLabelHeight - config.xAxisSpaceBefore)-config.graphSpaceAfter; + xLabelWidth = Math.abs(Math.cos(rotateLabels * Math.PI / 180) * widestLabel) + Math.abs(Math.cos((rotateLabels + 90) * Math.PI / 180) * highestLabel); + + leftNotUsableSize = Max([leftNotUsableSize, borderWidth + config.spaceLeft + xLabelWidth / 2]); + rightNotUsableSize = Max([rightNotUsableSize, borderWidth + config.spaceRight + xLabelWidth / 2]); + availableWidth = width - leftNotUsableSize - rightNotUsableSize; + } + + if(config.xAxisBottom) + { + bottomNotUsableHeightWithoutXLabels = borderWidth + config.spaceBottom + footNoteHeight + spaceLegendHeight + xAxisLabelHeight; + bottomNotUsableHeightWithXLabels = bottomNotUsableHeightWithoutXLabels + xLabelHeight+config.graphSpaceAfter; + availableHeight = height - topNotUsableSize - bottomNotUsableHeightWithXLabels; + } + else + { + bottomNotUsableHeightWithoutXLabels = borderWidth + config.spaceBottom + footNoteHeight + spaceLegendHeight + xAxisLabelHeight; + bottomNotUsableHeightWithXLabels = bottomNotUsableHeightWithoutXLabels +config.graphSpaceAfter; + availableHeight = height - topNotUsableSize - bottomNotUsableHeightWithXLabels; + } + + // ----------------------- DRAW EXTERNAL ELEMENTS ------------------------------------------------- + + if(widestYLabel != 1){ + + // Draw Borders + + if (borderWidth > 0) { + ctx.save(); + ctx.beginPath(); + ctx.lineWidth = 2 * borderWidth; + ctx.strokeStyle = config.canvasBordersColor; + ctx.moveTo(0, 0); + ctx.lineTo(0, height); + ctx.lineTo(width, height); + ctx.lineTo(width, 0); + ctx.lineTo(0, 0); + ctx.stroke(); + ctx.restore(); + } + + // Draw Graph Title + + if (graphTitleHeight > 0) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.graphTitleFontStyle + " " + config.graphTitleFontSize + "px " + config.graphTitleFontFamily; + ctx.fillStyle = config.graphTitleFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(config.spaceLeft + (width - config.spaceLeft - config.spaceRight) / 2, graphTitlePosY); + ctx.fillText(config.graphTitle, 0, 0); + ctx.stroke(); + ctx.restore(); + } + + // Draw Graph Sub-Title + + if (graphSubTitleHeight > 0) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.graphSubTitleFontStyle + " " + config.graphSubTitleFontSize + "px " + config.graphSubTitleFontFamily; + ctx.fillStyle = config.graphSubTitleFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(config.spaceLeft + (width - config.spaceLeft - config.spaceRight) / 2, graphSubTitlePosY); + ctx.fillText(config.graphSubTitle, 0, 0); + ctx.stroke(); + ctx.restore(); + } + + // Draw Y Axis Unit + + if (yAxisUnitHeight > 0) { + if (config.yAxisLeft) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisUnitFontStyle + " " + config.yAxisUnitFontSize + "px " + config.yAxisUnitFontFamily; + ctx.fillStyle = config.yAxisUnitFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(leftNotUsableSize, yAxisUnitPosY); + ctx.fillText(config.yAxisUnit, 0, 0); + ctx.stroke(); + ctx.restore(); + } + if (config.yAxisRight) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisUnitFontStyle + " " + config.yAxisUnitFontSize + "px " + config.yAxisUnitFontFamily; + ctx.fillStyle = config.yAxisUnitFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(width - rightNotUsableSize, yAxisUnitPosY); + ctx.fillText(config.yAxisUnit, 0, 0); + ctx.stroke(); + ctx.restore(); + } + } + + // Draw Y Axis Label + + if (yAxisLabelWidth > 0) { + if (config.yAxisLeft) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisFontStyle + " " + config.yAxisFontSize + "px " + config.yAxisFontFamily; + ctx.fillStyle = config.yAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(yAxisLabelPosLeft, topNotUsableSize + (availableHeight / 2)); + ctx.rotate(-(90 * (Math.PI / 180))); + ctx.fillText(config.yAxisLabel, 0, 0); + ctx.stroke(); + ctx.restore(); + } + if (config.yAxisRight) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.yAxisFontStyle + " " + config.yAxisFontSize + "px " + config.yAxisFontFamily; + ctx.fillStyle = config.yAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(yAxisLabelPosRight, topNotUsableSize + (availableHeight / 2)); + ctx.rotate(+(90 * (Math.PI / 180))); + ctx.fillText(config.yAxisLabel, 0, 0); + ctx.stroke(); + ctx.restore(); + } + } + + // Draw X Axis Label + + if (xAxisLabelHeight > 0) { + if (config.xAxisBottom) { + ctx.save(); + ctx.beginPath(); + ctx.font = config.xAxisFontStyle + " " + config.xAxisFontSize + "px " + config.xAxisFontFamily; + ctx.fillStyle = config.xAxisFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(leftNotUsableSize + (availableWidth / 2), xAxisLabelPos); + ctx.fillText(config.xAxisLabel, 0, 0); + ctx.stroke(); + ctx.restore(); + } + } + + // Draw Legend + + if (nbeltLegend > 1) { + if (config.legendBorders == true) { + ctx.save(); + ctx.beginPath(); + + ctx.lineWidth = config.legendBordersWidth; + ctx.strokeStyle = config.legendBordersColors; + + ctx.moveTo(xLegendBorderPos, yLegendBorderPos); + ctx.lineTo(xLegendBorderPos, yLegendBorderPos + legendBorderHeight); + ctx.lineTo(xLegendBorderPos + legendBorderWidth, yLegendBorderPos + legendBorderHeight); + ctx.lineTo(xLegendBorderPos + legendBorderWidth, yLegendBorderPos); + ctx.lineTo(xLegendBorderPos, yLegendBorderPos); + ctx.lineTo(xLegendBorderPos + legendBorderWidth, yLegendBorderPos); + ctx.lineTo(xLegendBorderPos, yLegendBorderPos); + ctx.lineTo(xLegendBorderPos, yLegendBorderPos + legendBorderHeight); + + ctx.stroke(); + ctx.restore(); + } + + nbcols = nbLegendCols - 1; + ypos = yFirstLegendTextPos - (config.legendFontSize + config.legendSpaceBetweenTextVertical); + xpos = 0; + + if (drawLegendOnData) fromi = data.datasets.length; + else fromi = data.length; + + for (var i = fromi - 1; i >= 0; i--) { + orderi = i; + if (reverseLegend) { + if (drawLegendOnData) orderi = data.datasets.length - i - 1; + else orderi = data.length - i - 1; + } + + if (drawLegendOnData) tpof = typeof (data.datasets[orderi].title); + else tpof = typeof (data[orderi].title) + + if (tpof == "string") { + if (drawLegendOnData) lgtxt = fmtChartJS(config,data.datasets[orderi].title,config.fmtLegend).trim(); + else lgtxt = fmtChartJS(config,data[orderi].title,config.fmtLegend).trim(); + if (lgtxt != "") { + nbcols++; + if (nbcols == nbLegendCols) { + nbcols = 0; + xpos = xFirstLegendTextPos; + ypos += config.legendFontSize + config.legendSpaceBetweenTextVertical; + } + else { + xpos += widestLegend + config.legendSpaceBetweenTextHorizontal; + } + + ctx.save(); + ctx.beginPath(); + + if (drawLegendOnData) { + if (typeof data.datasets[orderi].strokeColor == "function")ctx.strokeStyle = data.datasets[orderi].strokeColor("STROKECOLOR",data,config,orderi,-1,1,-1); + else if(typeof data.datasets[orderi].strokeColor=="string")ctx.strokeStyle = data.datasets[orderi].strokeColor; + else ctx.strokeStyle=config.defaultStrokeColor; + } + else { + if (typeof data[orderi].color == "function")ctx.fillStyle = data[orderi].color("COLOR",data,config,orderi,-1,1,data[orderi].value); + else if(typeof data[orderi].color == "string")ctx.strokeStyle = data[orderi].color; + else ctx.strokeStyle=config.defaultStrokeColor; + } + + + + if (legendBox) { + ctx.lineWidth = 1; + ctx.moveTo(xpos , ypos); + ctx.lineTo(xpos + config.legendBlockSize, ypos); + ctx.lineTo(xpos + config.legendBlockSize, ypos - config.legendFontSize ); + ctx.lineTo(xpos , ypos - config.legendFontSize ); + ctx.lineTo(xpos , ypos); + ctx.closePath(); + if (drawLegendOnData) { + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,-1,1,-1); + else if(typeof data.datasets[orderi].fillColor=="string")ctx.fillStyle = data.datasets[orderi].fillColor; + else ctx.fillStyle=config.defaultFillColor;} + else {if(typeof data[orderi].color == "string")ctx.fillStyle = data[orderi].color;else ctx.fillStyle=config.defaultFillColor;} + ctx.fill(); + } + else { + ctx.lineWidth = config.legendColorIndicatorStrokeWidth ? + config.legendColorIndicatorStrokeWidth : config.datasetStrokeWidth; + if (config.legendColorIndicatorStrokeWidth && config.legendColorIndicatorStrokeWidth > config.legendFontSize) { + ctx.lineWidth = config.legendFontSize; + } + ctx.moveTo(xpos + 2, ypos - (config.legendFontSize / 2)); + ctx.lineTo(xpos + 2 + config.legendBlockSize, ypos - (config.legendFontSize / 2)); + } + ctx.stroke(); + ctx.restore(); + ctx.save(); + ctx.beginPath(); + ctx.font = config.legendFontStyle + " " + config.legendFontSize + "px " + config.legendFontFamily; + ctx.fillStyle = config.legendFontColor; + ctx.textAlign = "left"; + ctx.textBaseline = "bottom"; + ctx.translate(xpos + config.legendBlockSize + config.legendSpaceBetweenBoxAndText, ypos); + ctx.fillText(lgtxt, 0, 0); + ctx.stroke(); + ctx.restore(); + } + } + } + } + + // Draw FootNote + if (config.footNote.trim() != "") { + ctx.save(); + ctx.font = config.footNoteFontStyle + " " + config.footNoteFontSize + "px " + config.footNoteFontFamily; + ctx.fillStyle = config.footNoteFontColor; + ctx.textAlign = "center"; + ctx.textBaseline = "bottom"; + ctx.translate(leftNotUsableSize + (availableWidth / 2), footNotePosY); + ctx.fillText(config.footNote, 0, 0); + ctx.stroke(); + ctx.restore(); + } + + + } + + clrx = leftNotUsableSize; + clrwidth = availableWidth; + clry = topNotUsableSize; + clrheight = availableHeight; + + + return { + leftNotUsableSize: leftNotUsableSize, + rightNotUsableSize: rightNotUsableSize, + availableWidth: availableWidth, + topNotUsableSize: topNotUsableSize, + bottomNotUsableHeightWithoutXLabels: bottomNotUsableHeightWithoutXLabels, + bottomNotUsableHeightWithXLabels: bottomNotUsableHeightWithXLabels, + availableHeight: availableHeight, + widestXLabel: widestXLabel, + highestXLabel: highestXLabel, + widestYLabel: widestYLabel, + highestYLabel: highestYLabel, + rotateLabels: rotateLabels, + xLabelPos: xLabelPos, + clrx: clrx, + clry: clry, + clrwidth: clrwidth, + clrheight: clrheight + }; + } ; + + // Function for additionalLine (BarLine|Line) + function drawLinesDataset(animPc,data,config,ctx,vars) { + var xAxisPosY = vars.xAxisPosY; + var yAxisPosX = vars.yAxisPosX; + var valueHop = vars.valueHop; + var scaleHop = vars.scaleHop; + var zeroY = vars.zeroY; + var calculatedScale = vars.calculatedScale; + var annotateCnt = vars.annotateCnt; + + var totvalue = new Array(); + var maxvalue = new Array(); + + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + totvalue[j] = 0; + maxvalue[j] = -999999999; + } + } + for (var i = 0; i < data.datasets.length; i++) { + for (var j = 0; j < data.datasets[i].data.length; j++) { + totvalue[j] += data.datasets[i].data[j]; + maxvalue[j] = Max([maxvalue[j], data.datasets[i].data[j]]); + } + } + + for (var i = 0; i < data.datasets.length; i++) { + + var prevpt=-1; + var frstpt=-1; + + if (animPc >= 1) { + if (typeof (data.datasets[i].title) == "string") lgtxt = data.datasets[i].title.trim(); + else lgtxt = ""; + } + + if (typeof data.datasets[i].strokeColor == "function") { + ctx.strokeStyle = data.datasets[i].strokeColor("STROKECOLOR",data,config,i,-1,animPc,-1); + } + else if(typeof data.datasets[i].strokeColor=="string") { + ctx.strokeStyle = data.datasets[i].strokeColor; + } + else ctx.strokeStyle=config.defaultStrokeColor; + ctx.lineWidth = config.datasetStrokeWidth; + ctx.beginPath(); + + for (var j = 0; j < data.datasets[i].data.length; j++) { + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + + if (prevpt==-1){ + ctx.moveTo(xPos(j), yPos(i, j)); + frstpt=j; + } else { + if (config.bezierCurve) { + ctx.bezierCurveTo(xPos(j-(j-prevpt)/2), yPos(i, prevpt), xPos(j-(j-prevpt)/2), yPos(i, j), xPos(j), yPos(i, j)); + } + else { + ctx.lineTo(xPos(j), yPos(i, j)); + } + } + prevpt=j; + if (animPc >= 1) { + if (i == 0) divprev = data.datasets[i].data[j]; + else divprev = data.datasets[i].data[j] - data.datasets[i - 1].data[j]; + if (i == data.datasets.length - 1) divnext = data.datasets[i].data[j]; + else divnext = data.datasets[i].data[j] - data.datasets[i + 1].data[j]; + + if (typeof (data.labels[j]) == "string") lgtxt2 = data.labels[j].trim(); + else lgtxt2 = ""; + jsGraphAnnotate[ctx.ChartNewId][annotateCnt++] = ["POINT", xPos(j), yPos(i, j), lgtxt, lgtxt2, 1*data.datasets[i].data[j], divprev, divnext, maxvalue[j], totvalue[j], i, j]; + if (config.inGraphDataShow) { + ctx.save(); + ctx.textAlign = config.inGraphDataAlign; + ctx.textBaseline = config.inGraphDataVAlign; + ctx.font = config.inGraphDataFontStyle + ' ' + config.inGraphDataFontSize + 'px ' + config.inGraphDataFontFamily; + ctx.fillStyle = config.inGraphDataFontColor; + var dotX = yAxisPosX + (valueHop *k), + dotY = xAxisPosY - animPc*(calculateOffset(config, data.datasets[i].data[j],calculatedScale,scaleHop)), + paddingTextX = config.inGraphDataPaddingX, + paddingTextY = config.inGraphDataPaddingY; + var dispString = tmplbis(config.inGraphDataTmpl, { config:config, v1 : fmtChartJS(config,lgtxt,config.fmtV1), v2 : fmtChartJS(config,lgtxt2,config.fmtV2), v3 : fmtChartJS(config,1*data.datasets[i].data[j],config.fmtV3), v4 : fmtChartJS(config,divprev,config.fmtV4), v5 : fmtChartJS(config,divnext,config.fmtV5), v6 : fmtChartJS(config,maxvalue[j],config.fmtV6), v7 : fmtChartJS(config,totvalue[j],config.fmtV7), v8 : roundToWithThousands(config,fmtChartJS(config,100 * data.datasets[i].data[j] / totvalue[j],config.fmtV8),config.roundPct),v9 : fmtChartJS(config,yAxisPosX+ (valueHop *k),config.fmtV9),v10 : fmtChartJS(config,xAxisPosY - (calculateOffset(config, data.datasets[i].data[j], calculatedScale, scaleHop)),config.fmtV10),v11 : fmtChartJS(config,i,config.fmtV11), v12 : fmtChartJS(config,j,config.fmtV12),data:data}); + ctx.translate(xPos(j) + paddingTextX, yPos(i,j) - paddingTextY); + ctx.rotate(config.inGraphDataRotate * (Math.PI / 180)); + ctx.fillTextMultiLine(dispString,0,0,ctx.textBaseline,config.inGraphDataFontSize); + ctx.restore(); + } + } + } + } + ctx.stroke(); + if (config.datasetFill) { + ctx.lineTo(yAxisPosX + (valueHop * (data.datasets[i].data.length - 1)), xAxisPosY - zeroY); + ctx.lineTo(xPos(frstpt), xAxisPosY - zeroY); + ctx.lineTo(xPos(frstpt), yPos(i, frstpt)); + ctx.closePath(); + if (typeof data.datasets[i].fillColor == "function")ctx.fillStyle = data.datasets[i].fillColor("FILLCOLOR",data,config,i,-1,animPc,-1); + else if(typeof data.datasets[i].fillColor=="string")ctx.fillStyle = data.datasets[i].fillColor; + else ctx.fillStyle=config.defaultFillColor; + ctx.fill(); + + } + else { + ctx.closePath(); + } + if (config.pointDot) { + if (typeof data.datasets[i].pointColor == "function")ctx.fillStyle = data.datasets[i].pointColor("POINTCOLOR",data,config,i,-1,animPc,-1); + else ctx.fillStyle = data.datasets[i].pointColor; + if (typeof data.datasets[i].pointStrokeColor == "function")ctx.strokeStyle = data.datasets[i].pointStrokeColor("POINTSTROKECOLOR",data,config,i,-1,animPc,-1); + else ctx.strokeStyle = data.datasets[i].pointStrokeColor; + + ctx.lineWidth = config.pointDotStrokeWidth; + for (var k = 0; k < data.datasets[i].data.length; k++) { + if (!(typeof(data.datasets[i].data[k])=='undefined')) { + ctx.beginPath(); + ctx.arc(xPos(k), yPos(i,k), config.pointDotRadius, 0, Math.PI * 2, true); + ctx.fill(); + ctx.stroke(); + } + } + } + }; + + function yPos(dataSet, iteration) { + return xAxisPosY - zeroY - animPc * (calculateOffset(config, data.datasets[dataSet].data[iteration], calculatedScale, scaleHop)-zeroY); + }; + function xPos(iteration) { + return yAxisPosX + (valueHop * iteration); + }; + } + + + + + function log10(val) { + return Math.log(val) / Math.LN10; + } ; + + function setRect(ctx,config) + { + if(config.clearRect){ + clear(ctx); + ctx.clearRect(0, 0, width, height); + } else { + clear(ctx); + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = config.savePngBackgroundColor; + ctx.strokeStyle = config.savePngBackgroundColor; + ctx.beginPath(); + ctx.moveTo(0,0); + ctx.lineTo(0,ctx.canvas.height); + ctx.lineTo(ctx.canvas.width,ctx.canvas.height); + ctx.lineTo(ctx.canvas.width,0); + ctx.lineTo(0,0); + ctx.stroke(); + ctx.fill(); + + } + } ; + + + function defMouse(ctx,data,config,tpgraph) { + + if (config.annotateDisplay == true) { + if (cursorDivCreated == false) oCursor = new makeCursorObj('divCursor'); + if (isIE() < 9 && isIE() != false) ctx.canvas.attachEvent("on" + config.annotateFunction.split(' ')[0], function (event) { + if ((config.annotateFunction.split(' ')[1]=="left" && event.which==1) || + (config.annotateFunction.split(' ')[1]=="middle" && event.which==2) || + (config.annotateFunction.split(' ')[1]=="right" && event.which==3) || + (typeof(config.annotateFunction.split(' ')[1])!="string")) doMouseMove(ctx, config, event,data) + }); + else ctx.canvas.addEventListener(config.annotateFunction.split(' ')[0], function (event) { + if ((config.annotateFunction.split(' ')[1]=="left" && event.which==1) || + (config.annotateFunction.split(' ')[1]=="middle" && event.which==2) || + (config.annotateFunction.split(' ')[1]=="right" && event.which==3) || + (typeof(config.annotateFunction.split(' ')[1])!="string")) doMouseMove(ctx, config, event,data) + }, false); + } + + if(config.savePng) + { + if (isIE() < 9 && isIE() != false) ctx.canvas.attachEvent("on"+ config.savePngFunction.split(' ')[0], function(event) { + if ((config.savePngFunction.split(' ')[1]=="left" && event.which==1) || + (config.savePngFunction.split(' ')[1]=="middle" && event.which==2) || + (config.savePngFunction.split(' ')[1]=="right" && event.which==3) || + (typeof(config.savePngFunction.split(' ')[1])!="string")) saveCanvas(ctx,data,config,tpgraph); + }); + else ctx.canvas.addEventListener(config.savePngFunction.split(' ')[0], function (event) { + if ((config.savePngFunction.split(' ')[1]=="left" && event.which==1) || + (config.savePngFunction.split(' ')[1]=="middle" && event.which==2) || + (config.savePngFunction.split(' ')[1]=="right" && event.which==3) || + (typeof(config.savePngFunction.split(' ')[1])!="string")) saveCanvas(ctx,data,config,tpgraph); + } + ,false); + + } + + }; + +}; diff --git a/e107_web/js/chart/mathFunctions.js b/e107_web/js/chart/mathFunctions.js new file mode 100644 index 000000000..918a50d80 --- /dev/null +++ b/e107_web/js/chart/mathFunctions.js @@ -0,0 +1,148 @@ +function mean(params) { + var datasetNr = params.datasetNr; + var data = params.data; + var mean = 0; + var nr = 0; + for (var j = 0; j < data.datasets[datasetNr].data.length; j++) { + // important to check because missing values are possible + if (!(typeof(data.datasets[datasetNr].data[j])=='undefined')){ + mean += 1*data.datasets[datasetNr].data[j]; + nr++; + } + } + mean /= nr; + return mean; +} + +function varianz(params) { + var data = params.data; + var datasetNr = params.datasetNr; + var meanVal = mean(params); + var varianz = 0; + var nr = 0; + for (var j = 0; j < data.datasets[datasetNr].data.length; j++) { + // important to check because missing values are possible + if (!(typeof(data.datasets[datasetNr].data[j])=='undefined')) { + varianz += Math.pow(1*data.datasets[datasetNr].data[j]-meanVal,2); + nr++; + } + } + return varianz/nr; +} + +function stddev(params) { + return Math.sqrt(varianz(params)); +} + +function cv(params) { + return stddev(params)/mean(params); +} + + + +function drawMath(ctx,config,data,msr,vars) { + var xAxisPosY = vars.xAxisPosY; + var yAxisPosX = vars.yAxisPosX; + var valueHop = vars.valueHop; + var scaleHop = vars.scaleHop; + var zeroY = vars.zeroY; + var calculatedScale = vars.calculatedScale; + var calculateOffset = vars.calculateOffset; + var barWidth = vars.barWidth; + var barBool = !(typeof barWidth == "undefined") ? true : false; + + // check each dataset if a mathDraw function exists + for (var i = 0; i < data.datasets.length; i++) { + // get mathFctName (stddev|mean|...) + var deviationFct = data.datasets[i].drawMathDeviation; + if (deviationFct) { + drawMathDeviation(i,deviationFct); + } + var lineFct = data.datasets[i].drawMathLine; + if (lineFct) { + drawMathLine(i,lineFct); + } + } + + /** + * Draw a deviation vertical line (if needed with top and bottom horizontal lines) + * @param i {integer} dataset number + * @param deviationFct {string} math function name + */ + function drawMathDeviation(i,deviationFct) { + var deviation = 0; + // check if the math function exists + if (typeof eval(deviationFct) == "function") { + var parameter = {data:data,datasetNr: i}; + deviation = window[deviationFct](parameter); + } + if (isNumber(deviation)) { + ctx.strokeStyle= data.datasets[i].deviationStrokeColor ? data.datasets[i].deviationStrokeColor : config.defaultStrokeColor; + ctx.lineWidth = config.datasetStrokeWidth; + ctx.beginPath(); + for (var j = 0; j < data.datasets[i].data.length; j++) { + // important to check because missing values are possible + if (!(typeof(data.datasets[i].data[j])=='undefined')) { + var deviationWidth = data.datasets[i].deviationWidth; + // draw the top and the bottom of the vertical line if a deviationWidth exists + if (deviationWidth) { + ctx.moveTo(xPos(j,i,barWidth,barBool)-deviationWidth,yPos(i,j,-deviation,true)); + ctx.lineTo(xPos(j,i,barWidth,barBool)+deviationWidth,yPos(i,j,-deviation,true)); + ctx.moveTo(xPos(j,i,barWidth,barBool)-deviationWidth,yPos(i,j,deviation,true)); + ctx.lineTo(xPos(j,i,barWidth,barBool)+deviationWidth,yPos(i,j,deviation,true)); + } + // draw the vertical line + ctx.moveTo(xPos(j,i,barWidth,barBool),yPos(i,j,-deviation,true)); + ctx.lineTo(xPos(j,i,barWidth,barBool),yPos(i,j,deviation,true)); + } + } + ctx.stroke(); + ctx.closePath(); + } + } + + /** + * Draw a horizontal line + * @param i {integer} numer of dataset + * @param lineFct {string} name of the mathfunctions => compute height + */ + function drawMathLine(i,lineFct) { + var line = 0; + // check if the math function exists + if (typeof eval(lineFct) == "function") { + var parameter = {data:data,datasetNr: i}; + line = window[lineFct](parameter); + } + if (!(typeof(line)=='undefined')) { + ctx.strokeStyle= data.datasets[i].mathLineStrokeColor ? data.datasets[i].mathLineStrokeColor : config.defaultStrokeColor; + ctx.lineWidth = config.datasetStrokeWidth; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,yPos(i,0,line,false)); + ctx.lineTo(yAxisPosX + msr.availableWidth,yPos(i,data.datasets[i].data.length-1,line,false)); + ctx.stroke(); + ctx.closePath(); + } + } + + /** + * Get a y position depending on the current values + * @param dataset {integer} number of dataset + * @param iteration {integer} number of value inside dataset.data + * @param add {float} add a value to the current value if value is true + * @param value {bool} true => value+add, false=>add + * @returns {float} position (px) + */ + function yPos(dataSet, iteration, add,value) { + value = value ? 1*data.datasets[dataSet].data[iteration] : 0; + return xAxisPosY - calculateOffset(config, value+add, calculatedScale, scaleHop); + }; + function xPos(iteration,dataSet,barWidth,bar) { + if (bar) { + return yAxisPosX + config.barValueSpacing + valueHop * iteration + barWidth * dataSet + + config.barDatasetSpacing * dataSet + config.barStrokeWidth * dataSet+barWidth/2; + } else { + return yAxisPosX + (valueHop * iteration); + } + + }; +}