Chart-pie.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  1. /*!
  2. * Chart.js
  3. * http://chartjs.org/
  4. *
  5. * Copyright 2013 Nick Downie
  6. * Released under the MIT license
  7. * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
  8. */
  9. //Define the global Chart Variable as a class.
  10. window.Chart = function (context) {
  11. var chart = this;
  12. //Easing functions adapted from Robert Penner's easing equations
  13. //http://www.robertpenner.com/easing/
  14. var animationOptions = {
  15. linear: function (t) {
  16. return t;
  17. },
  18. easeInQuad: function (t) {
  19. return t * t;
  20. },
  21. easeOutQuad: function (t) {
  22. return -1 * t * (t - 2);
  23. },
  24. easeInOutQuad: function (t) {
  25. if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
  26. return -1 / 2 * ((--t) * (t - 2) - 1);
  27. },
  28. easeInCubic: function (t) {
  29. return t * t * t;
  30. },
  31. easeOutCubic: function (t) {
  32. return 1 * ((t = t / 1 - 1) * t * t + 1);
  33. },
  34. easeInOutCubic: function (t) {
  35. if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
  36. return 1 / 2 * ((t -= 2) * t * t + 2);
  37. },
  38. easeInQuart: function (t) {
  39. return t * t * t * t;
  40. },
  41. easeOutQuart: function (t) {
  42. return -1 * ((t = t / 1 - 1) * t * t * t - 1);
  43. },
  44. easeInOutQuart: function (t) {
  45. if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
  46. return -1 / 2 * ((t -= 2) * t * t * t - 2);
  47. },
  48. easeInQuint: function (t) {
  49. return 1 * (t /= 1) * t * t * t * t;
  50. },
  51. easeOutQuint: function (t) {
  52. return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
  53. },
  54. easeInOutQuint: function (t) {
  55. if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
  56. return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
  57. },
  58. easeInSine: function (t) {
  59. return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
  60. },
  61. easeOutSine: function (t) {
  62. return 1 * Math.sin(t / 1 * (Math.PI / 2));
  63. },
  64. easeInOutSine: function (t) {
  65. return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
  66. },
  67. easeInExpo: function (t) {
  68. return (t == 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
  69. },
  70. easeOutExpo: function (t) {
  71. return (t == 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
  72. },
  73. easeInOutExpo: function (t) {
  74. if (t == 0) return 0;
  75. if (t == 1) return 1;
  76. if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
  77. return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
  78. },
  79. easeInCirc: function (t) {
  80. if (t >= 1) return t;
  81. return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
  82. },
  83. easeOutCirc: function (t) {
  84. return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
  85. },
  86. easeInOutCirc: function (t) {
  87. if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
  88. return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
  89. },
  90. easeInElastic: function (t) {
  91. var s = 1.70158;
  92. var p = 0;
  93. var a = 1;
  94. if (t == 0) return 0;
  95. if ((t /= 1) == 1) return 1;
  96. if (!p) p = 1 * .3;
  97. if (a < Math.abs(1)) {
  98. a = 1;
  99. var s = p / 4;
  100. } else var s = p / (2 * Math.PI) * Math.asin(1 / a);
  101. return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
  102. },
  103. easeOutElastic: function (t) {
  104. var s = 1.70158;
  105. var p = 0;
  106. var a = 1;
  107. if (t == 0) return 0;
  108. if ((t /= 1) == 1) return 1;
  109. if (!p) p = 1 * .3;
  110. if (a < Math.abs(1)) {
  111. a = 1;
  112. var s = p / 4;
  113. } else var s = p / (2 * Math.PI) * Math.asin(1 / a);
  114. return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
  115. },
  116. easeInOutElastic: function (t) {
  117. var s = 1.70158;
  118. var p = 0;
  119. var a = 1;
  120. if (t == 0) return 0;
  121. if ((t /= 1 / 2) == 2) return 1;
  122. if (!p) p = 1 * (.3 * 1.5);
  123. if (a < Math.abs(1)) {
  124. a = 1;
  125. var s = p / 4;
  126. } else var s = p / (2 * Math.PI) * Math.asin(1 / a);
  127. if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
  128. return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * .5 + 1;
  129. },
  130. easeInBack: function (t) {
  131. var s = 1.70158;
  132. return 1 * (t /= 1) * t * ((s + 1) * t - s);
  133. },
  134. easeOutBack: function (t) {
  135. var s = 1.70158;
  136. return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
  137. },
  138. easeInOutBack: function (t) {
  139. var s = 1.70158;
  140. if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
  141. return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
  142. },
  143. easeInBounce: function (t) {
  144. return 1 - animationOptions.easeOutBounce(1 - t);
  145. },
  146. easeOutBounce: function (t) {
  147. if ((t /= 1) < (1 / 2.75)) {
  148. return 1 * (7.5625 * t * t);
  149. } else if (t < (2 / 2.75)) {
  150. return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + .75);
  151. } else if (t < (2.5 / 2.75)) {
  152. return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375);
  153. } else {
  154. return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375);
  155. }
  156. },
  157. easeInOutBounce: function (t) {
  158. if (t < 1 / 2) return animationOptions.easeInBounce(t * 2) * .5;
  159. return animationOptions.easeOutBounce(t * 2 - 1) * .5 + 1 * .5;
  160. }
  161. };
  162. //Variables global to the chart
  163. var width = context.canvas.width;
  164. var height = context.canvas.height;
  165. //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
  166. if (window.devicePixelRatio) {
  167. context.canvas.style.width = width + "px";
  168. context.canvas.style.height = height + "px";
  169. context.canvas.height = height * window.devicePixelRatio;
  170. context.canvas.width = width * window.devicePixelRatio;
  171. context.scale(window.devicePixelRatio, window.devicePixelRatio);
  172. }
  173. this.PolarArea = function (data, options) {
  174. chart.PolarArea.defaults = {
  175. scaleOverlay: true,
  176. scaleOverride: false,
  177. scaleSteps: null,
  178. scaleStepWidth: null,
  179. scaleStartValue: null,
  180. scaleShowLine: true,
  181. scaleLineColor: "rgba(0,0,0,.1)",
  182. scaleLineWidth: 1,
  183. scaleShowLabels: true,
  184. scaleLabel: "<%=value%>",
  185. scaleFontFamily: "'Arial'",
  186. scaleFontSize: 12,
  187. scaleFontStyle: "normal",
  188. scaleFontColor: "#666",
  189. scaleShowLabelBackdrop: true,
  190. scaleBackdropColor: "rgba(255,255,255,0.75)",
  191. scaleBackdropPaddingY: 2,
  192. scaleBackdropPaddingX: 2,
  193. segmentShowStroke: true,
  194. segmentStrokeColor: "#fff",
  195. segmentStrokeWidth: 2,
  196. animation: true,
  197. animationSteps: 100,
  198. animationEasing: "easeOutBounce",
  199. animateRotate: true,
  200. animateScale: false,
  201. onAnimationComplete: null
  202. };
  203. var config = (options) ? mergeChartConfig(chart.PolarArea.defaults, options) : chart.PolarArea.defaults;
  204. return new PolarArea(data, config, context);
  205. };
  206. this.Radar = function (data, options) {
  207. chart.Radar.defaults = {
  208. scaleOverlay: false,
  209. scaleOverride: false,
  210. scaleSteps: null,
  211. scaleStepWidth: null,
  212. scaleStartValue: null,
  213. scaleShowLine: true,
  214. scaleLineColor: "rgba(0,0,0,.1)",
  215. scaleLineWidth: 1,
  216. scaleShowLabels: false,
  217. scaleLabel: "<%=value%>",
  218. scaleFontFamily: "'Arial'",
  219. scaleFontSize: 12,
  220. scaleFontStyle: "normal",
  221. scaleFontColor: "#666",
  222. scaleShowLabelBackdrop: true,
  223. scaleBackdropColor: "rgba(255,255,255,0.75)",
  224. scaleBackdropPaddingY: 2,
  225. scaleBackdropPaddingX: 2,
  226. angleShowLineOut: true,
  227. angleLineColor: "rgba(0,0,0,.1)",
  228. angleLineWidth: 1,
  229. pointLabelFontFamily: "'Arial'",
  230. pointLabelFontStyle: "normal",
  231. pointLabelFontSize: 12,
  232. pointLabelFontColor: "#666",
  233. pointDot: true,
  234. pointDotRadius: 3,
  235. pointDotStrokeWidth: 1,
  236. datasetStroke: true,
  237. datasetStrokeWidth: 2,
  238. datasetFill: true,
  239. animation: true,
  240. animationSteps: 60,
  241. animationEasing: "easeOutQuart",
  242. onAnimationComplete: null
  243. };
  244. var config = (options) ? mergeChartConfig(chart.Radar.defaults, options) : chart.Radar.defaults;
  245. return new Radar(data, config, context);
  246. };
  247. this.Pie = function (data, options) {
  248. chart.Pie.defaults = {
  249. segmentShowStroke: true,
  250. segmentStrokeColor: "#fff",
  251. segmentStrokeWidth: 2,
  252. animation: true,
  253. animationSteps: 100,
  254. animationEasing: "easeOutBounce",
  255. animateRotate: true,
  256. animateScale: false,
  257. onAnimationComplete: null
  258. };
  259. var config = (options) ? mergeChartConfig(chart.Pie.defaults, options) : chart.Pie.defaults;
  260. return new Pie(data, config, context);
  261. };
  262. this.Doughnut = function (data, options) {
  263. chart.Doughnut.defaults = {
  264. segmentShowStroke: true,
  265. segmentStrokeColor: "#fff",
  266. segmentStrokeWidth: 2,
  267. percentageInnerCutout: 50,
  268. animation: true,
  269. animationSteps: 100,
  270. animationEasing: "easeOutBounce",
  271. animateRotate: true,
  272. animateScale: false,
  273. onAnimationComplete: null
  274. };
  275. var config = (options) ? mergeChartConfig(chart.Doughnut.defaults, options) : chart.Doughnut.defaults;
  276. return new Doughnut(data, config, context);
  277. };
  278. this.Line = function (data, options) {
  279. chart.Line.defaults = {
  280. scaleOverlay: false,
  281. scaleOverride: false,
  282. scaleSteps: null,
  283. scaleStepWidth: null,
  284. scaleStartValue: null,
  285. scaleLineColor: "rgba(0,0,0,.1)",
  286. scaleLineWidth: 1,
  287. scaleShowLabels: true,
  288. scaleLabel: "<%=value%>",
  289. scaleFontFamily: "'Arial'",
  290. scaleFontSize: 12,
  291. scaleFontStyle: "normal",
  292. scaleFontColor: "#666",
  293. scaleShowGridLines: true,
  294. scaleGridLineColor: "rgba(0,0,0,.05)",
  295. scaleGridLineWidth: 1,
  296. bezierCurve: true,
  297. pointDot: true,
  298. pointDotRadius: 4,
  299. pointDotStrokeWidth: 2,
  300. datasetStroke: true,
  301. datasetStrokeWidth: 2,
  302. datasetFill: true,
  303. animation: true,
  304. animationSteps: 60,
  305. animationEasing: "easeOutQuart",
  306. onAnimationComplete: null
  307. };
  308. var config = (options) ? mergeChartConfig(chart.Line.defaults, options) : chart.Line.defaults;
  309. return new Line(data, config, context);
  310. }
  311. this.Bar = function (data, options) {
  312. chart.Bar.defaults = {
  313. scaleOverlay: false,
  314. scaleOverride: false,
  315. scaleSteps: null,
  316. scaleStepWidth: null,
  317. scaleStartValue: null,
  318. scaleLineColor: "rgba(0,0,0,.1)",
  319. scaleLineWidth: 1,
  320. scaleShowLabels: true,
  321. scaleLabel: "<%=value%>",
  322. scaleFontFamily: "'Arial'",
  323. scaleFontSize: 12,
  324. scaleFontStyle: "normal",
  325. scaleFontColor: "#666",
  326. scaleShowGridLines: true,
  327. scaleGridLineColor: "rgba(0,0,0,.05)",
  328. scaleGridLineWidth: 1,
  329. barShowStroke: true,
  330. barStrokeWidth: 2,
  331. barValueSpacing: 5,
  332. barDatasetSpacing: 1,
  333. animation: true,
  334. animationSteps: 60,
  335. animationEasing: "easeOutQuart",
  336. onAnimationComplete: null
  337. };
  338. var config = (options) ? mergeChartConfig(chart.Bar.defaults, options) : chart.Bar.defaults;
  339. return new Bar(data, config, context);
  340. }
  341. var clear = function (c) {
  342. c.clearRect(0, 0, width, height);
  343. };
  344. var PolarArea = function (data, config, ctx) {
  345. var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
  346. calculateDrawingSizes();
  347. valueBounds = getValueBounds();
  348. labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : null;
  349. //Check and set the scale
  350. if (!config.scaleOverride) {
  351. calculatedScale = calculateScale(scaleHeight, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString);
  352. } else {
  353. calculatedScale = {
  354. steps: config.scaleSteps,
  355. stepValue: config.scaleStepWidth,
  356. graphMin: config.scaleStartValue,
  357. labels: []
  358. }
  359. populateLabels(labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, config.scaleStepWidth);
  360. }
  361. scaleHop = maxSize / (calculatedScale.steps);
  362. //Wrap in an animation loop wrapper
  363. animationLoop(config, drawScale, drawAllSegments, ctx);
  364. function calculateDrawingSizes() {
  365. maxSize = (Min([width, height]) / 2);
  366. //Remove whatever is larger - the font size or line width.
  367. maxSize -= Max([config.scaleFontSize * 0.5, config.scaleLineWidth * 0.5]);
  368. labelHeight = config.scaleFontSize * 2;
  369. //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
  370. if (config.scaleShowLabelBackdrop) {
  371. labelHeight += (2 * config.scaleBackdropPaddingY);
  372. maxSize -= config.scaleBackdropPaddingY * 1.5;
  373. }
  374. scaleHeight = maxSize;
  375. //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
  376. labelHeight = Default(labelHeight, 5);
  377. }
  378. function drawScale() {
  379. for (var i = 0; i < calculatedScale.steps; i++) {
  380. //If the line object is there
  381. if (config.scaleShowLine) {
  382. ctx.beginPath();
  383. ctx.arc(width / 2, height / 2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
  384. ctx.strokeStyle = config.scaleLineColor;
  385. ctx.lineWidth = config.scaleLineWidth;
  386. ctx.stroke();
  387. }
  388. if (config.scaleShowLabels) {
  389. ctx.textAlign = "center";
  390. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  391. var label = calculatedScale.labels[i];
  392. //If the backdrop object is within the font object
  393. if (config.scaleShowLabelBackdrop) {
  394. var textWidth = ctx.measureText(label).width;
  395. ctx.fillStyle = config.scaleBackdropColor;
  396. ctx.beginPath();
  397. ctx.rect(
  398. Math.round(width / 2 - textWidth / 2 - config.scaleBackdropPaddingX), //X
  399. Math.round(height / 2 - (scaleHop * (i + 1)) - config.scaleFontSize * 0.5 - config.scaleBackdropPaddingY),//Y
  400. Math.round(textWidth + (config.scaleBackdropPaddingX * 2)), //Width
  401. Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY * 2)) //Height
  402. );
  403. ctx.fill();
  404. }
  405. ctx.textBaseline = "middle";
  406. ctx.fillStyle = config.scaleFontColor;
  407. ctx.fillText(label, width / 2, height / 2 - (scaleHop * (i + 1)));
  408. }
  409. }
  410. }
  411. function drawAllSegments(animationDecimal) {
  412. var startAngle = -Math.PI / 2,
  413. angleStep = (Math.PI * 2) / data.length,
  414. scaleAnimation = 1,
  415. rotateAnimation = 1;
  416. if (config.animation) {
  417. if (config.animateScale) {
  418. scaleAnimation = animationDecimal;
  419. }
  420. if (config.animateRotate) {
  421. rotateAnimation = animationDecimal;
  422. }
  423. }
  424. for (var i = 0; i < data.length; i++) {
  425. ctx.beginPath();
  426. ctx.arc(width / 2, height / 2, scaleAnimation * calculateOffset(data[i].value, calculatedScale, scaleHop), startAngle, startAngle + rotateAnimation * angleStep, false);
  427. ctx.lineTo(width / 2, height / 2);
  428. ctx.closePath();
  429. ctx.fillStyle = data[i].color;
  430. ctx.fill();
  431. if (config.segmentShowStroke) {
  432. ctx.strokeStyle = config.segmentStrokeColor;
  433. ctx.lineWidth = config.segmentStrokeWidth;
  434. ctx.stroke();
  435. }
  436. startAngle += rotateAnimation * angleStep;
  437. }
  438. }
  439. function getValueBounds() {
  440. var upperValue = Number.MIN_VALUE;
  441. var lowerValue = Number.MAX_VALUE;
  442. for (var i = 0; i < data.length; i++) {
  443. if (data[i].value > upperValue) {
  444. upperValue = data[i].value;
  445. }
  446. if (data[i].value < lowerValue) {
  447. lowerValue = data[i].value;
  448. }
  449. }
  450. ;
  451. var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66)));
  452. var minSteps = Math.floor((scaleHeight / labelHeight * 0.5));
  453. return {
  454. maxValue: upperValue,
  455. minValue: lowerValue,
  456. maxSteps: maxSteps,
  457. minSteps: minSteps
  458. };
  459. }
  460. }
  461. var Radar = function (data, config, ctx) {
  462. var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;
  463. //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
  464. if (!data.labels) data.labels = [];
  465. calculateDrawingSizes();
  466. var valueBounds = getValueBounds();
  467. labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : null;
  468. //Check and set the scale
  469. if (!config.scaleOverride) {
  470. calculatedScale = calculateScale(scaleHeight, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString);
  471. } else {
  472. calculatedScale = {
  473. steps: config.scaleSteps,
  474. stepValue: config.scaleStepWidth,
  475. graphMin: config.scaleStartValue,
  476. labels: []
  477. }
  478. populateLabels(labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, config.scaleStepWidth);
  479. }
  480. scaleHop = maxSize / (calculatedScale.steps);
  481. animationLoop(config, drawScale, drawAllDataPoints, ctx);
  482. //Radar specific functions.
  483. function drawAllDataPoints(animationDecimal) {
  484. var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length;
  485. ctx.save();
  486. //translate to the centre of the canvas.
  487. ctx.translate(width / 2, height / 2);
  488. //We accept multiple data sets for radar charts, so show loop through each set
  489. for (var i = 0; i < data.datasets.length; i++) {
  490. ctx.beginPath();
  491. ctx.moveTo(0, animationDecimal * (-1 * calculateOffset(data.datasets[i].data[0], calculatedScale, scaleHop)));
  492. for (var j = 1; j < data.datasets[i].data.length; j++) {
  493. ctx.rotate(rotationDegree);
  494. ctx.lineTo(0, animationDecimal * (-1 * calculateOffset(data.datasets[i].data[j], calculatedScale, scaleHop)));
  495. }
  496. ctx.closePath();
  497. ctx.fillStyle = data.datasets[i].fillColor;
  498. ctx.strokeStyle = data.datasets[i].strokeColor;
  499. ctx.lineWidth = config.datasetStrokeWidth;
  500. ctx.fill();
  501. ctx.stroke();
  502. if (config.pointDot) {
  503. ctx.fillStyle = data.datasets[i].pointColor;
  504. ctx.strokeStyle = data.datasets[i].pointStrokeColor;
  505. ctx.lineWidth = config.pointDotStrokeWidth;
  506. for (var k = 0; k < data.datasets[i].data.length; k++) {
  507. ctx.rotate(rotationDegree);
  508. ctx.beginPath();
  509. ctx.arc(0, animationDecimal * (-1 * calculateOffset(data.datasets[i].data[k], calculatedScale, scaleHop)), config.pointDotRadius, 2 * Math.PI, false);
  510. ctx.fill();
  511. ctx.stroke();
  512. }
  513. }
  514. ctx.rotate(rotationDegree);
  515. }
  516. ctx.restore();
  517. }
  518. function drawScale() {
  519. var rotationDegree = (2 * Math.PI) / data.datasets[0].data.length;
  520. ctx.save();
  521. ctx.translate(width / 2, height / 2);
  522. if (config.angleShowLineOut) {
  523. ctx.strokeStyle = config.angleLineColor;
  524. ctx.lineWidth = config.angleLineWidth;
  525. for (var h = 0; h < data.datasets[0].data.length; h++) {
  526. ctx.rotate(rotationDegree);
  527. ctx.beginPath();
  528. ctx.moveTo(0, 0);
  529. ctx.lineTo(0, -maxSize);
  530. ctx.stroke();
  531. }
  532. }
  533. for (var i = 0; i < calculatedScale.steps; i++) {
  534. ctx.beginPath();
  535. if (config.scaleShowLine) {
  536. ctx.strokeStyle = config.scaleLineColor;
  537. ctx.lineWidth = config.scaleLineWidth;
  538. ctx.moveTo(0, -scaleHop * (i + 1));
  539. for (var j = 0; j < data.datasets[0].data.length; j++) {
  540. ctx.rotate(rotationDegree);
  541. ctx.lineTo(0, -scaleHop * (i + 1));
  542. }
  543. ctx.closePath();
  544. ctx.stroke();
  545. }
  546. if (config.scaleShowLabels) {
  547. ctx.textAlign = 'center';
  548. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  549. ctx.textBaseline = "middle";
  550. if (config.scaleShowLabelBackdrop) {
  551. var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
  552. ctx.fillStyle = config.scaleBackdropColor;
  553. ctx.beginPath();
  554. ctx.rect(
  555. Math.round(-textWidth / 2 - config.scaleBackdropPaddingX), //X
  556. Math.round((-scaleHop * (i + 1)) - config.scaleFontSize * 0.5 - config.scaleBackdropPaddingY),//Y
  557. Math.round(textWidth + (config.scaleBackdropPaddingX * 2)), //Width
  558. Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY * 2)) //Height
  559. );
  560. ctx.fill();
  561. }
  562. ctx.fillStyle = config.scaleFontColor;
  563. ctx.fillText(calculatedScale.labels[i], 0, -scaleHop * (i + 1));
  564. }
  565. }
  566. for (var k = 0; k < data.labels.length; k++) {
  567. ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize + "px " + config.pointLabelFontFamily;
  568. ctx.fillStyle = config.pointLabelFontColor;
  569. var opposite = Math.sin(rotationDegree * k) * (maxSize + config.pointLabelFontSize);
  570. var adjacent = Math.cos(rotationDegree * k) * (maxSize + config.pointLabelFontSize);
  571. if (rotationDegree * k == Math.PI || rotationDegree * k == 0) {
  572. ctx.textAlign = "center";
  573. } else if (rotationDegree * k > Math.PI) {
  574. ctx.textAlign = "right";
  575. } else {
  576. ctx.textAlign = "left";
  577. }
  578. ctx.textBaseline = "middle";
  579. ctx.fillText(data.labels[k], opposite, -adjacent);
  580. }
  581. ctx.restore();
  582. };
  583. function calculateDrawingSizes() {
  584. maxSize = (Min([width, height]) / 2);
  585. labelHeight = config.scaleFontSize * 2;
  586. var labelLength = 0;
  587. for (var i = 0; i < data.labels.length; i++) {
  588. ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize + "px " + config.pointLabelFontFamily;
  589. var textMeasurement = ctx.measureText(data.labels[i]).width;
  590. if (textMeasurement > labelLength) labelLength = textMeasurement;
  591. }
  592. //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
  593. maxSize -= Max([labelLength, ((config.pointLabelFontSize / 2) * 1.5)]);
  594. maxSize -= config.pointLabelFontSize;
  595. maxSize = CapValue(maxSize, null, 0);
  596. scaleHeight = maxSize;
  597. //If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
  598. labelHeight = Default(labelHeight, 5);
  599. };
  600. function getValueBounds() {
  601. var upperValue = Number.MIN_VALUE;
  602. var lowerValue = Number.MAX_VALUE;
  603. for (var i = 0; i < data.datasets.length; i++) {
  604. for (var j = 0; j < data.datasets[i].data.length; j++) {
  605. if (data.datasets[i].data[j] > upperValue) {
  606. upperValue = data.datasets[i].data[j]
  607. }
  608. if (data.datasets[i].data[j] < lowerValue) {
  609. lowerValue = data.datasets[i].data[j]
  610. }
  611. }
  612. }
  613. var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66)));
  614. var minSteps = Math.floor((scaleHeight / labelHeight * 0.5));
  615. return {
  616. maxValue: upperValue,
  617. minValue: lowerValue,
  618. maxSteps: maxSteps,
  619. minSteps: minSteps
  620. };
  621. }
  622. }
  623. var Pie = function (data, config, ctx) {
  624. var segmentTotal = 0;
  625. //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
  626. var pieRadius = Min([height / 2, width / 2]) - 5;
  627. for (var i = 0; i < data.length; i++) {
  628. segmentTotal += data[i].value;
  629. }
  630. animationLoop(config, null, drawPieSegments, ctx);
  631. function drawPieSegments(animationDecimal) {
  632. var cumulativeAngle = -Math.PI / 2,
  633. scaleAnimation = 1,
  634. rotateAnimation = 1;
  635. if (config.animation) {
  636. if (config.animateScale) {
  637. scaleAnimation = animationDecimal;
  638. }
  639. if (config.animateRotate) {
  640. rotateAnimation = animationDecimal;
  641. }
  642. }
  643. for (var i = 0; i < data.length; i++) {
  644. var segmentAngle = rotateAnimation * ((data[i].value / segmentTotal) * (Math.PI * 2));
  645. ctx.beginPath();
  646. ctx.arc(width / 2, height / 2, scaleAnimation * pieRadius, cumulativeAngle, cumulativeAngle + segmentAngle);
  647. ctx.lineTo(width / 2, height / 2);
  648. ctx.closePath();
  649. ctx.fillStyle = data[i].color;
  650. ctx.fill();
  651. if (config.segmentShowStroke) {
  652. ctx.lineWidth = config.segmentStrokeWidth;
  653. ctx.strokeStyle = config.segmentStrokeColor;
  654. ctx.stroke();
  655. }
  656. cumulativeAngle += segmentAngle;
  657. }
  658. }
  659. }
  660. var Doughnut = function (data, config, ctx) {
  661. var segmentTotal = 0;
  662. //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
  663. var doughnutRadius = Min([height / 2, width / 2]) - 5;
  664. var cutoutRadius = doughnutRadius * (config.percentageInnerCutout / 100);
  665. for (var i = 0; i < data.length; i++) {
  666. segmentTotal += data[i].value;
  667. }
  668. animationLoop(config, null, drawPieSegments, ctx);
  669. function drawPieSegments(animationDecimal) {
  670. var cumulativeAngle = -Math.PI / 2,
  671. scaleAnimation = 1,
  672. rotateAnimation = 1;
  673. if (config.animation) {
  674. if (config.animateScale) {
  675. scaleAnimation = animationDecimal;
  676. }
  677. if (config.animateRotate) {
  678. rotateAnimation = animationDecimal;
  679. }
  680. }
  681. for (var i = 0; i < data.length; i++) {
  682. var segmentAngle = rotateAnimation * ((data[i].value / segmentTotal) * (Math.PI * 2));
  683. ctx.beginPath();
  684. ctx.arc(width / 2, height / 2, scaleAnimation * doughnutRadius, cumulativeAngle, cumulativeAngle + segmentAngle, false);
  685. ctx.arc(width / 2, height / 2, scaleAnimation * cutoutRadius, cumulativeAngle + segmentAngle, cumulativeAngle, true);
  686. ctx.closePath();
  687. ctx.fillStyle = data[i].color;
  688. ctx.fill();
  689. if (config.segmentShowStroke) {
  690. ctx.lineWidth = config.segmentStrokeWidth;
  691. ctx.strokeStyle = config.segmentStrokeColor;
  692. ctx.stroke();
  693. }
  694. cumulativeAngle += segmentAngle;
  695. }
  696. }
  697. }
  698. var Line = function (data, config, ctx) {
  699. var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,
  700. widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, rotateLabels = 0;
  701. calculateDrawingSizes();
  702. valueBounds = getValueBounds();
  703. //Check and set the scale
  704. labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : "";
  705. if (!config.scaleOverride) {
  706. calculatedScale = calculateScale(scaleHeight, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString);
  707. } else {
  708. calculatedScale = {
  709. steps: config.scaleSteps,
  710. stepValue: config.scaleStepWidth,
  711. graphMin: config.scaleStartValue,
  712. labels: []
  713. }
  714. populateLabels(labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, config.scaleStepWidth);
  715. }
  716. scaleHop = Math.floor(scaleHeight / calculatedScale.steps);
  717. calculateXAxisSize();
  718. animationLoop(config, drawScale, drawLines, ctx);
  719. function drawLines(animPc) {
  720. for (var i = 0; i < data.datasets.length; i++) {
  721. ctx.strokeStyle = data.datasets[i].strokeColor;
  722. ctx.lineWidth = config.datasetStrokeWidth;
  723. ctx.beginPath();
  724. ctx.moveTo(yAxisPosX, xAxisPosY - animPc * (calculateOffset(data.datasets[i].data[0], calculatedScale, scaleHop)))
  725. for (var j = 1; j < data.datasets[i].data.length; j++) {
  726. if (config.bezierCurve) {
  727. ctx.bezierCurveTo(xPos(j - 0.5), yPos(i, j - 1), xPos(j - 0.5), yPos(i, j), xPos(j), yPos(i, j));
  728. } else {
  729. ctx.lineTo(xPos(j), yPos(i, j));
  730. }
  731. }
  732. ctx.stroke();
  733. if (config.datasetFill) {
  734. ctx.lineTo(yAxisPosX + (valueHop * (data.datasets[i].data.length - 1)), xAxisPosY);
  735. ctx.lineTo(yAxisPosX, xAxisPosY);
  736. ctx.closePath();
  737. ctx.fillStyle = data.datasets[i].fillColor;
  738. ctx.fill();
  739. } else {
  740. ctx.closePath();
  741. }
  742. if (config.pointDot) {
  743. ctx.fillStyle = data.datasets[i].pointColor;
  744. ctx.strokeStyle = data.datasets[i].pointStrokeColor;
  745. ctx.lineWidth = config.pointDotStrokeWidth;
  746. for (var k = 0; k < data.datasets[i].data.length; k++) {
  747. ctx.beginPath();
  748. ctx.arc(yAxisPosX + (valueHop * k), xAxisPosY - animPc * (calculateOffset(data.datasets[i].data[k], calculatedScale, scaleHop)), config.pointDotRadius, 0, Math.PI * 2, true);
  749. ctx.fill();
  750. ctx.stroke();
  751. }
  752. }
  753. }
  754. function yPos(dataSet, iteration) {
  755. return xAxisPosY - animPc * (calculateOffset(data.datasets[dataSet].data[iteration], calculatedScale, scaleHop));
  756. }
  757. function xPos(iteration) {
  758. return yAxisPosX + (valueHop * iteration);
  759. }
  760. }
  761. function drawScale() {
  762. //X axis line
  763. ctx.lineWidth = config.scaleLineWidth;
  764. ctx.strokeStyle = config.scaleLineColor;
  765. ctx.beginPath();
  766. ctx.moveTo(width - widestXLabel / 2 + 5, xAxisPosY);
  767. ctx.lineTo(width - (widestXLabel / 2) - xAxisLength - 5, xAxisPosY);
  768. ctx.stroke();
  769. if (rotateLabels > 0) {
  770. ctx.save();
  771. ctx.textAlign = "right";
  772. } else {
  773. ctx.textAlign = "center";
  774. }
  775. ctx.fillStyle = config.scaleFontColor;
  776. for (var i = 0; i < data.labels.length; i++) {
  777. ctx.save();
  778. if (rotateLabels > 0) {
  779. ctx.translate(yAxisPosX + i * valueHop, xAxisPosY + config.scaleFontSize);
  780. ctx.rotate(-(rotateLabels * (Math.PI / 180)));
  781. ctx.fillText(data.labels[i], 0, 0);
  782. ctx.restore();
  783. } else {
  784. ctx.fillText(data.labels[i], yAxisPosX + i * valueHop, xAxisPosY + config.scaleFontSize + 3);
  785. }
  786. ctx.beginPath();
  787. ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY + 3);
  788. //Check i isnt 0, so we dont go over the Y axis twice.
  789. if (config.scaleShowGridLines && i > 0) {
  790. ctx.lineWidth = config.scaleGridLineWidth;
  791. ctx.strokeStyle = config.scaleGridLineColor;
  792. ctx.lineTo(yAxisPosX + i * valueHop, 5);
  793. } else {
  794. ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY + 3);
  795. }
  796. ctx.stroke();
  797. }
  798. //Y axis
  799. ctx.lineWidth = config.scaleLineWidth;
  800. ctx.strokeStyle = config.scaleLineColor;
  801. ctx.beginPath();
  802. ctx.moveTo(yAxisPosX, xAxisPosY + 5);
  803. ctx.lineTo(yAxisPosX, 5);
  804. ctx.stroke();
  805. ctx.textAlign = "right";
  806. ctx.textBaseline = "middle";
  807. for (var j = 0; j < calculatedScale.steps; j++) {
  808. ctx.beginPath();
  809. ctx.moveTo(yAxisPosX - 3, xAxisPosY - ((j + 1) * scaleHop));
  810. if (config.scaleShowGridLines) {
  811. ctx.lineWidth = config.scaleGridLineWidth;
  812. ctx.strokeStyle = config.scaleGridLineColor;
  813. ctx.lineTo(yAxisPosX + xAxisLength + 5, xAxisPosY - ((j + 1) * scaleHop));
  814. } else {
  815. ctx.lineTo(yAxisPosX - 0.5, xAxisPosY - ((j + 1) * scaleHop));
  816. }
  817. ctx.stroke();
  818. if (config.scaleShowLabels) {
  819. ctx.fillText(calculatedScale.labels[j], yAxisPosX - 8, xAxisPosY - ((j + 1) * scaleHop));
  820. }
  821. }
  822. }
  823. function calculateXAxisSize() {
  824. var longestText = 1;
  825. //if we are showing the labels
  826. if (config.scaleShowLabels) {
  827. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  828. for (var i = 0; i < calculatedScale.labels.length; i++) {
  829. var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
  830. longestText = (measuredText > longestText) ? measuredText : longestText;
  831. }
  832. //Add a little extra padding from the y axis
  833. longestText += 10;
  834. }
  835. xAxisLength = width - longestText - widestXLabel;
  836. valueHop = Math.floor(xAxisLength / (data.labels.length - 1));
  837. yAxisPosX = width - widestXLabel / 2 - xAxisLength;
  838. xAxisPosY = scaleHeight + config.scaleFontSize / 2;
  839. }
  840. function calculateDrawingSizes() {
  841. maxSize = height;
  842. //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
  843. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  844. widestXLabel = 1;
  845. for (var i = 0; i < data.labels.length; i++) {
  846. var textLength = ctx.measureText(data.labels[i]).width;
  847. //If the text length is longer - make that equal to longest text!
  848. widestXLabel = (textLength > widestXLabel) ? textLength : widestXLabel;
  849. }
  850. if (width / data.labels.length < widestXLabel) {
  851. rotateLabels = 45;
  852. if (width / data.labels.length < Math.cos(rotateLabels) * widestXLabel) {
  853. rotateLabels = 90;
  854. maxSize -= widestXLabel;
  855. } else {
  856. maxSize -= Math.sin(rotateLabels) * widestXLabel;
  857. }
  858. } else {
  859. maxSize -= config.scaleFontSize;
  860. }
  861. //Add a little padding between the x line and the text
  862. maxSize -= 5;
  863. labelHeight = config.scaleFontSize;
  864. maxSize -= labelHeight;
  865. //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
  866. scaleHeight = maxSize;
  867. //Then get the area above we can safely draw on.
  868. }
  869. function getValueBounds() {
  870. var upperValue = Number.MIN_VALUE;
  871. var lowerValue = Number.MAX_VALUE;
  872. for (var i = 0; i < data.datasets.length; i++) {
  873. for (var j = 0; j < data.datasets[i].data.length; j++) {
  874. if (data.datasets[i].data[j] > upperValue) {
  875. upperValue = data.datasets[i].data[j]
  876. }
  877. ;
  878. if (data.datasets[i].data[j] < lowerValue) {
  879. lowerValue = data.datasets[i].data[j]
  880. }
  881. ;
  882. }
  883. }
  884. ;
  885. var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66)));
  886. var minSteps = Math.floor((scaleHeight / labelHeight * 0.5));
  887. return {
  888. maxValue: upperValue,
  889. minValue: lowerValue,
  890. maxSteps: maxSteps,
  891. minSteps: minSteps
  892. };
  893. }
  894. }
  895. var Bar = function (data, config, ctx) {
  896. var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,
  897. widestXLabel, xAxisLength, yAxisPosX, xAxisPosY, barWidth, rotateLabels = 0;
  898. calculateDrawingSizes();
  899. valueBounds = getValueBounds();
  900. //Check and set the scale
  901. labelTemplateString = (config.scaleShowLabels) ? config.scaleLabel : "";
  902. if (!config.scaleOverride) {
  903. calculatedScale = calculateScale(scaleHeight, valueBounds.maxSteps, valueBounds.minSteps, valueBounds.maxValue, valueBounds.minValue, labelTemplateString);
  904. } else {
  905. calculatedScale = {
  906. steps: config.scaleSteps,
  907. stepValue: config.scaleStepWidth,
  908. graphMin: config.scaleStartValue,
  909. labels: []
  910. }
  911. populateLabels(labelTemplateString, calculatedScale.labels, calculatedScale.steps, config.scaleStartValue, config.scaleStepWidth);
  912. }
  913. scaleHop = Math.floor(scaleHeight / calculatedScale.steps);
  914. calculateXAxisSize();
  915. animationLoop(config, drawScale, drawBars, ctx);
  916. function drawBars(animPc) {
  917. ctx.lineWidth = config.barStrokeWidth;
  918. for (var i = 0; i < data.datasets.length; i++) {
  919. ctx.fillStyle = data.datasets[i].fillColor;
  920. ctx.strokeStyle = data.datasets[i].strokeColor;
  921. for (var j = 0; j < data.datasets[i].data.length; j++) {
  922. var barOffset = yAxisPosX + config.barValueSpacing + valueHop * j + barWidth * i + config.barDatasetSpacing * i + config.barStrokeWidth * i;
  923. ctx.beginPath();
  924. ctx.moveTo(barOffset, xAxisPosY);
  925. ctx.lineTo(barOffset, xAxisPosY - animPc * calculateOffset(data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2));
  926. ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc * calculateOffset(data.datasets[i].data[j], calculatedScale, scaleHop) + (config.barStrokeWidth / 2));
  927. ctx.lineTo(barOffset + barWidth, xAxisPosY);
  928. if (config.barShowStroke) {
  929. ctx.stroke();
  930. }
  931. ctx.closePath();
  932. ctx.fill();
  933. }
  934. }
  935. }
  936. function drawScale() {
  937. //X axis line
  938. ctx.lineWidth = config.scaleLineWidth;
  939. ctx.strokeStyle = config.scaleLineColor;
  940. ctx.beginPath();
  941. ctx.moveTo(width - widestXLabel / 2 + 5, xAxisPosY);
  942. ctx.lineTo(width - (widestXLabel / 2) - xAxisLength - 5, xAxisPosY);
  943. ctx.stroke();
  944. if (rotateLabels > 0) {
  945. ctx.save();
  946. ctx.textAlign = "right";
  947. } else {
  948. ctx.textAlign = "center";
  949. }
  950. ctx.fillStyle = config.scaleFontColor;
  951. for (var i = 0; i < data.labels.length; i++) {
  952. ctx.save();
  953. if (rotateLabels > 0) {
  954. ctx.translate(yAxisPosX + i * valueHop, xAxisPosY + config.scaleFontSize);
  955. ctx.rotate(-(rotateLabels * (Math.PI / 180)));
  956. ctx.fillText(data.labels[i], 0, 0);
  957. ctx.restore();
  958. } else {
  959. ctx.fillText(data.labels[i], yAxisPosX + i * valueHop + valueHop / 2, xAxisPosY + config.scaleFontSize + 3);
  960. }
  961. ctx.beginPath();
  962. ctx.moveTo(yAxisPosX + (i + 1) * valueHop, xAxisPosY + 3);
  963. //Check i isnt 0, so we dont go over the Y axis twice.
  964. ctx.lineWidth = config.scaleGridLineWidth;
  965. ctx.strokeStyle = config.scaleGridLineColor;
  966. ctx.lineTo(yAxisPosX + (i + 1) * valueHop, 5);
  967. ctx.stroke();
  968. }
  969. //Y axis
  970. ctx.lineWidth = config.scaleLineWidth;
  971. ctx.strokeStyle = config.scaleLineColor;
  972. ctx.beginPath();
  973. ctx.moveTo(yAxisPosX, xAxisPosY + 5);
  974. ctx.lineTo(yAxisPosX, 5);
  975. ctx.stroke();
  976. ctx.textAlign = "right";
  977. ctx.textBaseline = "middle";
  978. for (var j = 0; j < calculatedScale.steps; j++) {
  979. ctx.beginPath();
  980. ctx.moveTo(yAxisPosX - 3, xAxisPosY - ((j + 1) * scaleHop));
  981. if (config.scaleShowGridLines) {
  982. ctx.lineWidth = config.scaleGridLineWidth;
  983. ctx.strokeStyle = config.scaleGridLineColor;
  984. ctx.lineTo(yAxisPosX + xAxisLength + 5, xAxisPosY - ((j + 1) * scaleHop));
  985. } else {
  986. ctx.lineTo(yAxisPosX - 0.5, xAxisPosY - ((j + 1) * scaleHop));
  987. }
  988. ctx.stroke();
  989. if (config.scaleShowLabels) {
  990. ctx.fillText(calculatedScale.labels[j], yAxisPosX - 8, xAxisPosY - ((j + 1) * scaleHop));
  991. }
  992. }
  993. }
  994. function calculateXAxisSize() {
  995. var longestText = 1;
  996. //if we are showing the labels
  997. if (config.scaleShowLabels) {
  998. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  999. for (var i = 0; i < calculatedScale.labels.length; i++) {
  1000. var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
  1001. longestText = (measuredText > longestText) ? measuredText : longestText;
  1002. }
  1003. //Add a little extra padding from the y axis
  1004. longestText += 10;
  1005. }
  1006. xAxisLength = width - longestText - widestXLabel;
  1007. valueHop = Math.floor(xAxisLength / (data.labels.length));
  1008. barWidth = (valueHop - config.scaleGridLineWidth * 2 - (config.barValueSpacing * 2) - (config.barDatasetSpacing * data.datasets.length - 1) - ((config.barStrokeWidth / 2) * data.datasets.length - 1)) / data.datasets.length;
  1009. yAxisPosX = width - widestXLabel / 2 - xAxisLength;
  1010. xAxisPosY = scaleHeight + config.scaleFontSize / 2;
  1011. }
  1012. function calculateDrawingSizes() {
  1013. maxSize = height;
  1014. //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
  1015. ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
  1016. widestXLabel = 1;
  1017. for (var i = 0; i < data.labels.length; i++) {
  1018. var textLength = ctx.measureText(data.labels[i]).width;
  1019. //If the text length is longer - make that equal to longest text!
  1020. widestXLabel = (textLength > widestXLabel) ? textLength : widestXLabel;
  1021. }
  1022. if (width / data.labels.length < widestXLabel) {
  1023. rotateLabels = 45;
  1024. if (width / data.labels.length < Math.cos(rotateLabels) * widestXLabel) {
  1025. rotateLabels = 90;
  1026. maxSize -= widestXLabel;
  1027. } else {
  1028. maxSize -= Math.sin(rotateLabels) * widestXLabel;
  1029. }
  1030. } else {
  1031. maxSize -= config.scaleFontSize;
  1032. }
  1033. //Add a little padding between the x line and the text
  1034. maxSize -= 5;
  1035. labelHeight = config.scaleFontSize;
  1036. maxSize -= labelHeight;
  1037. //Set 5 pixels greater than the font size to allow for a little padding from the X axis.
  1038. scaleHeight = maxSize;
  1039. //Then get the area above we can safely draw on.
  1040. }
  1041. function getValueBounds() {
  1042. var upperValue = Number.MIN_VALUE;
  1043. var lowerValue = Number.MAX_VALUE;
  1044. for (var i = 0; i < data.datasets.length; i++) {
  1045. for (var j = 0; j < data.datasets[i].data.length; j++) {
  1046. if (data.datasets[i].data[j] > upperValue) {
  1047. upperValue = data.datasets[i].data[j]
  1048. }
  1049. ;
  1050. if (data.datasets[i].data[j] < lowerValue) {
  1051. lowerValue = data.datasets[i].data[j]
  1052. }
  1053. ;
  1054. }
  1055. }
  1056. ;
  1057. var maxSteps = Math.floor((scaleHeight / (labelHeight * 0.66)));
  1058. var minSteps = Math.floor((scaleHeight / labelHeight * 0.5));
  1059. return {
  1060. maxValue: upperValue,
  1061. minValue: lowerValue,
  1062. maxSteps: maxSteps,
  1063. minSteps: minSteps
  1064. };
  1065. }
  1066. }
  1067. function calculateOffset(val, calculatedScale, scaleHop) {
  1068. var outerValue = calculatedScale.steps * calculatedScale.stepValue;
  1069. var adjustedValue = val - calculatedScale.graphMin;
  1070. var scalingFactor = CapValue(adjustedValue / outerValue, 1, 0);
  1071. return (scaleHop * calculatedScale.steps) * scalingFactor;
  1072. }
  1073. function animationLoop(config, drawScale, drawData, ctx) {
  1074. var animFrameAmount = (config.animation) ? 1 / CapValue(config.animationSteps, Number.MAX_VALUE, 1) : 1,
  1075. easingFunction = animationOptions[config.animationEasing],
  1076. percentAnimComplete = (config.animation) ? 0 : 1;
  1077. if (typeof drawScale !== "function") drawScale = function () {
  1078. };
  1079. requestAnimFrame(animLoop);
  1080. function animateFrame() {
  1081. var easeAdjustedAnimationPercent = (config.animation) ? CapValue(easingFunction(percentAnimComplete), null, 0) : 1;
  1082. clear(ctx);
  1083. if (config.scaleOverlay) {
  1084. drawData(easeAdjustedAnimationPercent);
  1085. drawScale();
  1086. } else {
  1087. drawScale();
  1088. drawData(easeAdjustedAnimationPercent);
  1089. }
  1090. }
  1091. function animLoop() {
  1092. //We need to check if the animation is incomplete (less than 1), or complete (1).
  1093. percentAnimComplete += animFrameAmount;
  1094. animateFrame();
  1095. //Stop the loop continuing forever
  1096. if (percentAnimComplete <= 1) {
  1097. requestAnimFrame(animLoop);
  1098. } else {
  1099. if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
  1100. }
  1101. }
  1102. }
  1103. //Declare global functions to be called within this namespace here.
  1104. // shim layer with setTimeout fallback
  1105. var requestAnimFrame = (function () {
  1106. return window.requestAnimationFrame ||
  1107. window.webkitRequestAnimationFrame ||
  1108. window.mozRequestAnimationFrame ||
  1109. window.oRequestAnimationFrame ||
  1110. window.msRequestAnimationFrame ||
  1111. function (callback) {
  1112. window.setTimeout(callback, 1000 / 60);
  1113. };
  1114. })();
  1115. function calculateScale(drawingHeight, maxSteps, minSteps, maxValue, minValue, labelTemplateString) {
  1116. var graphMin, graphMax, graphRange, stepValue, numberOfSteps, valueRange, rangeOrderOfMagnitude, decimalNum;
  1117. valueRange = maxValue - minValue;
  1118. rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
  1119. graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
  1120. graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
  1121. graphRange = graphMax - graphMin;
  1122. stepValue = Math.pow(10, rangeOrderOfMagnitude);
  1123. numberOfSteps = Math.round(graphRange / stepValue);
  1124. //Compare number of steps to the max and min for that size graph, and add in half steps if need be.
  1125. while (numberOfSteps < minSteps || numberOfSteps > maxSteps) {
  1126. if (numberOfSteps < minSteps) {
  1127. stepValue /= 2;
  1128. numberOfSteps = Math.round(graphRange / stepValue);
  1129. } else {
  1130. stepValue *= 2;
  1131. numberOfSteps = Math.round(graphRange / stepValue);
  1132. }
  1133. }
  1134. ;
  1135. var labels = [];
  1136. populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue);
  1137. return {
  1138. steps: numberOfSteps,
  1139. stepValue: stepValue,
  1140. graphMin: graphMin,
  1141. labels: labels
  1142. }
  1143. function calculateOrderOfMagnitude(val) {
  1144. return Math.floor(Math.log(val) / Math.LN10);
  1145. }
  1146. }
  1147. //Populate an array of all the labels by interpolating the string.
  1148. function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) {
  1149. if (labelTemplateString) {
  1150. //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
  1151. for (var i = 1; i < numberOfSteps + 1; i++) {
  1152. labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))}));
  1153. }
  1154. }
  1155. }
  1156. //Max value from array
  1157. function Max(array) {
  1158. return Math.max.apply(Math, array);
  1159. };
  1160. //Min value from array
  1161. function Min(array) {
  1162. return Math.min.apply(Math, array);
  1163. };
  1164. //Default if undefined
  1165. function Default(userDeclared, valueIfFalse) {
  1166. if (!userDeclared) {
  1167. return valueIfFalse;
  1168. } else {
  1169. return userDeclared;
  1170. }
  1171. };
  1172. //Is a number function
  1173. function isNumber(n) {
  1174. return !isNaN(parseFloat(n)) && isFinite(n);
  1175. }
  1176. //Apply cap a value at a high or low number
  1177. function CapValue(valueToCap, maxValue, minValue) {
  1178. if (isNumber(maxValue)) {
  1179. if (valueToCap > maxValue) {
  1180. return maxValue;
  1181. }
  1182. }
  1183. if (isNumber(minValue)) {
  1184. if (valueToCap < minValue) {
  1185. return minValue;
  1186. }
  1187. }
  1188. return valueToCap;
  1189. }
  1190. function getDecimalPlaces(num) {
  1191. var numberOfDecimalPlaces;
  1192. if (num % 1 != 0) {
  1193. return num.toString().split(".")[1].length
  1194. } else {
  1195. return 0;
  1196. }
  1197. }
  1198. function mergeChartConfig(defaults, userDefined) {
  1199. var returnObj = {};
  1200. for (var attrname in defaults) {
  1201. returnObj[attrname] = defaults[attrname];
  1202. }
  1203. for (var attrname in userDefined) {
  1204. returnObj[attrname] = userDefined[attrname];
  1205. }
  1206. return returnObj;
  1207. }
  1208. //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
  1209. var cache = {};
  1210. function tmpl(str, data) {
  1211. // Figure out if we're getting a template, or if we need to
  1212. // load the template - and be sure to cache the result.
  1213. var fn = !/\W/.test(str) ?
  1214. cache[str] = cache[str] ||
  1215. tmpl(document.getElementById(str).innerHTML) :
  1216. // Generate a reusable function that will serve as a template
  1217. // generator (and which will be cached).
  1218. new Function("obj",
  1219. "var p=[],print=function(){p.push.apply(p,arguments);};" +
  1220. // Introduce the data as local variables using with(){}
  1221. "with(obj){p.push('" +
  1222. // Convert the template into pure JavaScript
  1223. str
  1224. .replace(/[\r\t\n]/g, " ")
  1225. .split("<%").join("\t")
  1226. .replace(/((^|%>)[^\t]*)'/g, "$1\r")
  1227. .replace(/\t=(.*?)%>/g, "',$1,'")
  1228. .split("\t").join("');")
  1229. .split("%>").join("p.push('")
  1230. .split("\r").join("\\'")
  1231. + "');}return p.join('');");
  1232. // Provide some basic currying to the user
  1233. return data ? fn(data) : fn;
  1234. };
  1235. }