├── Chart.js
├── QChartJs.qml
├── QChartJsTypes.js
├── QMLChartData.js
├── QMLChartJS.qmlproject
├── README.md
├── main.qml
└── screenshot
└── screenshot.png
/Chart.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Chart.js
3 | * http://chartjs.org/
4 | * Version: 1.0.1-beta.4
5 | *
6 | * Copyright 2014 Nick Downie
7 | * Released under the MIT license
8 | * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
9 | */
10 |
11 | //Adapter by Shuirna, incorporate it to QML canvas
12 | //NOTE: This modification are inspired by QChart.js(Author: Julien Wintz)
13 | //I'm using the newest version of Chart.js(This version is a huge change compared to the previous version
14 | //SO it has all the new features such as Tooltips and future extension
15 | //Some import Modification:
16 | // 1. replace aninmation with QML's property animation
17 | // 2. bind mouse event with QML's mouseArea
18 | // 3. replace window object with a global variable(root) for QML
19 |
20 | //Imitiate window object in the browser
21 | var root;
22 |
23 | function newChartInstance(context, data, options, chartType) {
24 | var ChartInstance = root;
25 | if (chartType === "Bar") {
26 | return new ChartInstance(context).Bar(data, options);
27 | } else if (chartType === "Line") {
28 | return new ChartInstance(context).Line(data, options);
29 | } else if (chartType === "Doughnut") {
30 | return new ChartInstance(context).Doughnut(data, options);
31 | } else if (chartType === "Pie") {
32 | return new ChartInstance(context).Pie(data, options);
33 | } else if (chartType === "PolarArea") {
34 | return new ChartInstance(context).PolarArea(data, options);
35 | } else if (chartType === "Radar") {
36 | return new ChartInstance(context).Radar(data, options);
37 | } else if (chartType === "StackedBar") {
38 | return new ChartInstance(context).StackedBar(data, options);
39 | } else {
40 | console.error("No supported Chart Type..Welcome to extend...Or check Chart.js for more extension!");
41 | }
42 | }
43 |
44 | //Chart Core
45 | (function() {
46 |
47 | // "use strict";
48 |
49 | //Declare root variable - window in the browser, global on the server
50 | // var root = this,
51 | // previous = root.Chart;
52 |
53 | //Occupy the global variable of Chart, and create a simple base class
54 | var Chart = function(context) {
55 | var chart = this;
56 | this.canvas = context.canvas;
57 |
58 | this.ctx = context;
59 |
60 | //Variables global to the chart
61 | var width = this.width = context.canvas.width;
62 | var height = this.height = context.canvas.height;
63 | this.aspectRatio = this.width / this.height;
64 | //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
65 | helpers.retinaScale(this);
66 |
67 | return this;
68 | };
69 | //Globally expose the defaults to allow for user updating/changing
70 | Chart.defaults = {
71 | global: {
72 | // Boolean - Whether to animate the chart
73 | animation: true,
74 |
75 | // Number - Number of animation steps
76 | animationSteps: 60,
77 |
78 | // String - Animation easing effect
79 | animationEasing: "easeOutQuart",
80 |
81 | // Boolean - If we should show the scale at all
82 | showScale: true,
83 |
84 | // Boolean - If we want to override with a hard coded scale
85 | scaleOverride: false,
86 |
87 | // ** Required if scaleOverride is true **
88 | // Number - The number of steps in a hard coded scale
89 | scaleSteps: null,
90 | // Number - The value jump in the hard coded scale
91 | scaleStepWidth: null,
92 | // Number - The scale starting value
93 | scaleStartValue: null,
94 |
95 | // String - Colour of the scale line
96 | scaleLineColor: "rgba(0,0,0,.1)",
97 |
98 | // Number - Pixel width of the scale line
99 | scaleLineWidth: 1,
100 |
101 | // Boolean - Whether to show labels on the scale
102 | scaleShowLabels: true,
103 |
104 | // Interpolated JS string - can access value
105 | scaleLabel: "<%=value%>",
106 |
107 | // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
108 | scaleIntegersOnly: true,
109 |
110 | // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
111 | scaleBeginAtZero: false,
112 |
113 | // String - Scale label font declaration for the scale label
114 | scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
115 |
116 | // Number - Scale label font size in pixels
117 | scaleFontSize: 12,
118 |
119 | // String - Scale label font weight style
120 | scaleFontStyle: "normal",
121 |
122 | // String - Scale label font colour
123 | scaleFontColor: "#666",
124 |
125 | // Boolean - whether or not the chart should be responsive and resize when the browser does.
126 | responsive: false,
127 |
128 | // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
129 | maintainAspectRatio: true,
130 |
131 | // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
132 | showTooltips: true,
133 |
134 | // Array - Array of string names to attach tooltip events
135 | tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
136 |
137 | // String - Tooltip background colour
138 | tooltipFillColor: "rgba(0,0,0,0.8)",
139 |
140 | // String - Tooltip label font declaration for the scale label
141 | tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
142 |
143 | // Number - Tooltip label font size in pixels
144 | tooltipFontSize: 14,
145 |
146 | // String - Tooltip font weight style
147 | tooltipFontStyle: "normal",
148 |
149 | // String - Tooltip label font colour
150 | tooltipFontColor: "#fff",
151 |
152 | // String - Tooltip title font declaration for the scale label
153 | tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
154 |
155 | // Number - Tooltip title font size in pixels
156 | tooltipTitleFontSize: 14,
157 |
158 | // String - Tooltip title font weight style
159 | tooltipTitleFontStyle: "bold",
160 |
161 | // String - Tooltip title font colour
162 | tooltipTitleFontColor: "#fff",
163 |
164 | // Number - pixel width of padding around tooltip text
165 | tooltipYPadding: 6,
166 |
167 | // Number - pixel width of padding around tooltip text
168 | tooltipXPadding: 6,
169 |
170 | // Number - Size of the caret on the tooltip
171 | tooltipCaretSize: 8,
172 |
173 | // Number - Pixel radius of the tooltip border
174 | tooltipCornerRadius: 6,
175 |
176 | // Number - Pixel offset from point x to tooltip edge
177 | tooltipXOffset: 10,
178 |
179 | // String - Template string for single tooltips
180 | tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
181 |
182 | // String - Template string for single tooltips
183 | multiTooltipTemplate: "<%= value %>",
184 |
185 | // String - Colour behind the legend colour block
186 | multiTooltipKeyBackground: '#fff',
187 |
188 | // Function - Will fire on animation progression.
189 | onAnimationProgress: function() {},
190 |
191 | // Function - Will fire on animation completion.
192 | onAnimationComplete: function() {}
193 |
194 | }
195 | };
196 |
197 | //Create a dictionary of chart types, to allow for extension of existing types
198 | Chart.types = {};
199 |
200 | //Global Chart helpers object for utility methods and classes
201 | var helpers = Chart.helpers = {};
202 |
203 | //-- Basic js utility methods
204 | var each = helpers.each = function(loopable, callback, self) {
205 | var additionalArgs = Array.prototype.slice.call(arguments, 3);
206 | // Check to see if null or undefined firstly.
207 | if (loopable) {
208 | if (loopable.length === +loopable.length) {
209 | var i;
210 | for (i = 0; i < loopable.length; i++) {
211 | callback.apply(self, [loopable[i], i].concat(additionalArgs));
212 | }
213 | } else {
214 | for (var item in loopable) {
215 | callback.apply(self, [loopable[item], item].concat(additionalArgs));
216 | }
217 | }
218 | }
219 | },
220 | clone = helpers.clone = function(obj) {
221 | var objClone = {};
222 | each(obj, function(value, key) {
223 | if (obj.hasOwnProperty(key)) objClone[key] = value;
224 | });
225 | return objClone;
226 | },
227 | extend = helpers.extend = function(base) {
228 | each(Array.prototype.slice.call(arguments, 1), function(extensionObject) {
229 | each(extensionObject, function(value, key) {
230 | if (extensionObject.hasOwnProperty(key)) base[key] = value;
231 | });
232 | });
233 | return base;
234 | },
235 | merge = helpers.merge = function(base, master) {
236 | //Merge properties in left object over to a shallow clone of object right.
237 | var args = Array.prototype.slice.call(arguments, 0);
238 | args.unshift({});
239 | return extend.apply(null, args);
240 | },
241 | indexOf = helpers.indexOf = function(arrayToSearch, item) {
242 | if (Array.prototype.indexOf) {
243 | return arrayToSearch.indexOf(item);
244 | } else {
245 | for (var i = 0; i < arrayToSearch.length; i++) {
246 | if (arrayToSearch[i] === item) return i;
247 | }
248 | return -1;
249 | }
250 | },
251 | where = helpers.where = function(collection, filterCallback) {
252 | var filtered = [];
253 |
254 | helpers.each(collection, function(item) {
255 | if (filterCallback(item)) {
256 | filtered.push(item);
257 | }
258 | });
259 |
260 | return filtered;
261 | },
262 | findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
263 | // Default to start of the array
264 | if (!startIndex) {
265 | startIndex = -1;
266 | }
267 | for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
268 | var currentItem = arrayToSearch[i];
269 | if (filterCallback(currentItem)) {
270 | return currentItem;
271 | }
272 | };
273 | },
274 | findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
275 | // Default to end of the array
276 | if (!startIndex) {
277 | startIndex = arrayToSearch.length;
278 | }
279 | for (var i = startIndex - 1; i >= 0; i--) {
280 | var currentItem = arrayToSearch[i];
281 | if (filterCallback(currentItem)) {
282 | return currentItem;
283 | }
284 | };
285 | },
286 | inherits = helpers.inherits = function(extensions) {
287 | //Basic javascript inheritance based on the model created in Backbone.js
288 | var parent = this;
289 | var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() {
290 | return parent.apply(this, arguments);
291 | };
292 |
293 | var Surrogate = function() {
294 | this.constructor = ChartElement;
295 | };
296 | Surrogate.prototype = parent.prototype;
297 | ChartElement.prototype = new Surrogate();
298 |
299 | ChartElement.extend = inherits;
300 |
301 | if (extensions) extend(ChartElement.prototype, extensions);
302 |
303 | ChartElement.__super__ = parent.prototype;
304 |
305 | return ChartElement;
306 | },
307 | noop = helpers.noop = function() {},
308 | uid = helpers.uid = (function() {
309 | var id = 0;
310 | return function() {
311 | return "chart-" + id++;
312 | };
313 | })(),
314 | warn = helpers.warn = function(str) {
315 | //Method for warning of errors
316 | // if (window.console && typeof window.console.warn == "function") console.warn(str);
317 | },
318 | amd = helpers.amd = (typeof define == 'function' && define.amd),
319 | //-- Math methods
320 | isNumber = helpers.isNumber = function(n) {
321 | return !isNaN(parseFloat(n)) && isFinite(n);
322 | },
323 | max = helpers.max = function(array) {
324 | return Math.max.apply(Math, array);
325 | },
326 | min = helpers.min = function(array) {
327 | return Math.min.apply(Math, array);
328 | },
329 | cap = helpers.cap = function(valueToCap, maxValue, minValue) {
330 | if (isNumber(maxValue)) {
331 | if (valueToCap > maxValue) {
332 | return maxValue;
333 | }
334 | } else if (isNumber(minValue)) {
335 | if (valueToCap < minValue) {
336 | return minValue;
337 | }
338 | }
339 | return valueToCap;
340 | },
341 | getDecimalPlaces = helpers.getDecimalPlaces = function(num) {
342 | if (num % 1 !== 0 && isNumber(num)) {
343 | return num.toString().split(".")[1].length;
344 | } else {
345 | return 0;
346 | }
347 | },
348 | toRadians = helpers.radians = function(degrees) {
349 | return degrees * (Math.PI / 180);
350 | },
351 | // Gets the angle from vertical upright to the point about a centre.
352 | getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) {
353 | var distanceFromXCenter = anglePoint.x - centrePoint.x,
354 | distanceFromYCenter = anglePoint.y - centrePoint.y,
355 | radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
356 |
357 |
358 | var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
359 |
360 | //If the segment is in the top left quadrant, we need to add another rotation to the angle
361 | if (distanceFromXCenter < 0 && distanceFromYCenter < 0) {
362 | angle += Math.PI * 2;
363 | }
364 |
365 | return {
366 | angle: angle,
367 | distance: radialDistanceFromCenter
368 | };
369 | },
370 | aliasPixel = helpers.aliasPixel = function(pixelWidth) {
371 | return (pixelWidth % 2 === 0) ? 0 : 0.5;
372 | },
373 | splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) {
374 | //Props to Rob Spencer at scaled innovation for his post on splining between points
375 | //http://scaledinnovation.com/analytics/splines/aboutSplines.html
376 | var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)),
377 | d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)),
378 | fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta
379 | fb = t * d12 / (d01 + d12);
380 | return {
381 | inner: {
382 | x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x),
383 | y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y)
384 | },
385 | outer: {
386 | x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x),
387 | y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y)
388 | }
389 | };
390 | },
391 | calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) {
392 | return Math.floor(Math.log(val) / Math.LN10);
393 | },
394 | calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) {
395 |
396 | //Set a minimum step of two - a point at the top of the graph, and a point at the base
397 | var minSteps = 2,
398 | maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
399 | skipFitting = (minSteps >= maxSteps);
400 |
401 | var maxValue = max(valuesArray),
402 | minValue = min(valuesArray);
403 |
404 | // We need some degree of seperation here to calculate the scales if all the values are the same
405 | // Adding/minusing 0.5 will give us a range of 1.
406 | if (maxValue === minValue) {
407 | maxValue += 0.5;
408 | // So we don't end up with a graph with a negative start value if we've said always start from zero
409 | if (minValue >= 0.5 && !startFromZero) {
410 | minValue -= 0.5;
411 | } else {
412 | // Make up a whole number above the values
413 | maxValue += 0.5;
414 | }
415 | }
416 |
417 | var valueRange = Math.abs(maxValue - minValue),
418 | rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
419 | graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
420 | graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
421 | graphRange = graphMax - graphMin,
422 | stepValue = Math.pow(10, rangeOrderOfMagnitude),
423 | numberOfSteps = Math.round(graphRange / stepValue);
424 |
425 | //If we have more space on the graph we'll use it to give more definition to the data
426 | while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
427 | if (numberOfSteps > maxSteps) {
428 | stepValue *= 2;
429 | numberOfSteps = Math.round(graphRange / stepValue);
430 | // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
431 | if (numberOfSteps % 1 !== 0) {
432 | skipFitting = true;
433 | }
434 | }
435 | //We can fit in double the amount of scale points on the scale
436 | else {
437 | //If user has declared ints only, and the step value isn't a decimal
438 | if (integersOnly && rangeOrderOfMagnitude >= 0) {
439 | //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
440 | if (stepValue / 2 % 1 === 0) {
441 | stepValue /= 2;
442 | numberOfSteps = Math.round(graphRange / stepValue);
443 | }
444 | //If it would make it a float break out of the loop
445 | else {
446 | break;
447 | }
448 | }
449 | //If the scale doesn't have to be an int, make the scale more granular anyway.
450 | else {
451 | stepValue /= 2;
452 | numberOfSteps = Math.round(graphRange / stepValue);
453 | }
454 |
455 | }
456 | }
457 |
458 | if (skipFitting) {
459 | numberOfSteps = minSteps;
460 | stepValue = graphRange / numberOfSteps;
461 | }
462 |
463 | return {
464 | steps: numberOfSteps,
465 | stepValue: stepValue,
466 | min: graphMin,
467 | max: graphMin + (numberOfSteps * stepValue)
468 | };
469 |
470 | },
471 | /* jshint ignore:start */
472 | // Blows up jshint errors based on the new Function constructor
473 | //Templating methods
474 | //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
475 | template = helpers.template = function(templateString, valuesObject) {
476 | // If templateString is function rather than string-template - call the function for valuesObject
477 | if (templateString instanceof Function) {
478 | return templateString(valuesObject);
479 | }
480 |
481 | var cache = {};
482 |
483 | function tmpl(str, data) {
484 | // Figure out if we're getting a template, or if we need to
485 | // load the template - and be sure to cache the result.
486 | var fn = !/\W/.test(str) ?
487 | cache[str] = cache[str] :
488 |
489 | // Generate a reusable function that will serve as a template
490 | // generator (and which will be cached).
491 | new Function("obj",
492 | "var p=[],print=function(){p.push.apply(p,arguments);};" +
493 |
494 | // Introduce the data as local variables using with(){}
495 | "with(obj){p.push('" +
496 |
497 | // Convert the template into pure JavaScript
498 | str
499 | .replace(/[\r\t\n]/g, " ")
500 | .split("<%").join("\t")
501 | .replace(/((^|%>)[^\t]*)'/g, "$1\r")
502 | .replace(/\t=(.*?)%>/g, "',$1,'")
503 | .split("\t").join("');")
504 | .split("%>").join("p.push('")
505 | .split("\r").join("\\'") +
506 | "');}return p.join('');"
507 | );
508 |
509 | // Provide some basic currying to the user
510 | return data ? fn(data) : fn;
511 | }
512 | return tmpl(templateString, valuesObject);
513 | },
514 | /* jshint ignore:end */
515 | generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) {
516 | var labelsArray = new Array(numberOfSteps);
517 | if (labelTemplateString) {
518 | each(labelsArray, function(val, index) {
519 | labelsArray[index] = template(templateString, {
520 | value: (graphMin + (stepValue * (index + 1)))
521 | });
522 | });
523 | }
524 | return labelsArray;
525 | },
526 | //--Animation methods
527 | //Easing functions adapted from Robert Penner's easing equations
528 | //http://www.robertpenner.com/easing/
529 | easingEffects = helpers.easingEffects = {
530 | linear: function(t) {
531 | return t;
532 | },
533 | easeInQuad: function(t) {
534 | return t * t;
535 | },
536 | easeOutQuad: function(t) {
537 | return -1 * t * (t - 2);
538 | },
539 | easeInOutQuad: function(t) {
540 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
541 | return -1 / 2 * ((--t) * (t - 2) - 1);
542 | },
543 | easeInCubic: function(t) {
544 | return t * t * t;
545 | },
546 | easeOutCubic: function(t) {
547 | return 1 * ((t = t / 1 - 1) * t * t + 1);
548 | },
549 | easeInOutCubic: function(t) {
550 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
551 | return 1 / 2 * ((t -= 2) * t * t + 2);
552 | },
553 | easeInQuart: function(t) {
554 | return t * t * t * t;
555 | },
556 | easeOutQuart: function(t) {
557 | return -1 * ((t = t / 1 - 1) * t * t * t - 1);
558 | },
559 | easeInOutQuart: function(t) {
560 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
561 | return -1 / 2 * ((t -= 2) * t * t * t - 2);
562 | },
563 | easeInQuint: function(t) {
564 | return 1 * (t /= 1) * t * t * t * t;
565 | },
566 | easeOutQuint: function(t) {
567 | return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
568 | },
569 | easeInOutQuint: function(t) {
570 | if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
571 | return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
572 | },
573 | easeInSine: function(t) {
574 | return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
575 | },
576 | easeOutSine: function(t) {
577 | return 1 * Math.sin(t / 1 * (Math.PI / 2));
578 | },
579 | easeInOutSine: function(t) {
580 | return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
581 | },
582 | easeInExpo: function(t) {
583 | return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
584 | },
585 | easeOutExpo: function(t) {
586 | return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
587 | },
588 | easeInOutExpo: function(t) {
589 | if (t === 0) return 0;
590 | if (t === 1) return 1;
591 | if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
592 | return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
593 | },
594 | easeInCirc: function(t) {
595 | if (t >= 1) return t;
596 | return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
597 | },
598 | easeOutCirc: function(t) {
599 | return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
600 | },
601 | easeInOutCirc: function(t) {
602 | if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
603 | return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
604 | },
605 | easeInElastic: function(t) {
606 | var s = 1.70158;
607 | var p = 0;
608 | var a = 1;
609 | if (t === 0) return 0;
610 | if ((t /= 1) == 1) return 1;
611 | if (!p) p = 1 * 0.3;
612 | if (a < Math.abs(1)) {
613 | a = 1;
614 | s = p / 4;
615 | } else s = p / (2 * Math.PI) * Math.asin(1 / a);
616 | return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
617 | },
618 | easeOutElastic: function(t) {
619 | var s = 1.70158;
620 | var p = 0;
621 | var a = 1;
622 | if (t === 0) return 0;
623 | if ((t /= 1) == 1) return 1;
624 | if (!p) p = 1 * 0.3;
625 | if (a < Math.abs(1)) {
626 | a = 1;
627 | s = p / 4;
628 | } else s = p / (2 * Math.PI) * Math.asin(1 / a);
629 | return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
630 | },
631 | easeInOutElastic: function(t) {
632 | var s = 1.70158;
633 | var p = 0;
634 | var a = 1;
635 | if (t === 0) return 0;
636 | if ((t /= 1 / 2) == 2) return 1;
637 | if (!p) p = 1 * (0.3 * 1.5);
638 | if (a < Math.abs(1)) {
639 | a = 1;
640 | s = p / 4;
641 | } else s = p / (2 * Math.PI) * Math.asin(1 / a);
642 | if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
643 | return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
644 | },
645 | easeInBack: function(t) {
646 | var s = 1.70158;
647 | return 1 * (t /= 1) * t * ((s + 1) * t - s);
648 | },
649 | easeOutBack: function(t) {
650 | var s = 1.70158;
651 | return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
652 | },
653 | easeInOutBack: function(t) {
654 | var s = 1.70158;
655 | if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
656 | return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
657 | },
658 | easeInBounce: function(t) {
659 | return 1 - easingEffects.easeOutBounce(1 - t);
660 | },
661 | easeOutBounce: function(t) {
662 | if ((t /= 1) < (1 / 2.75)) {
663 | return 1 * (7.5625 * t * t);
664 | } else if (t < (2 / 2.75)) {
665 | return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
666 | } else if (t < (2.5 / 2.75)) {
667 | return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
668 | } else {
669 | return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
670 | }
671 | },
672 | easeInOutBounce: function(t) {
673 | if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
674 | return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
675 | }
676 | },
677 | //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
678 | requestAnimFrame = helpers.requestAnimFrame = (function() {
679 | return noop;
680 | // return window.requestAnimationFrame ||
681 | // window.webkitRequestAnimationFrame ||
682 | // window.mozRequestAnimationFrame ||
683 | // window.oRequestAnimationFrame ||
684 | // window.msRequestAnimationFrame ||
685 | // function(callback) {
686 | // return window.setTimeout(callback, 1000 / 60);
687 | // };
688 | })(),
689 | cancelAnimFrame = helpers.cancelAnimFrame = (function() {
690 | return noop;
691 | // return window.cancelAnimationFrame ||
692 | // window.webkitCancelAnimationFrame ||
693 | // window.mozCancelAnimationFrame ||
694 | // window.oCancelAnimationFrame ||
695 | // window.msCancelAnimationFrame ||
696 | // function(callback) {
697 | // return window.clearTimeout(callback, 1000 / 60);
698 | // };
699 | })(),
700 | animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) {
701 |
702 | var currentStep = 0,
703 | easingFunction = easingEffects[easingString] || easingEffects.linear;
704 |
705 | var animationFrame = function() {
706 | currentStep++;
707 | var stepDecimal = currentStep / totalSteps;
708 | var easeDecimal = easingFunction(stepDecimal);
709 |
710 | callback.call(chartInstance, easeDecimal, stepDecimal, currentStep);
711 | onProgress.call(chartInstance, easeDecimal, stepDecimal);
712 | if (currentStep < totalSteps) {
713 | chartInstance.animationFrame = requestAnimFrame(animationFrame);
714 | } else {
715 | onComplete.apply(chartInstance);
716 | }
717 | };
718 | requestAnimFrame(animationFrame);
719 | },
720 | //-- DOM methods
721 | getRelativePosition = helpers.getRelativePosition = function(evt) {
722 | var mouseX, mouseY;
723 | mouseX = evt.clientX - evt.left;
724 | mouseY = evt.clientY - evt.top;
725 |
726 | return {
727 | x: mouseX,
728 | y: mouseY
729 | };
730 | // var mouseX, mouseY;
731 | // var e = evt.originalEvent || evt,
732 | // canvas = evt.currentTarget || evt.srcElement,
733 | // boundingRect = canvas.getBoundingClientRect();
734 |
735 | // if (e.touches){
736 | // mouseX = e.touches[0].clientX - boundingRect.left;
737 | // mouseY = e.touches[0].clientY - boundingRect.top;
738 |
739 | // }
740 | // else{
741 | // mouseX = e.clientX - boundingRect.left;
742 | // mouseY = e.clientY - boundingRect.top;
743 | // }
744 |
745 | // return {
746 | // x : mouseX,
747 | // y : mouseY
748 | // };
749 |
750 | },
751 | addEvent = helpers.addEvent = function(node, eventType, method) {
752 | // if (node.addEventListener){
753 | // node.addEventListener(eventType,method);
754 | // } else if (node.attachEvent){
755 | // node.attachEvent("on"+eventType, method);
756 | // } else {
757 | // node["on"+eventType] = method;
758 | // }
759 | },
760 | removeEvent = helpers.removeEvent = function(node, eventType, handler) {
761 | if (node.removeEventListener) {
762 | node.removeEventListener(eventType, handler, false);
763 | } else if (node.detachEvent) {
764 | node.detachEvent("on" + eventType, handler);
765 | } else {
766 | node["on" + eventType] = noop;
767 | }
768 | },
769 | bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) {
770 | // Create the events object if it's not already present
771 | if (!chartInstance.events) chartInstance.events = {};
772 |
773 | each(arrayOfEvents, function(eventName) {
774 | chartInstance.events[eventName] = function() {
775 | handler.apply(chartInstance, arguments);
776 | };
777 | // addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
778 | });
779 | },
780 | unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) {
781 | each(arrayOfEvents, function(handler, eventName) {
782 | removeEvent(chartInstance.chart.canvas, eventName, handler);
783 | });
784 | },
785 | getMaximumWidth = helpers.getMaximumWidth = function(domNode) {
786 | var container = domNode.parentNode;
787 | // TODO = check cross browser stuff with this.
788 | return container.clientWidth;
789 | },
790 | getMaximumHeight = helpers.getMaximumHeight = function(domNode) {
791 | var container = domNode.parentNode;
792 | // TODO = check cross browser stuff with this.
793 | return container.clientHeight;
794 | },
795 | getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
796 | retinaScale = helpers.retinaScale = function(chart) {
797 | var ctx = chart.ctx,
798 | width = chart.canvas.width,
799 | height = chart.canvas.height;
800 |
801 | // if (window.devicePixelRatio) {
802 | // ctx.canvas.style.width = width + "px";
803 | // ctx.canvas.style.height = height + "px";
804 | // ctx.canvas.height = height * window.devicePixelRatio;
805 | // ctx.canvas.width = width * window.devicePixelRatio;
806 | // ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
807 | // }
808 | },
809 | //-- Canvas methods
810 | clear = helpers.clear = function(chart) {
811 | chart.ctx.clearRect(0, 0, chart.width, chart.height);
812 | },
813 | fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) {
814 | return fontStyle + " " + pixelSize + "px " + fontFamily;
815 | },
816 | longestText = helpers.longestText = function(ctx, font, arrayOfStrings) {
817 | ctx.font = font;
818 | var longest = 0;
819 | each(arrayOfStrings, function(string) {
820 | var textWidth = ctx.measureText(string).width;
821 | longest = (textWidth > longest) ? textWidth : longest;
822 | });
823 | return longest;
824 | },
825 | drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) {
826 | ctx.beginPath();
827 | ctx.moveTo(x + radius, y);
828 | ctx.lineTo(x + width - radius, y);
829 | ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
830 | ctx.lineTo(x + width, y + height - radius);
831 | ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
832 | ctx.lineTo(x + radius, y + height);
833 | ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
834 | ctx.lineTo(x, y + radius);
835 | ctx.quadraticCurveTo(x, y, x + radius, y);
836 | ctx.closePath();
837 | };
838 |
839 |
840 | //Store a reference to each instance - allowing us to globally resize chart instances on window resize.
841 | //Destroy method on the chart will remove the instance of the chart from this reference.
842 | Chart.instances = {};
843 |
844 | Chart.Type = function(data, options, chart) {
845 | this.options = options;
846 | this.chart = chart;
847 | this.id = uid();
848 | //Add the chart instance to the global namespace
849 | Chart.instances[this.id] = this;
850 |
851 | // Initialize is always called when a chart type is created
852 | // By default it is a no op, but it should be extended
853 | if (options.responsive) {
854 | this.resize();
855 | }
856 | this.initialize.call(this, data);
857 | };
858 |
859 | //Core methods that'll be a part of every chart type
860 | extend(Chart.Type.prototype, {
861 | initialize: function() {
862 | return this;
863 | },
864 | clear: function() {
865 | clear(this.chart);
866 | return this;
867 | },
868 | stop: function() {
869 | // Stops any current animation loop occuring
870 | // helpers.cancelAnimFrame.call(root, this.animationFrame);
871 | return this;
872 | },
873 | resize: function(callback) {
874 | this.stop();
875 | var canvas = this.chart.canvas,
876 | newWidth = getMaximumWidth(this.chart.canvas),
877 | newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
878 |
879 | canvas.width = this.chart.width = newWidth;
880 | canvas.height = this.chart.height = newHeight;
881 |
882 | retinaScale(this.chart);
883 |
884 | if (typeof callback === "function") {
885 | callback.apply(this, Array.prototype.slice.call(arguments, 1));
886 | }
887 | return this;
888 | },
889 | reflow: noop,
890 | render: function(reflow) {
891 | if (reflow) {
892 | this.reflow();
893 | }
894 | if (this.options.animation && !reflow) {
895 | helpers.animationLoop(
896 | this.draw,
897 | this.options.animationSteps,
898 | this.options.animationEasing,
899 | this.options.onAnimationProgress,
900 | this.options.onAnimationComplete,
901 | this
902 | );
903 | } else {
904 | this.draw();
905 | this.options.onAnimationComplete.call(this);
906 | }
907 | return this;
908 | },
909 | generateLegend: function() {
910 | return template(this.options.legendTemplate, this);
911 | },
912 | destroy: function() {
913 | this.clear();
914 | unbindEvents(this, this.events);
915 | delete Chart.instances[this.id];
916 | },
917 | showTooltip: function(ChartElements, forceRedraw) {
918 | // Only redraw the chart if we've actually changed what we're hovering on.
919 | if (typeof this.activeElements === 'undefined') this.activeElements = [];
920 |
921 | var isChanged = (function(Elements) {
922 | var changed = false;
923 |
924 | if (Elements.length !== this.activeElements.length) {
925 | changed = true;
926 | return changed;
927 | }
928 |
929 | each(Elements, function(element, index) {
930 | if (element !== this.activeElements[index]) {
931 | changed = true;
932 | }
933 | }, this);
934 | return changed;
935 | }).call(this, ChartElements);
936 |
937 | if (!isChanged && !forceRedraw) {
938 | return;
939 | } else {
940 | this.activeElements = ChartElements;
941 | }
942 | this.draw();
943 | if (ChartElements.length > 0) {
944 | // If we have multiple datasets, show a MultiTooltip for all of the data points at that index
945 | if (this.datasets && this.datasets.length > 1) {
946 | var dataArray,
947 | dataIndex;
948 |
949 | for (var i = this.datasets.length - 1; i >= 0; i--) {
950 | dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
951 | dataIndex = indexOf(dataArray, ChartElements[0]);
952 | if (dataIndex !== -1) {
953 | break;
954 | }
955 | }
956 | var tooltipLabels = [],
957 | tooltipColors = [],
958 | medianPosition = (function(index) {
959 |
960 | // Get all the points at that particular index
961 | var Elements = [],
962 | dataCollection,
963 | xPositions = [],
964 | yPositions = [],
965 | xMax,
966 | yMax,
967 | xMin,
968 | yMin;
969 | helpers.each(this.datasets, function(dataset) {
970 | dataCollection = dataset.points || dataset.bars || dataset.segments;
971 | if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
972 | Elements.push(dataCollection[dataIndex]);
973 | }
974 | });
975 |
976 | helpers.each(Elements, function(element) {
977 | xPositions.push(element.x);
978 | yPositions.push(element.y);
979 |
980 |
981 | //Include any colour information about the element
982 | tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
983 | tooltipColors.push({
984 | fill: element._saved.fillColor || element.fillColor,
985 | stroke: element._saved.strokeColor || element.strokeColor
986 | });
987 |
988 | }, this);
989 |
990 | yMin = min(yPositions);
991 | yMax = max(yPositions);
992 |
993 | xMin = min(xPositions);
994 | xMax = max(xPositions);
995 |
996 | return {
997 | x: (xMin > this.chart.width / 2) ? xMin : xMax,
998 | y: (yMin + yMax) / 2
999 | };
1000 | }).call(this, dataIndex);
1001 |
1002 | new Chart.MultiTooltip({
1003 | x: medianPosition.x,
1004 | y: medianPosition.y,
1005 | xPadding: this.options.tooltipXPadding,
1006 | yPadding: this.options.tooltipYPadding,
1007 | xOffset: this.options.tooltipXOffset,
1008 | fillColor: this.options.tooltipFillColor,
1009 | textColor: this.options.tooltipFontColor,
1010 | fontFamily: this.options.tooltipFontFamily,
1011 | fontStyle: this.options.tooltipFontStyle,
1012 | fontSize: this.options.tooltipFontSize,
1013 | titleTextColor: this.options.tooltipTitleFontColor,
1014 | titleFontFamily: this.options.tooltipTitleFontFamily,
1015 | titleFontStyle: this.options.tooltipTitleFontStyle,
1016 | titleFontSize: this.options.tooltipTitleFontSize,
1017 | cornerRadius: this.options.tooltipCornerRadius,
1018 | labels: tooltipLabels,
1019 | legendColors: tooltipColors,
1020 | legendColorBackground: this.options.multiTooltipKeyBackground,
1021 | title: ChartElements[0].label,
1022 | chart: this.chart,
1023 | ctx: this.chart.ctx
1024 | }).draw();
1025 |
1026 | } else {
1027 | each(ChartElements, function(Element) {
1028 | var tooltipPosition = Element.tooltipPosition();
1029 | new Chart.Tooltip({
1030 | x: Math.round(tooltipPosition.x),
1031 | y: Math.round(tooltipPosition.y),
1032 | xPadding: this.options.tooltipXPadding,
1033 | yPadding: this.options.tooltipYPadding,
1034 | fillColor: this.options.tooltipFillColor,
1035 | textColor: this.options.tooltipFontColor,
1036 | fontFamily: this.options.tooltipFontFamily,
1037 | fontStyle: this.options.tooltipFontStyle,
1038 | fontSize: this.options.tooltipFontSize,
1039 | caretHeight: this.options.tooltipCaretSize,
1040 | cornerRadius: this.options.tooltipCornerRadius,
1041 | text: template(this.options.tooltipTemplate, Element),
1042 | chart: this.chart
1043 | }).draw();
1044 | }, this);
1045 | }
1046 | }
1047 | return this;
1048 | },
1049 | toBase64Image: function() {
1050 | return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
1051 | }
1052 | });
1053 |
1054 | Chart.Type.extend = function(extensions) {
1055 |
1056 | var parent = this;
1057 |
1058 | var ChartType = function() {
1059 | return parent.apply(this, arguments);
1060 | };
1061 |
1062 | //Copy the prototype object of the this class
1063 | ChartType.prototype = clone(parent.prototype);
1064 | //Now overwrite some of the properties in the base class with the new extensions
1065 | extend(ChartType.prototype, extensions);
1066 |
1067 | ChartType.extend = Chart.Type.extend;
1068 |
1069 | if (extensions.name || parent.prototype.name) {
1070 |
1071 | var chartName = extensions.name || parent.prototype.name;
1072 | //Assign any potential default values of the new chart type
1073 |
1074 | //If none are defined, we'll use a clone of the chart type this is being extended from.
1075 | //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
1076 | //doesn't define some defaults of their own.
1077 |
1078 | var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
1079 |
1080 | Chart.defaults[chartName] = extend(baseDefaults, extensions.defaults);
1081 |
1082 | Chart.types[chartName] = ChartType;
1083 |
1084 | //Register this new chart type in the Chart prototype
1085 | Chart.prototype[chartName] = function(data, options) {
1086 | var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
1087 | return new ChartType(data, config, this);
1088 | };
1089 | } else {
1090 | warn("Name not provided for this chart, so it hasn't been registered");
1091 | }
1092 | return parent;
1093 | };
1094 |
1095 | Chart.Element = function(configuration) {
1096 | extend(this, configuration);
1097 | this.initialize.apply(this, arguments);
1098 | this.save();
1099 | };
1100 | extend(Chart.Element.prototype, {
1101 | initialize: function() {},
1102 | restore: function(props) {
1103 | if (!props) {
1104 | extend(this, this._saved);
1105 | } else {
1106 | each(props, function(key) {
1107 | this[key] = this._saved[key];
1108 | }, this);
1109 | }
1110 | return this;
1111 | },
1112 | save: function() {
1113 | this._saved = clone(this);
1114 | delete this._saved._saved;
1115 | return this;
1116 | },
1117 | update: function(newProps) {
1118 | each(newProps, function(value, key) {
1119 | this._saved[key] = this[key];
1120 | this[key] = value;
1121 | }, this);
1122 | return this;
1123 | },
1124 | transition: function(props, ease) {
1125 | each(props, function(value, key) {
1126 | this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
1127 | }, this);
1128 | return this;
1129 | },
1130 | tooltipPosition: function() {
1131 | return {
1132 | x: this.x,
1133 | y: this.y
1134 | };
1135 | },
1136 | hasValue: function() {
1137 | return isNumber(this.value);
1138 | }
1139 | });
1140 |
1141 | Chart.Element.extend = inherits;
1142 |
1143 |
1144 | Chart.Point = Chart.Element.extend({
1145 | display: true,
1146 | inRange: function(chartX, chartY) {
1147 | var hitDetectionRange = this.hitDetectionRadius + this.radius;
1148 | return ((Math.pow(chartX - this.x, 2) + Math.pow(chartY - this.y, 2)) < Math.pow(hitDetectionRange, 2));
1149 | },
1150 | draw: function() {
1151 | if (this.display) {
1152 | var ctx = this.ctx;
1153 | ctx.beginPath();
1154 |
1155 | ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
1156 | ctx.closePath();
1157 |
1158 | ctx.strokeStyle = this.strokeColor;
1159 | ctx.lineWidth = this.strokeWidth;
1160 |
1161 | ctx.fillStyle = this.fillColor;
1162 |
1163 | ctx.fill();
1164 | ctx.stroke();
1165 | }
1166 |
1167 |
1168 | //Quick debug for bezier curve splining
1169 | //Highlights control points and the line between them.
1170 | //Handy for dev - stripped in the min version.
1171 |
1172 | // ctx.save();
1173 | // ctx.fillStyle = "black";
1174 | // ctx.strokeStyle = "black"
1175 | // ctx.beginPath();
1176 | // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
1177 | // ctx.fill();
1178 |
1179 | // ctx.beginPath();
1180 | // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
1181 | // ctx.fill();
1182 |
1183 | // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1184 | // ctx.lineTo(this.x, this.y);
1185 | // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1186 | // ctx.stroke();
1187 |
1188 | // ctx.restore();
1189 |
1190 |
1191 |
1192 | }
1193 | });
1194 |
1195 | Chart.Arc = Chart.Element.extend({
1196 | inRange: function(chartX, chartY) {
1197 |
1198 | var pointRelativePosition = helpers.getAngleFromPoint(this, {
1199 | x: chartX,
1200 | y: chartY
1201 | });
1202 |
1203 | //Check if within the range of the open/close angle
1204 | var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
1205 | withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1206 |
1207 | return (betweenAngles && withinRadius);
1208 | //Ensure within the outside of the arc centre, but inside arc outer
1209 | },
1210 | tooltipPosition: function() {
1211 | var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
1212 | rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
1213 | return {
1214 | x: this.x + (Math.cos(centreAngle) * rangeFromCentre),
1215 | y: this.y + (Math.sin(centreAngle) * rangeFromCentre)
1216 | };
1217 | },
1218 | draw: function(animationPercent) {
1219 |
1220 | var easingDecimal = animationPercent || 1;
1221 |
1222 | var ctx = this.ctx;
1223 |
1224 | ctx.beginPath();
1225 |
1226 | ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
1227 |
1228 | ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
1229 |
1230 | ctx.closePath();
1231 | ctx.strokeStyle = this.strokeColor;
1232 | ctx.lineWidth = this.strokeWidth;
1233 |
1234 | ctx.fillStyle = this.fillColor;
1235 |
1236 | ctx.fill();
1237 | ctx.lineJoin = 'bevel';
1238 |
1239 | if (this.showStroke) {
1240 | ctx.stroke();
1241 | }
1242 | }
1243 | });
1244 |
1245 | Chart.Rectangle = Chart.Element.extend({
1246 | draw: function() {
1247 | var ctx = this.ctx,
1248 | halfWidth = this.width / 2,
1249 | leftX = this.x - halfWidth,
1250 | rightX = this.x + halfWidth,
1251 | top = this.base - (this.base - this.y),
1252 | halfStroke = this.strokeWidth / 2;
1253 |
1254 | // Canvas doesn't allow us to stroke inside the width so we can
1255 | // adjust the sizes to fit if we're setting a stroke on the line
1256 | if (this.showStroke) {
1257 | leftX += halfStroke;
1258 | rightX -= halfStroke;
1259 | top += halfStroke;
1260 | }
1261 |
1262 | ctx.beginPath();
1263 |
1264 | ctx.fillStyle = this.fillColor;
1265 | ctx.strokeStyle = this.strokeColor;
1266 | ctx.lineWidth = this.strokeWidth;
1267 |
1268 | // It'd be nice to keep this class totally generic to any rectangle
1269 | // and simply specify which border to miss out.
1270 | ctx.moveTo(leftX, this.base);
1271 | ctx.lineTo(leftX, top);
1272 | ctx.lineTo(rightX, top);
1273 | ctx.lineTo(rightX, this.base);
1274 | ctx.fill();
1275 | if (this.showStroke) {
1276 | ctx.stroke();
1277 | }
1278 | },
1279 | height: function() {
1280 | return this.base - this.y;
1281 | },
1282 | inRange: function(chartX, chartY) {
1283 | return (chartX >= this.x - this.width / 2 && chartX <= this.x + this.width / 2) && (chartY >= this.y && chartY <= this.base);
1284 | }
1285 | });
1286 |
1287 | Chart.Tooltip = Chart.Element.extend({
1288 | draw: function() {
1289 | var ctx = this.chart.ctx;
1290 |
1291 | ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
1292 |
1293 | this.xAlign = "center";
1294 | this.yAlign = "above";
1295 |
1296 | //Distance between the actual element.y position and the start of the tooltip caret
1297 | var caretPadding = 2;
1298 |
1299 | var tooltipWidth = ctx.measureText(this.text).width + 2 * this.xPadding,
1300 | tooltipRectHeight = this.fontSize + 2 * this.yPadding,
1301 | tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
1302 |
1303 | if (this.x + tooltipWidth / 2 > this.chart.width) {
1304 | this.xAlign = "left";
1305 | } else if (this.x - tooltipWidth / 2 < 0) {
1306 | this.xAlign = "right";
1307 | }
1308 |
1309 | if (this.y - tooltipHeight < 0) {
1310 | this.yAlign = "below";
1311 | }
1312 |
1313 |
1314 | var tooltipX = this.x - tooltipWidth / 2,
1315 | tooltipY = this.y - tooltipHeight;
1316 |
1317 | ctx.fillStyle = this.fillColor;
1318 |
1319 | var caretX = this.x;
1320 | var isOutOfRightBorder = false;
1321 | //check whether tool tip will show out of the right border
1322 | if (this.x + this.caretHeight + this.cornerRadius > this.chart.width) {
1323 | isOutOfRightBorder = true;
1324 | caretX -= this.caretHeight;
1325 | }
1326 |
1327 | switch (this.yAlign) {
1328 | case "above":
1329 | //Draw a caret above the x/y
1330 | ctx.beginPath();
1331 | ctx.moveTo(this.x, this.y - caretPadding);
1332 | ctx.lineTo(caretX + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1333 | ctx.lineTo(caretX - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1334 | ctx.closePath();
1335 | ctx.fill();
1336 | break;
1337 | case "below":
1338 | tooltipY = this.y + caretPadding + this.caretHeight;
1339 | //Draw a caret below the x/y
1340 | ctx.beginPath();
1341 | ctx.moveTo(this.x, this.y + caretPadding);
1342 | ctx.lineTo(caretX + this.caretHeight, this.y + caretPadding + this.caretHeight);
1343 | ctx.lineTo(caretX - this.caretHeight, this.y + caretPadding + this.caretHeight);
1344 | ctx.closePath();
1345 | ctx.fill();
1346 | break;
1347 | }
1348 |
1349 | switch (this.xAlign) {
1350 | case "left":
1351 | if (isOutOfRightBorder) {
1352 | tooltipX = this.x - tooltipWidth + this.cornerRadius;
1353 | } else {
1354 | tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1355 | }
1356 |
1357 | break;
1358 | case "right":
1359 | tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1360 | break;
1361 | }
1362 |
1363 | drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, this.cornerRadius);
1364 |
1365 | ctx.fill();
1366 |
1367 | ctx.fillStyle = this.textColor;
1368 | ctx.textAlign = "center";
1369 | ctx.textBaseline = "middle";
1370 | ctx.fillText(this.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
1371 | }
1372 | });
1373 |
1374 | Chart.MultiTooltip = Chart.Element.extend({
1375 | initialize: function() {
1376 | this.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
1377 |
1378 | this.titleFont = fontString(this.titleFontSize, this.titleFontStyle, this.titleFontFamily);
1379 |
1380 | this.height = (this.labels.length * this.fontSize) + ((this.labels.length - 1) * (this.fontSize / 2)) + (this.yPadding * 2) + this.titleFontSize * 1.5;
1381 |
1382 | this.ctx.font = this.titleFont;
1383 |
1384 | var titleWidth = this.ctx.measureText(this.title).width,
1385 | //Label has a legend square as well so account for this.
1386 | labelWidth = longestText(this.ctx, this.font, this.labels) + this.fontSize + 3,
1387 | longestTextWidth = max([labelWidth, titleWidth]);
1388 |
1389 | this.width = longestTextWidth + (this.xPadding * 2);
1390 |
1391 |
1392 | var halfHeight = this.height / 2;
1393 |
1394 | //Check to ensure the height will fit on the canvas
1395 | //The three is to buffer form the very
1396 | if (this.y - halfHeight < 0) {
1397 | this.y = halfHeight;
1398 | } else if (this.y + halfHeight > this.chart.height) {
1399 | this.y = this.chart.height - halfHeight;
1400 | }
1401 |
1402 | //Decide whether to align left or right based on position on canvas
1403 | if (this.x > this.chart.width / 2) {
1404 | this.x -= this.xOffset + this.width;
1405 | } else {
1406 | this.x += this.xOffset;
1407 | }
1408 |
1409 |
1410 | },
1411 | getLineHeight: function(index) {
1412 | var baseLineHeight = this.y - (this.height / 2) + this.yPadding,
1413 | afterTitleIndex = index - 1;
1414 |
1415 | //If the index is zero, we're getting the title
1416 | if (index === 0) {
1417 | return baseLineHeight + this.titleFontSize / 2;
1418 | } else {
1419 | return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleFontSize * 1.5;
1420 | }
1421 |
1422 | },
1423 | draw: function() {
1424 | drawRoundedRectangle(this.ctx, this.x, this.y - this.height / 2, this.width, this.height, this.cornerRadius);
1425 | var ctx = this.ctx;
1426 | ctx.fillStyle = this.fillColor;
1427 | ctx.fill();
1428 | ctx.closePath();
1429 |
1430 | ctx.textAlign = "left";
1431 | ctx.textBaseline = "middle";
1432 | ctx.fillStyle = this.titleTextColor;
1433 | ctx.font = this.titleFont;
1434 |
1435 | ctx.fillText(this.title, this.x + this.xPadding, this.getLineHeight(0));
1436 |
1437 | ctx.font = this.font;
1438 | helpers.each(this.labels, function(label, index) {
1439 | ctx.fillStyle = this.textColor;
1440 | ctx.fillText(label, this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1441 |
1442 | //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1443 | //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1444 | //Instead we'll make a white filled block to put the legendColour palette over.
1445 |
1446 | ctx.fillStyle = this.legendColorBackground;
1447 | ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize);
1448 |
1449 | ctx.fillStyle = this.legendColors[index].fill;
1450 | ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize);
1451 |
1452 |
1453 | }, this);
1454 | }
1455 | });
1456 |
1457 | Chart.Scale = Chart.Element.extend({
1458 | initialize: function() {
1459 | this.fit();
1460 | },
1461 | buildYLabels: function() {
1462 | this.yLabels = [];
1463 |
1464 | var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1465 |
1466 | for (var i = 0; i <= this.steps; i++) {
1467 | this.yLabels.push(template(this.templateString, {
1468 | value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
1469 | }));
1470 | }
1471 | this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx, this.font, this.yLabels) : 0;
1472 | },
1473 | addXLabel: function(label) {
1474 | this.xLabels.push(label);
1475 | this.valuesCount++;
1476 | this.fit();
1477 | },
1478 | updateXLabel: function(indexToUpdate, label) {
1479 | this.xLabels[indexToUpdate] = label;
1480 | this.fit();
1481 | this.update();
1482 | },
1483 | removeXLabel: function() {
1484 | this.xLabels.shift();
1485 | this.valuesCount--;
1486 | this.fit();
1487 | },
1488 | removeXLabelAtIndex: function(indexToRemove) {
1489 | this.xLabels.splice(indexToRemove, 1);
1490 | this.valuesCount--;
1491 | this.fit();
1492 | },
1493 | // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1494 | fit: function() {
1495 | // First we need the width of the yLabels, assuming the xLabels aren't rotated
1496 |
1497 | // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1498 | this.startPoint = (this.display) ? this.fontSize : 0;
1499 | this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1500 |
1501 | // Apply padding settings to the start and end point.
1502 | this.startPoint += this.padding;
1503 | this.endPoint -= this.padding;
1504 |
1505 | // Cache the starting height, so can determine if we need to recalculate the scale yAxis
1506 | var cachedHeight = this.endPoint - this.startPoint,
1507 | cachedYLabelWidth;
1508 |
1509 | // Build the current yLabels so we have an idea of what size they'll be to start
1510 | /*
1511 | * This sets what is returned from calculateScaleRange as static properties of this class:
1512 | *
1513 | this.steps;
1514 | this.stepValue;
1515 | this.min;
1516 | this.max;
1517 | *
1518 | */
1519 | this.calculateYRange(cachedHeight);
1520 |
1521 | // With these properties set we can now build the array of yLabels
1522 | // and also the width of the largest yLabel
1523 | this.buildYLabels();
1524 |
1525 | this.calculateXLabelRotation();
1526 |
1527 | while ((cachedHeight > this.endPoint - this.startPoint)) {
1528 | cachedHeight = this.endPoint - this.startPoint;
1529 | cachedYLabelWidth = this.yLabelWidth;
1530 |
1531 | this.calculateYRange(cachedHeight);
1532 | this.buildYLabels();
1533 |
1534 | // Only go through the xLabel loop again if the yLabel width has changed
1535 | if (cachedYLabelWidth < this.yLabelWidth) {
1536 | this.calculateXLabelRotation();
1537 | }
1538 | }
1539 |
1540 | },
1541 | calculateXLabelRotation: function() {
1542 | //Get the width of each grid by calculating the difference
1543 | //between x offsets between 0 and 1.
1544 |
1545 | this.ctx.font = this.font;
1546 |
1547 | var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1548 | lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1549 | firstRotated,
1550 | lastRotated;
1551 |
1552 |
1553 | this.xScalePaddingRight = lastWidth / 2 + 3;
1554 | this.xScalePaddingLeft = (firstWidth / 2 > this.yLabelWidth + 10) ? firstWidth / 2 : this.yLabelWidth + 10;
1555 |
1556 | this.xLabelRotation = 0;
1557 | if (this.display) {
1558 | var originalLabelWidth = longestText(this.ctx, this.font, this.xLabels),
1559 | cosRotation,
1560 | firstRotatedWidth;
1561 | this.xLabelWidth = originalLabelWidth;
1562 | //Allow 3 pixels x2 padding either side for label readability
1563 | var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1564 |
1565 | //Max label rotate should be 90 - also act as a loop counter
1566 | while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)) {
1567 | cosRotation = Math.cos(toRadians(this.xLabelRotation));
1568 |
1569 | firstRotated = cosRotation * firstWidth;
1570 | lastRotated = cosRotation * lastWidth;
1571 |
1572 | // We're right aligning the text now.
1573 | if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8) {
1574 | this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1575 | }
1576 | this.xScalePaddingRight = this.fontSize / 2;
1577 |
1578 |
1579 | this.xLabelRotation++;
1580 | this.xLabelWidth = cosRotation * originalLabelWidth;
1581 |
1582 | }
1583 | if (this.xLabelRotation > 0) {
1584 | this.endPoint -= Math.sin(toRadians(this.xLabelRotation)) * originalLabelWidth + 3;
1585 | }
1586 | } else {
1587 | this.xLabelWidth = 0;
1588 | this.xScalePaddingRight = this.padding;
1589 | this.xScalePaddingLeft = this.padding;
1590 | }
1591 |
1592 | },
1593 | // Needs to be overidden in each Chart type
1594 | // Otherwise we need to pass all the data into the scale class
1595 | calculateYRange: noop,
1596 | drawingArea: function() {
1597 | return this.startPoint - this.endPoint;
1598 | },
1599 | calculateY: function(value) {
1600 | var scalingFactor = this.drawingArea() / (this.min - this.max);
1601 | return this.endPoint - (scalingFactor * (value - this.min));
1602 | },
1603 | calculateX: function(index) {
1604 | var isRotated = (this.xLabelRotation > 0),
1605 | // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1606 | innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1607 | valueWidth = innerWidth / (this.valuesCount - ((this.offsetGridLines) ? 0 : 1)),
1608 | valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1609 |
1610 | if (this.offsetGridLines) {
1611 | valueOffset += (valueWidth / 2);
1612 | }
1613 |
1614 | return Math.round(valueOffset);
1615 | },
1616 | update: function(newProps) {
1617 | helpers.extend(this, newProps);
1618 | this.fit();
1619 | },
1620 | draw: function() {
1621 | var ctx = this.ctx,
1622 | yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1623 | xStart = Math.round(this.xScalePaddingLeft);
1624 | if (this.display) {
1625 | ctx.fillStyle = this.textColor;
1626 | ctx.font = this.font;
1627 | each(this.yLabels, function(labelString, index) {
1628 | var yLabelCenter = this.endPoint - (yLabelGap * index),
1629 | linePositionY = Math.round(yLabelCenter);
1630 |
1631 | ctx.textAlign = "right";
1632 | ctx.textBaseline = "middle";
1633 | if (this.showLabels) {
1634 | ctx.fillText(labelString, xStart - 10, yLabelCenter);
1635 | }
1636 | ctx.beginPath();
1637 | if (index > 0) {
1638 | // This is a grid line in the centre, so drop that
1639 | ctx.lineWidth = this.gridLineWidth;
1640 | ctx.strokeStyle = this.gridLineColor;
1641 | } else {
1642 | // This is the first line on the scale
1643 | ctx.lineWidth = this.lineWidth;
1644 | ctx.strokeStyle = this.lineColor;
1645 | }
1646 |
1647 | linePositionY += helpers.aliasPixel(ctx.lineWidth);
1648 |
1649 | ctx.moveTo(xStart, linePositionY);
1650 | ctx.lineTo(this.width, linePositionY);
1651 | ctx.stroke();
1652 | ctx.closePath();
1653 |
1654 | ctx.lineWidth = this.lineWidth;
1655 | ctx.strokeStyle = this.lineColor;
1656 | ctx.beginPath();
1657 | ctx.moveTo(xStart - 5, linePositionY);
1658 | ctx.lineTo(xStart, linePositionY);
1659 | ctx.stroke();
1660 | ctx.closePath();
1661 |
1662 | }, this);
1663 |
1664 | each(this.xLabels, function(label, index) {
1665 | var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1666 | // Check to see if line/bar here and decide where to place the line
1667 | linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1668 | isRotated = (this.xLabelRotation > 0);
1669 |
1670 | ctx.beginPath();
1671 |
1672 | if (index > 0) {
1673 | // This is a grid line in the centre, so drop that
1674 | ctx.lineWidth = this.gridLineWidth;
1675 | ctx.strokeStyle = this.gridLineColor;
1676 | } else {
1677 | // This is the first line on the scale
1678 | ctx.lineWidth = this.lineWidth;
1679 | ctx.strokeStyle = this.lineColor;
1680 | }
1681 | ctx.moveTo(linePos, this.endPoint);
1682 | ctx.lineTo(linePos, this.startPoint - 3);
1683 | ctx.stroke();
1684 | ctx.closePath();
1685 |
1686 |
1687 | ctx.lineWidth = this.lineWidth;
1688 | ctx.strokeStyle = this.lineColor;
1689 |
1690 |
1691 | // Small lines at the bottom of the base grid line
1692 | ctx.beginPath();
1693 | ctx.moveTo(linePos, this.endPoint);
1694 | ctx.lineTo(linePos, this.endPoint + 5);
1695 | ctx.stroke();
1696 | ctx.closePath();
1697 |
1698 | ctx.save();
1699 | ctx.translate(xPos, (isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1700 | ctx.rotate(toRadians(this.xLabelRotation) * -1);
1701 | ctx.font = this.font;
1702 | ctx.textAlign = (isRotated) ? "right" : "center";
1703 | ctx.textBaseline = (isRotated) ? "middle" : "top";
1704 | ctx.fillText(label, 0, 0);
1705 | ctx.restore();
1706 | }, this);
1707 |
1708 | }
1709 | }
1710 |
1711 | });
1712 |
1713 | Chart.RadialScale = Chart.Element.extend({
1714 | initialize: function() {
1715 | this.size = min([this.height, this.width]);
1716 | this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
1717 | },
1718 | calculateCenterOffset: function(value) {
1719 | // Take into account half font size + the yPadding of the top value
1720 | var scalingFactor = this.drawingArea / (this.max - this.min);
1721 |
1722 | return (value - this.min) * scalingFactor;
1723 | },
1724 | update: function() {
1725 | if (!this.lineArc) {
1726 | this.setScaleSize();
1727 | } else {
1728 | this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
1729 | }
1730 | this.buildYLabels();
1731 | },
1732 | buildYLabels: function() {
1733 | this.yLabels = [];
1734 |
1735 | var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1736 |
1737 | for (var i = 0; i <= this.steps; i++) {
1738 | this.yLabels.push(template(this.templateString, {
1739 | value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
1740 | }));
1741 | }
1742 | },
1743 | getCircumference: function() {
1744 | return ((Math.PI * 2) / this.valuesCount);
1745 | },
1746 | setScaleSize: function() {
1747 | /*
1748 | * Right, this is really confusing and there is a lot of maths going on here
1749 | * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1750 | *
1751 | * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1752 | *
1753 | * Solution:
1754 | *
1755 | * We assume the radius of the polygon is half the size of the canvas at first
1756 | * at each index we check if the text overlaps.
1757 | *
1758 | * Where it does, we store that angle and that index.
1759 | *
1760 | * After finding the largest index and angle we calculate how much we need to remove
1761 | * from the shape radius to move the point inwards by that x.
1762 | *
1763 | * We average the left and right distances to get the maximum shape radius that can fit in the box
1764 | * along with labels.
1765 | *
1766 | * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1767 | * on each side, removing that from the size, halving it and adding the left x protrusion width.
1768 | *
1769 | * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1770 | * and position it in the most space efficient manner
1771 | *
1772 | * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1773 | */
1774 |
1775 |
1776 | // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1777 | // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1778 | var largestPossibleRadius = min([(this.height / 2 - this.pointLabelFontSize - 5), this.width / 2]),
1779 | pointPosition,
1780 | i,
1781 | textWidth,
1782 | halfTextWidth,
1783 | furthestRight = this.width,
1784 | furthestRightIndex,
1785 | furthestRightAngle,
1786 | furthestLeft = 0,
1787 | furthestLeftIndex,
1788 | furthestLeftAngle,
1789 | xProtrusionLeft,
1790 | xProtrusionRight,
1791 | radiusReductionRight,
1792 | radiusReductionLeft,
1793 | maxWidthRadius;
1794 | this.ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
1795 | for (i = 0; i < this.valuesCount; i++) {
1796 | // 5px to space the text slightly out - similar to what we do in the draw function.
1797 | pointPosition = this.getPointPosition(i, largestPossibleRadius);
1798 | textWidth = this.ctx.measureText(template(this.templateString, {
1799 | value: this.labels[i]
1800 | })).width + 5;
1801 | if (i === 0 || i === this.valuesCount / 2) {
1802 | // If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1803 | // of the radar chart, so text will be aligned centrally, so we'll half it and compare
1804 | // w/left and right text sizes
1805 | halfTextWidth = textWidth / 2;
1806 | if (pointPosition.x + halfTextWidth > furthestRight) {
1807 | furthestRight = pointPosition.x + halfTextWidth;
1808 | furthestRightIndex = i;
1809 | }
1810 | if (pointPosition.x - halfTextWidth < furthestLeft) {
1811 | furthestLeft = pointPosition.x - halfTextWidth;
1812 | furthestLeftIndex = i;
1813 | }
1814 | } else if (i < this.valuesCount / 2) {
1815 | // Less than half the values means we'll left align the text
1816 | if (pointPosition.x + textWidth > furthestRight) {
1817 | furthestRight = pointPosition.x + textWidth;
1818 | furthestRightIndex = i;
1819 | }
1820 | } else if (i > this.valuesCount / 2) {
1821 | // More than half the values means we'll right align the text
1822 | if (pointPosition.x - textWidth < furthestLeft) {
1823 | furthestLeft = pointPosition.x - textWidth;
1824 | furthestLeftIndex = i;
1825 | }
1826 | }
1827 | }
1828 |
1829 | xProtrusionLeft = furthestLeft;
1830 |
1831 | xProtrusionRight = Math.ceil(furthestRight - this.width);
1832 |
1833 | furthestRightAngle = this.getIndexAngle(furthestRightIndex);
1834 |
1835 | furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
1836 |
1837 | radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
1838 |
1839 | radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
1840 |
1841 | // Ensure we actually need to reduce the size of the chart
1842 | radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1843 | radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1844 |
1845 | this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2;
1846 |
1847 | //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1848 | this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1849 |
1850 | },
1851 | setCenterPoint: function(leftMovement, rightMovement) {
1852 |
1853 | var maxRight = this.width - rightMovement - this.drawingArea,
1854 | maxLeft = leftMovement + this.drawingArea;
1855 |
1856 | this.xCenter = (maxLeft + maxRight) / 2;
1857 | // Always vertically in the centre as the text height doesn't change
1858 | this.yCenter = (this.height / 2);
1859 | },
1860 |
1861 | getIndexAngle: function(index) {
1862 | var angleMultiplier = (Math.PI * 2) / this.valuesCount;
1863 | // Start from the top instead of right, so remove a quarter of the circle
1864 |
1865 | return index * angleMultiplier - (Math.PI / 2);
1866 | },
1867 | getPointPosition: function(index, distanceFromCenter) {
1868 | var thisAngle = this.getIndexAngle(index);
1869 | return {
1870 | x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
1871 | y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
1872 | };
1873 | },
1874 | draw: function() {
1875 | if (this.display) {
1876 | var ctx = this.ctx;
1877 | each(this.yLabels, function(label, index) {
1878 | // Don't draw a centre value
1879 | if (index > 0) {
1880 | var yCenterOffset = index * (this.drawingArea / this.steps),
1881 | yHeight = this.yCenter - yCenterOffset,
1882 | pointPosition;
1883 |
1884 | // Draw circular lines around the scale
1885 | if (this.lineWidth > 0) {
1886 | ctx.strokeStyle = this.lineColor;
1887 | ctx.lineWidth = this.lineWidth;
1888 |
1889 | if (this.lineArc) {
1890 | ctx.beginPath();
1891 | ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
1892 | ctx.closePath();
1893 | ctx.stroke();
1894 | } else {
1895 | ctx.beginPath();
1896 | for (var i = 0; i < this.valuesCount; i++) {
1897 | pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
1898 | if (i === 0) {
1899 | ctx.moveTo(pointPosition.x, pointPosition.y);
1900 | } else {
1901 | ctx.lineTo(pointPosition.x, pointPosition.y);
1902 | }
1903 | }
1904 | ctx.closePath();
1905 | ctx.stroke();
1906 | }
1907 | }
1908 | if (this.showLabels) {
1909 | ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
1910 | if (this.showLabelBackdrop) {
1911 | var labelWidth = ctx.measureText(label).width;
1912 | ctx.fillStyle = this.backdropColor;
1913 | ctx.fillRect(
1914 | this.xCenter - labelWidth / 2 - this.backdropPaddingX,
1915 | yHeight - this.fontSize / 2 - this.backdropPaddingY,
1916 | labelWidth + this.backdropPaddingX * 2,
1917 | this.fontSize + this.backdropPaddingY * 2
1918 | );
1919 | }
1920 | ctx.textAlign = 'center';
1921 | ctx.textBaseline = "middle";
1922 | ctx.fillStyle = this.fontColor;
1923 | ctx.fillText(label, this.xCenter, yHeight);
1924 | }
1925 | }
1926 | }, this);
1927 |
1928 | if (!this.lineArc) {
1929 | ctx.lineWidth = this.angleLineWidth;
1930 | ctx.strokeStyle = this.angleLineColor;
1931 | for (var i = this.valuesCount - 1; i >= 0; i--) {
1932 | if (this.angleLineWidth > 0) {
1933 | var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
1934 | ctx.beginPath();
1935 | ctx.moveTo(this.xCenter, this.yCenter);
1936 | ctx.lineTo(outerPosition.x, outerPosition.y);
1937 | ctx.stroke();
1938 | ctx.closePath();
1939 | }
1940 | // Extra 3px out for some label spacing
1941 | var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
1942 | ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
1943 | ctx.fillStyle = this.pointLabelFontColor;
1944 |
1945 | var labelsCount = this.labels.length,
1946 | halfLabelsCount = this.labels.length / 2,
1947 | quarterLabelsCount = halfLabelsCount / 2,
1948 | upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
1949 | exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
1950 | if (i === 0) {
1951 | ctx.textAlign = 'center';
1952 | } else if (i === halfLabelsCount) {
1953 | ctx.textAlign = 'center';
1954 | } else if (i < halfLabelsCount) {
1955 | ctx.textAlign = 'left';
1956 | } else {
1957 | ctx.textAlign = 'right';
1958 | }
1959 |
1960 | // Set the correct text baseline based on outer positioning
1961 | if (exactQuarter) {
1962 | ctx.textBaseline = 'middle';
1963 | } else if (upperHalf) {
1964 | ctx.textBaseline = 'bottom';
1965 | } else {
1966 | ctx.textBaseline = 'top';
1967 | }
1968 |
1969 | ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
1970 | }
1971 | }
1972 | }
1973 | }
1974 | });
1975 |
1976 | // Attach global event to resize each chart instance when the browser resizes
1977 | helpers.addEvent("window", "resize", (function() {
1978 | // Basic debounce of resize function so it doesn't hurt performance when resizing browser.
1979 | return noop;
1980 | // var timeout;
1981 | // return function(){
1982 | // clearTimeout(timeout);
1983 | // timeout = setTimeout(function(){
1984 | // each(Chart.instances,function(instance){
1985 | // // If the responsive flag is set in the chart instance config
1986 | // // Cascade the resize event down to the chart.
1987 | // if (instance.options.responsive){
1988 | // instance.resize(instance.render, true);
1989 | // }
1990 | // });
1991 | // }, 50);
1992 | // };
1993 | })());
1994 |
1995 |
1996 | if (amd) {
1997 | define(function() {
1998 | return Chart;
1999 | });
2000 | } else if (typeof module === 'object' && module.exports) {
2001 | module.exports = Chart;
2002 | }
2003 |
2004 | // root.Chart = Chart;
2005 | root = Chart;
2006 |
2007 | // Chart.noConflict = function(){
2008 | // root.Chart = previous;
2009 | // return Chart;
2010 | // };
2011 |
2012 | }).call(this);
2013 |
2014 | //Bar Chart
2015 | (function() {
2016 | // "use strict";
2017 |
2018 | // var root = this,
2019 | var Chart = root, //.Chart,
2020 | helpers = Chart.helpers;
2021 |
2022 |
2023 | var defaultConfig = {
2024 | //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
2025 | scaleBeginAtZero: true,
2026 |
2027 | //Boolean - Whether grid lines are shown across the chart
2028 | scaleShowGridLines: true,
2029 |
2030 | //String - Colour of the grid lines
2031 | scaleGridLineColor: "rgba(0,0,0,.05)",
2032 |
2033 | //Number - Width of the grid lines
2034 | scaleGridLineWidth: 1,
2035 |
2036 | //Boolean - If there is a stroke on each bar
2037 | barShowStroke: true,
2038 |
2039 | //Number - Pixel width of the bar stroke
2040 | barStrokeWidth: 2,
2041 |
2042 | //Number - Spacing between each of the X value sets
2043 | barValueSpacing: 5,
2044 |
2045 | //Number - Spacing between data sets within X values
2046 | barDatasetSpacing: 1,
2047 |
2048 | //String - A legend template
2049 | legendTemplate: "
-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"
2050 |
2051 | };
2052 |
2053 |
2054 | Chart.Type.extend({
2055 | name: "Bar",
2056 | defaults: defaultConfig,
2057 | initialize: function(data) {
2058 |
2059 | //Expose options as a scope variable here so we can access it in the ScaleClass
2060 | var options = this.options;
2061 |
2062 | this.ScaleClass = Chart.Scale.extend({
2063 | offsetGridLines: true,
2064 | calculateBarX: function(datasetCount, datasetIndex, barIndex) {
2065 | //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
2066 | var xWidth = this.calculateBaseWidth(),
2067 | xAbsolute = this.calculateX(barIndex) - (xWidth / 2),
2068 | barWidth = this.calculateBarWidth(datasetCount);
2069 |
2070 | return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth / 2;
2071 | },
2072 | calculateBaseWidth: function() {
2073 | return (this.calculateX(1) - this.calculateX(0)) - (2 * options.barValueSpacing);
2074 | },
2075 | calculateBarWidth: function(datasetCount) {
2076 | //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
2077 | var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
2078 |
2079 | return (baseWidth / datasetCount);
2080 | }
2081 | });
2082 |
2083 | this.datasets = [];
2084 |
2085 | //Set up tooltip events on the chart
2086 | // if (this.options.showTooltips){
2087 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2088 | // var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
2089 |
2090 | // this.eachBars(function(bar){
2091 | // bar.restore(['fillColor', 'strokeColor']);
2092 | // });
2093 | // helpers.each(activeBars, function(activeBar){
2094 | // activeBar.fillColor = activeBar.highlightFill;
2095 | // activeBar.strokeColor = activeBar.highlightStroke;
2096 | // });
2097 | // this.showTooltip(activeBars);
2098 | // });
2099 | // }
2100 |
2101 | //Declare the extension of the default point, to cater for the options passed in to the constructor
2102 | this.BarClass = Chart.Rectangle.extend({
2103 | strokeWidth: this.options.barStrokeWidth,
2104 | showStroke: this.options.barShowStroke,
2105 | ctx: this.chart.ctx
2106 | });
2107 |
2108 | //Iterate through each of the datasets, and build this into a property of the chart
2109 | helpers.each(data.datasets, function(dataset, datasetIndex) {
2110 |
2111 | var datasetObject = {
2112 | label: dataset.label || null,
2113 | fillColor: dataset.fillColor,
2114 | strokeColor: dataset.strokeColor,
2115 | bars: []
2116 | };
2117 |
2118 | this.datasets.push(datasetObject);
2119 |
2120 | helpers.each(dataset.data, function(dataPoint, index) {
2121 | //Add a new point for each piece of data, passing any required data to draw.
2122 | datasetObject.bars.push(new this.BarClass({
2123 | value: dataPoint,
2124 | label: data.labels[index],
2125 | datasetLabel: dataset.label,
2126 | strokeColor: dataset.strokeColor,
2127 | fillColor: dataset.fillColor,
2128 | highlightFill: dataset.highlightFill || dataset.fillColor,
2129 | highlightStroke: dataset.highlightStroke || dataset.strokeColor
2130 | }));
2131 | }, this);
2132 |
2133 | }, this);
2134 |
2135 | this.buildScale(data.labels);
2136 |
2137 | this.BarClass.prototype.base = this.scale.endPoint;
2138 |
2139 | this.eachBars(function(bar, index, datasetIndex) {
2140 | helpers.extend(bar, {
2141 | width: this.scale.calculateBarWidth(this.datasets.length),
2142 | x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2143 | y: this.scale.endPoint
2144 | });
2145 | bar.save();
2146 | }, this);
2147 |
2148 | // this.render();
2149 | },
2150 | bindMouseEvent: function(event) {
2151 | if (this.options.showTooltips) {
2152 | var activeBars = (event.type !== 'mouseout') ? this.getBarsAtEvent(event) : [];
2153 | this.eachBars(function(bar) {
2154 | bar.restore(['fillColor', 'strokeColor']);
2155 | });
2156 | helpers.each(activeBars, function(activeBar) {
2157 | activeBar.fillColor = activeBar.highlightFill;
2158 | activeBar.strokeColor = activeBar.highlightStroke;
2159 | });
2160 | this.showTooltip(activeBars);
2161 | }
2162 | },
2163 | update: function() {
2164 | this.scale.update();
2165 | // Reset any highlight colours before updating.
2166 | helpers.each(this.activeElements, function(activeElement) {
2167 | activeElement.restore(['fillColor', 'strokeColor']);
2168 | });
2169 |
2170 | this.eachBars(function(bar) {
2171 | bar.save();
2172 | });
2173 | this.render();
2174 | },
2175 | eachBars: function(callback) {
2176 | helpers.each(this.datasets, function(dataset, datasetIndex) {
2177 | helpers.each(dataset.bars, callback, this, datasetIndex);
2178 | }, this);
2179 | },
2180 | getBarsAtEvent: function(e) {
2181 | var barsArray = [],
2182 | eventPosition = helpers.getRelativePosition(e),
2183 | datasetIterator = function(dataset) {
2184 | barsArray.push(dataset.bars[barIndex]);
2185 | },
2186 | barIndex;
2187 |
2188 | for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
2189 | for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
2190 | if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x, eventPosition.y)) {
2191 | helpers.each(this.datasets, datasetIterator);
2192 | return barsArray;
2193 | }
2194 | }
2195 | }
2196 |
2197 | return barsArray;
2198 | },
2199 | buildScale: function(labels) {
2200 | var self = this;
2201 |
2202 | var dataTotal = function() {
2203 | var values = [];
2204 | self.eachBars(function(bar) {
2205 | values.push(bar.value);
2206 | });
2207 | return values;
2208 | };
2209 |
2210 | var scaleOptions = {
2211 | templateString: this.options.scaleLabel,
2212 | height: this.chart.height,
2213 | width: this.chart.width,
2214 | ctx: this.chart.ctx,
2215 | textColor: this.options.scaleFontColor,
2216 | fontSize: this.options.scaleFontSize,
2217 | fontStyle: this.options.scaleFontStyle,
2218 | fontFamily: this.options.scaleFontFamily,
2219 | valuesCount: labels.length,
2220 | beginAtZero: this.options.scaleBeginAtZero,
2221 | integersOnly: this.options.scaleIntegersOnly,
2222 | calculateYRange: function(currentHeight) {
2223 | var updatedRanges = helpers.calculateScaleRange(
2224 | dataTotal(),
2225 | currentHeight,
2226 | this.fontSize,
2227 | this.beginAtZero,
2228 | this.integersOnly
2229 | );
2230 | helpers.extend(this, updatedRanges);
2231 | },
2232 | xLabels: labels,
2233 | font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2234 | lineWidth: this.options.scaleLineWidth,
2235 | lineColor: this.options.scaleLineColor,
2236 | gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2237 | gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2238 | padding: (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
2239 | showLabels: this.options.scaleShowLabels,
2240 | display: this.options.showScale
2241 | };
2242 |
2243 | if (this.options.scaleOverride) {
2244 | helpers.extend(scaleOptions, {
2245 | calculateYRange: helpers.noop,
2246 | steps: this.options.scaleSteps,
2247 | stepValue: this.options.scaleStepWidth,
2248 | min: this.options.scaleStartValue,
2249 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2250 | });
2251 | }
2252 |
2253 | this.scale = new this.ScaleClass(scaleOptions);
2254 | },
2255 | addData: function(valuesArray, label) {
2256 | //Map the values array for each of the datasets
2257 | helpers.each(valuesArray, function(value, datasetIndex) {
2258 | //Add a new point for each piece of data, passing any required data to draw.
2259 | this.datasets[datasetIndex].bars.push(new this.BarClass({
2260 | value: value,
2261 | label: label,
2262 | x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount + 1),
2263 | y: this.scale.endPoint,
2264 | width: this.scale.calculateBarWidth(this.datasets.length),
2265 | base: this.scale.endPoint,
2266 | strokeColor: this.datasets[datasetIndex].strokeColor,
2267 | fillColor: this.datasets[datasetIndex].fillColor
2268 | }));
2269 | }, this);
2270 |
2271 | this.scale.addXLabel(label);
2272 | //Then re-render the chart.
2273 | this.update();
2274 | },
2275 | removeData: function() {
2276 | this.scale.removeXLabel();
2277 | //Then re-render the chart.
2278 | helpers.each(this.datasets, function(dataset) {
2279 | dataset.bars.shift();
2280 | }, this);
2281 | this.update();
2282 | },
2283 | reflow: function() {
2284 | helpers.extend(this.BarClass.prototype, {
2285 | y: this.scale.endPoint,
2286 | base: this.scale.endPoint
2287 | });
2288 | var newScaleProps = helpers.extend({
2289 | height: this.chart.height,
2290 | width: this.chart.width
2291 | });
2292 | this.scale.update(newScaleProps);
2293 | },
2294 | draw: function(ease) {
2295 | var easingDecimal = ease || 1;
2296 | this.clear();
2297 |
2298 | var ctx = this.chart.ctx;
2299 |
2300 | this.scale.draw(easingDecimal);
2301 |
2302 | //Draw all the bars for each dataset
2303 | helpers.each(this.datasets, function(dataset, datasetIndex) {
2304 | helpers.each(dataset.bars, function(bar, index) {
2305 | if (bar.hasValue()) {
2306 | bar.base = this.scale.endPoint;
2307 | //Transition then draw
2308 | bar.transition({
2309 | x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2310 | y: this.scale.calculateY(bar.value),
2311 | width: this.scale.calculateBarWidth(this.datasets.length)
2312 | }, easingDecimal).draw();
2313 | }
2314 | }, this);
2315 |
2316 | }, this);
2317 | }
2318 | });
2319 |
2320 |
2321 | }).call(this);
2322 |
2323 | //Doughnut & Pie Chart
2324 | (function() {
2325 | // "use strict";
2326 |
2327 | // var root = this,
2328 | var Chart = root, //.Chart,
2329 | //Cache a local reference to Chart.helpers
2330 | helpers = Chart.helpers;
2331 |
2332 | var defaultConfig = {
2333 | //Boolean - Whether we should show a stroke on each segment
2334 | segmentShowStroke: true,
2335 |
2336 | //String - The colour of each segment stroke
2337 | segmentStrokeColor: "#fff",
2338 |
2339 | //Number - The width of each segment stroke
2340 | segmentStrokeWidth: 2,
2341 |
2342 | //The percentage of the chart that we cut out of the middle.
2343 | percentageInnerCutout: 50,
2344 |
2345 | //Number - Amount of animation steps
2346 | animationSteps: 100,
2347 |
2348 | //String - Animation easing effect
2349 | animationEasing: "easeOutBounce",
2350 |
2351 | //Boolean - Whether we animate the rotation of the Doughnut
2352 | animateRotate: true,
2353 |
2354 | //Boolean - Whether we animate scaling the Doughnut from the centre
2355 | animateScale: false,
2356 |
2357 | //String - A legend template
2358 | legendTemplate: "-legend\"><% for (var i=0; i- \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
"
2359 |
2360 | };
2361 |
2362 |
2363 | Chart.Type.extend({
2364 | //Passing in a name registers this chart in the Chart namespace
2365 | name: "Doughnut",
2366 | //Providing a defaults will also register the deafults in the chart namespace
2367 | defaults: defaultConfig,
2368 | //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2369 | //Config is automatically merged by the core of Chart.js, and is available at this.options
2370 | initialize: function(data) {
2371 |
2372 | //Declare segments as a static property to prevent inheriting across the Chart type prototype
2373 | this.segments = [];
2374 | this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.segmentStrokeWidth / 2) / 2;
2375 |
2376 | this.SegmentArc = Chart.Arc.extend({
2377 | ctx: this.chart.ctx,
2378 | x: this.chart.width / 2,
2379 | y: this.chart.height / 2
2380 | });
2381 |
2382 | //Set up tooltip events on the chart
2383 | // if (this.options.showTooltips){
2384 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2385 | // var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2386 |
2387 | // helpers.each(this.segments,function(segment){
2388 | // segment.restore(["fillColor"]);
2389 | // });
2390 | // helpers.each(activeSegments,function(activeSegment){
2391 | // activeSegment.fillColor = activeSegment.highlightColor;
2392 | // });
2393 | // this.showTooltip(activeSegments);
2394 | // });
2395 | // }
2396 | this.calculateTotal(data);
2397 |
2398 | helpers.each(data, function(datapoint, index) {
2399 | this.addData(datapoint, index, true);
2400 | }, this);
2401 |
2402 | // this.render();
2403 | },
2404 | bindMouseEvent: function(event) {
2405 | if (this.options.showTooltips) {
2406 | var activeSegments = (event.type !== 'mouseout') ? this.getSegmentsAtEvent(event) : [];
2407 |
2408 | helpers.each(this.segments, function(segment) {
2409 | segment.restore(["fillColor"]);
2410 | });
2411 | helpers.each(activeSegments, function(activeSegment) {
2412 | activeSegment.fillColor = activeSegment.highlightColor;
2413 | });
2414 | this.showTooltip(activeSegments);
2415 | }
2416 | },
2417 | getSegmentsAtEvent: function(e) {
2418 | var segmentsArray = [];
2419 |
2420 | var location = helpers.getRelativePosition(e);
2421 |
2422 | helpers.each(this.segments, function(segment) {
2423 | if (segment.inRange(location.x, location.y)) segmentsArray.push(segment);
2424 | }, this);
2425 | return segmentsArray;
2426 | },
2427 | addData: function(segment, atIndex, silent) {
2428 | var index = atIndex || this.segments.length;
2429 | this.segments.splice(index, 0, new this.SegmentArc({
2430 | value: segment.value,
2431 | outerRadius: (this.options.animateScale) ? 0 : this.outerRadius,
2432 | innerRadius: (this.options.animateScale) ? 0 : (this.outerRadius / 100) * this.options.percentageInnerCutout,
2433 | fillColor: segment.color,
2434 | highlightColor: segment.highlight || segment.color,
2435 | showStroke: this.options.segmentShowStroke,
2436 | strokeWidth: this.options.segmentStrokeWidth,
2437 | strokeColor: this.options.segmentStrokeColor,
2438 | startAngle: Math.PI * 1.5,
2439 | circumference: (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
2440 | label: segment.label
2441 | }));
2442 | if (!silent) {
2443 | this.reflow();
2444 | this.update();
2445 | }
2446 | },
2447 | calculateCircumference: function(value) {
2448 | return (Math.PI * 2) * (value / this.total);
2449 | },
2450 | calculateTotal: function(data) {
2451 | this.total = 0;
2452 | helpers.each(data, function(segment) {
2453 | this.total += segment.value;
2454 | }, this);
2455 | },
2456 | update: function() {
2457 | this.calculateTotal(this.segments);
2458 |
2459 | // Reset any highlight colours before updating.
2460 | helpers.each(this.activeElements, function(activeElement) {
2461 | activeElement.restore(['fillColor']);
2462 | });
2463 |
2464 | helpers.each(this.segments, function(segment) {
2465 | segment.save();
2466 | });
2467 | this.render();
2468 | },
2469 |
2470 | removeData: function(atIndex) {
2471 | var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length - 1;
2472 | this.segments.splice(indexToDelete, 1);
2473 | this.reflow();
2474 | this.update();
2475 | },
2476 |
2477 | reflow: function() {
2478 | helpers.extend(this.SegmentArc.prototype, {
2479 | x: this.chart.width / 2,
2480 | y: this.chart.height / 2
2481 | });
2482 | this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.segmentStrokeWidth / 2) / 2;
2483 | helpers.each(this.segments, function(segment) {
2484 | segment.update({
2485 | outerRadius: this.outerRadius,
2486 | innerRadius: (this.outerRadius / 100) * this.options.percentageInnerCutout
2487 | });
2488 | }, this);
2489 | },
2490 | draw: function(easeDecimal) {
2491 | var animDecimal = (easeDecimal) ? easeDecimal : 1;
2492 | this.clear();
2493 | helpers.each(this.segments, function(segment, index) {
2494 | segment.transition({
2495 | circumference: this.calculateCircumference(segment.value),
2496 | outerRadius: this.outerRadius,
2497 | innerRadius: (this.outerRadius / 100) * this.options.percentageInnerCutout
2498 | }, animDecimal);
2499 |
2500 | segment.endAngle = segment.startAngle + segment.circumference;
2501 |
2502 | segment.draw();
2503 | if (index === 0) {
2504 | segment.startAngle = Math.PI * 1.5;
2505 | }
2506 | //Check to see if it's the last segment, if not get the next and update the start angle
2507 | if (index < this.segments.length - 1) {
2508 | this.segments[index + 1].startAngle = segment.endAngle;
2509 | }
2510 | }, this);
2511 |
2512 | }
2513 | });
2514 |
2515 | Chart.types.Doughnut.extend({
2516 | name: "Pie",
2517 | defaults: helpers.merge(defaultConfig, {
2518 | percentageInnerCutout: 0
2519 | })
2520 | });
2521 |
2522 | }).call(this);
2523 |
2524 | //Line Chart
2525 | (function() {
2526 | // "use strict";
2527 |
2528 | // var root = this,
2529 | var Chart = root, //.Chart,
2530 | helpers = Chart.helpers;
2531 |
2532 | var defaultConfig = {
2533 |
2534 | ///Boolean - Whether grid lines are shown across the chart
2535 | scaleShowGridLines: true,
2536 |
2537 | //String - Colour of the grid lines
2538 | scaleGridLineColor: "rgba(0,0,0,.05)",
2539 |
2540 | //Number - Width of the grid lines
2541 | scaleGridLineWidth: 1,
2542 |
2543 | //Boolean - Whether the line is curved between points
2544 | bezierCurve: true,
2545 |
2546 | //Number - Tension of the bezier curve between points
2547 | bezierCurveTension: 0.4,
2548 |
2549 | //Boolean - Whether to show a dot for each point
2550 | pointDot: true,
2551 |
2552 | //Number - Radius of each point dot in pixels
2553 | pointDotRadius: 4,
2554 |
2555 | //Number - Pixel width of point dot stroke
2556 | pointDotStrokeWidth: 1,
2557 |
2558 | //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
2559 | pointHitDetectionRadius: 4,
2560 |
2561 | //Boolean - Whether to show a stroke for datasets
2562 | datasetStroke: true,
2563 |
2564 | //Number - Pixel width of dataset stroke
2565 | datasetStrokeWidth: 2,
2566 |
2567 | //Boolean - Whether to fill the dataset with a colour
2568 | datasetFill: true,
2569 |
2570 | //String - A legend template
2571 | legendTemplate: "-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"
2572 |
2573 | };
2574 |
2575 |
2576 | Chart.Type.extend({
2577 | name: "Line",
2578 | defaults: defaultConfig,
2579 | initialize: function(data) {
2580 | //Declare the extension of the default point, to cater for the options passed in to the constructor
2581 | this.PointClass = Chart.Point.extend({
2582 | strokeWidth: this.options.pointDotStrokeWidth,
2583 | radius: this.options.pointDotRadius,
2584 | display: this.options.pointDot,
2585 | hitDetectionRadius: this.options.pointHitDetectionRadius,
2586 | ctx: this.chart.ctx,
2587 | inRange: function(mouseX) {
2588 | return (Math.pow(mouseX - this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius, 2));
2589 | }
2590 | });
2591 |
2592 | this.datasets = [];
2593 |
2594 | //Set up tooltip events on the chart
2595 | if (this.options.showTooltips) {
2596 | helpers.bindEvents(this, this.options.tooltipEvents, function(evt) {
2597 | var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
2598 | this.eachPoints(function(point) {
2599 | point.restore(['fillColor', 'strokeColor']);
2600 | });
2601 | helpers.each(activePoints, function(activePoint) {
2602 | activePoint.fillColor = activePoint.highlightFill;
2603 | activePoint.strokeColor = activePoint.highlightStroke;
2604 | });
2605 | this.showTooltip(activePoints);
2606 | });
2607 | }
2608 |
2609 | //Iterate through each of the datasets, and build this into a property of the chart
2610 | helpers.each(data.datasets, function(dataset) {
2611 |
2612 | var datasetObject = {
2613 | label: dataset.label || null,
2614 | fillColor: dataset.fillColor,
2615 | strokeColor: dataset.strokeColor,
2616 | pointColor: dataset.pointColor,
2617 | pointStrokeColor: dataset.pointStrokeColor,
2618 | points: []
2619 | };
2620 |
2621 | this.datasets.push(datasetObject);
2622 |
2623 |
2624 | helpers.each(dataset.data, function(dataPoint, index) {
2625 | //Add a new point for each piece of data, passing any required data to draw.
2626 | datasetObject.points.push(new this.PointClass({
2627 | value: dataPoint,
2628 | label: data.labels[index],
2629 | datasetLabel: dataset.label,
2630 | strokeColor: dataset.pointStrokeColor,
2631 | fillColor: dataset.pointColor,
2632 | highlightFill: dataset.pointHighlightFill || dataset.pointColor,
2633 | highlightStroke: dataset.pointHighlightStroke || dataset.pointStrokeColor
2634 | }));
2635 | }, this);
2636 |
2637 | this.buildScale(data.labels);
2638 |
2639 |
2640 | this.eachPoints(function(point, index) {
2641 | helpers.extend(point, {
2642 | x: this.scale.calculateX(index),
2643 | y: this.scale.endPoint
2644 | });
2645 | point.save();
2646 | }, this);
2647 |
2648 | }, this);
2649 |
2650 | // this.render();
2651 | },
2652 | bindMouseEvent: function(event) {
2653 | //Set up tooltip events on the chart
2654 | if (this.options.showTooltips) {
2655 | var activePoints = (event.type !== 'mouseout') ? this.getPointsAtEvent(event) : [];
2656 | this.eachPoints(function(point) {
2657 | point.restore(['fillColor', 'strokeColor']);
2658 | });
2659 | helpers.each(activePoints, function(activePoint) {
2660 | activePoint.fillColor = activePoint.highlightFill;
2661 | activePoint.strokeColor = activePoint.highlightStroke;
2662 | });
2663 | this.showTooltip(activePoints);
2664 | }
2665 | },
2666 | update: function() {
2667 | this.scale.update();
2668 | // Reset any highlight colours before updating.
2669 | helpers.each(this.activeElements, function(activeElement) {
2670 | activeElement.restore(['fillColor', 'strokeColor']);
2671 | });
2672 | this.eachPoints(function(point) {
2673 | point.save();
2674 | });
2675 | this.render();
2676 | },
2677 | eachPoints: function(callback) {
2678 | helpers.each(this.datasets, function(dataset) {
2679 | helpers.each(dataset.points, callback, this);
2680 | }, this);
2681 | },
2682 | getPointsAtEvent: function(e) {
2683 | var pointsArray = [],
2684 | eventPosition = helpers.getRelativePosition(e);
2685 | helpers.each(this.datasets, function(dataset) {
2686 | helpers.each(dataset.points, function(point) {
2687 | if (point.inRange(eventPosition.x, eventPosition.y)) pointsArray.push(point);
2688 | });
2689 | }, this);
2690 | return pointsArray;
2691 | },
2692 | buildScale: function(labels) {
2693 | var self = this;
2694 |
2695 | var dataTotal = function() {
2696 | var values = [];
2697 | self.eachPoints(function(point) {
2698 | values.push(point.value);
2699 | });
2700 |
2701 | return values;
2702 | };
2703 |
2704 | var scaleOptions = {
2705 | templateString: this.options.scaleLabel,
2706 | height: this.chart.height,
2707 | width: this.chart.width,
2708 | ctx: this.chart.ctx,
2709 | textColor: this.options.scaleFontColor,
2710 | fontSize: this.options.scaleFontSize,
2711 | fontStyle: this.options.scaleFontStyle,
2712 | fontFamily: this.options.scaleFontFamily,
2713 | valuesCount: labels.length,
2714 | beginAtZero: this.options.scaleBeginAtZero,
2715 | integersOnly: this.options.scaleIntegersOnly,
2716 | calculateYRange: function(currentHeight) {
2717 | var updatedRanges = helpers.calculateScaleRange(
2718 | dataTotal(),
2719 | currentHeight,
2720 | this.fontSize,
2721 | this.beginAtZero,
2722 | this.integersOnly
2723 | );
2724 | helpers.extend(this, updatedRanges);
2725 | },
2726 | xLabels: labels,
2727 | font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2728 | lineWidth: this.options.scaleLineWidth,
2729 | lineColor: this.options.scaleLineColor,
2730 | gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2731 | gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2732 | padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
2733 | showLabels: this.options.scaleShowLabels,
2734 | display: this.options.showScale
2735 | };
2736 |
2737 | if (this.options.scaleOverride) {
2738 | helpers.extend(scaleOptions, {
2739 | calculateYRange: helpers.noop,
2740 | steps: this.options.scaleSteps,
2741 | stepValue: this.options.scaleStepWidth,
2742 | min: this.options.scaleStartValue,
2743 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2744 | });
2745 | }
2746 |
2747 |
2748 | this.scale = new Chart.Scale(scaleOptions);
2749 | },
2750 | addData: function(valuesArray, label) {
2751 | //Map the values array for each of the datasets
2752 |
2753 | helpers.each(valuesArray, function(value, datasetIndex) {
2754 | //Add a new point for each piece of data, passing any required data to draw.
2755 | this.datasets[datasetIndex].points.push(new this.PointClass({
2756 | value: value,
2757 | label: label,
2758 | x: this.scale.calculateX(this.scale.valuesCount + 1),
2759 | y: this.scale.endPoint,
2760 | strokeColor: this.datasets[datasetIndex].pointStrokeColor,
2761 | fillColor: this.datasets[datasetIndex].pointColor
2762 | }));
2763 | }, this);
2764 |
2765 | this.scale.addXLabel(label);
2766 | //Then re-render the chart.
2767 | this.update();
2768 | },
2769 | removeData: function() {
2770 | this.scale.removeXLabel();
2771 | //Then re-render the chart.
2772 | helpers.each(this.datasets, function(dataset) {
2773 | dataset.points.shift();
2774 | }, this);
2775 | this.update();
2776 | },
2777 | reflow: function() {
2778 | var newScaleProps = helpers.extend({
2779 | height: this.chart.height,
2780 | width: this.chart.width
2781 | });
2782 | this.scale.update(newScaleProps);
2783 | },
2784 | draw: function(ease) {
2785 | var easingDecimal = ease || 1;
2786 | this.clear();
2787 |
2788 | var ctx = this.chart.ctx;
2789 |
2790 | // Some helper methods for getting the next/prev points
2791 | var hasValue = function(item) {
2792 | return item.value !== null;
2793 | },
2794 | nextPoint = function(point, collection, index) {
2795 | return helpers.findNextWhere(collection, hasValue, index) || point;
2796 | },
2797 | previousPoint = function(point, collection, index) {
2798 | return helpers.findPreviousWhere(collection, hasValue, index) || point;
2799 | };
2800 |
2801 | this.scale.draw(easingDecimal);
2802 |
2803 |
2804 | helpers.each(this.datasets, function(dataset) {
2805 | var pointsWithValues = helpers.where(dataset.points, hasValue);
2806 |
2807 | //Transition each point first so that the line and point drawing isn't out of sync
2808 | //We can use this extra loop to calculate the control points of this dataset also in this loop
2809 |
2810 | helpers.each(dataset.points, function(point, index) {
2811 | if (point.hasValue()) {
2812 | point.transition({
2813 | y: this.scale.calculateY(point.value),
2814 | x: this.scale.calculateX(index)
2815 | }, easingDecimal);
2816 | }
2817 | }, this);
2818 |
2819 |
2820 | // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
2821 | // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
2822 | if (this.options.bezierCurve) {
2823 | helpers.each(pointsWithValues, function(point, index) {
2824 | var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
2825 | point.controlPoints = helpers.splineCurve(
2826 | previousPoint(point, pointsWithValues, index),
2827 | point,
2828 | nextPoint(point, pointsWithValues, index),
2829 | tension
2830 | );
2831 |
2832 | // Prevent the bezier going outside of the bounds of the graph
2833 |
2834 | // Cap puter bezier handles to the upper/lower scale bounds
2835 | if (point.controlPoints.outer.y > this.scale.endPoint) {
2836 | point.controlPoints.outer.y = this.scale.endPoint;
2837 | } else if (point.controlPoints.outer.y < this.scale.startPoint) {
2838 | point.controlPoints.outer.y = this.scale.startPoint;
2839 | }
2840 |
2841 | // Cap inner bezier handles to the upper/lower scale bounds
2842 | if (point.controlPoints.inner.y > this.scale.endPoint) {
2843 | point.controlPoints.inner.y = this.scale.endPoint;
2844 | } else if (point.controlPoints.inner.y < this.scale.startPoint) {
2845 | point.controlPoints.inner.y = this.scale.startPoint;
2846 | }
2847 | }, this);
2848 | }
2849 |
2850 |
2851 | //Draw the line between all the points
2852 | ctx.lineWidth = this.options.datasetStrokeWidth;
2853 | ctx.strokeStyle = dataset.strokeColor;
2854 | ctx.beginPath();
2855 |
2856 | helpers.each(pointsWithValues, function(point, index) {
2857 | if (index === 0) {
2858 | ctx.moveTo(point.x, point.y);
2859 | } else {
2860 | if (this.options.bezierCurve) {
2861 | var previous = previousPoint(point, pointsWithValues, index);
2862 |
2863 | ctx.bezierCurveTo(
2864 | previous.controlPoints.outer.x,
2865 | previous.controlPoints.outer.y,
2866 | point.controlPoints.inner.x,
2867 | point.controlPoints.inner.y,
2868 | point.x,
2869 | point.y
2870 | );
2871 | } else {
2872 | ctx.lineTo(point.x, point.y);
2873 | }
2874 | }
2875 | }, this);
2876 |
2877 | ctx.stroke();
2878 |
2879 | if (this.options.datasetFill && pointsWithValues.length > 0) {
2880 | //Round off the line by going to the base of the chart, back to the start, then fill.
2881 | ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
2882 | ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
2883 | ctx.fillStyle = dataset.fillColor;
2884 | ctx.closePath();
2885 | ctx.fill();
2886 | }
2887 |
2888 | //Now draw the points over the line
2889 | //A little inefficient double looping, but better than the line
2890 | //lagging behind the point positions
2891 | helpers.each(pointsWithValues, function(point) {
2892 | point.draw();
2893 | });
2894 | }, this);
2895 | }
2896 | });
2897 |
2898 |
2899 | }).call(this);
2900 |
2901 | //Polar Area Chart
2902 | (function() {
2903 | // "use strict";
2904 |
2905 | // var root = this,
2906 | var Chart = root, //.Chart,
2907 | //Cache a local reference to Chart.helpers
2908 | helpers = Chart.helpers;
2909 |
2910 | var defaultConfig = {
2911 | //Boolean - Show a backdrop to the scale label
2912 | scaleShowLabelBackdrop: true,
2913 |
2914 | //String - The colour of the label backdrop
2915 | scaleBackdropColor: "rgba(255,255,255,0.75)",
2916 |
2917 | // Boolean - Whether the scale should begin at zero
2918 | scaleBeginAtZero: true,
2919 |
2920 | //Number - The backdrop padding above & below the label in pixels
2921 | scaleBackdropPaddingY: 2,
2922 |
2923 | //Number - The backdrop padding to the side of the label in pixels
2924 | scaleBackdropPaddingX: 2,
2925 |
2926 | //Boolean - Show line for each value in the scale
2927 | scaleShowLine: true,
2928 |
2929 | //Boolean - Stroke a line around each segment in the chart
2930 | segmentShowStroke: true,
2931 |
2932 | //String - The colour of the stroke on each segement.
2933 | segmentStrokeColor: "#fff",
2934 |
2935 | //Number - The width of the stroke value in pixels
2936 | segmentStrokeWidth: 2,
2937 |
2938 | //Number - Amount of animation steps
2939 | animationSteps: 100,
2940 |
2941 | //String - Animation easing effect.
2942 | animationEasing: "easeOutBounce",
2943 |
2944 | //Boolean - Whether to animate the rotation of the chart
2945 | animateRotate: true,
2946 |
2947 | //Boolean - Whether to animate scaling the chart from the centre
2948 | animateScale: false,
2949 |
2950 | //String - A legend template
2951 | legendTemplate: "-legend\"><% for (var i=0; i- \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
<%}%>
"
2952 | };
2953 |
2954 |
2955 | Chart.Type.extend({
2956 | //Passing in a name registers this chart in the Chart namespace
2957 | name: "PolarArea",
2958 | //Providing a defaults will also register the deafults in the chart namespace
2959 | defaults: defaultConfig,
2960 | //Initialize is fired when the chart is initialized - Data is passed in as a parameter
2961 | //Config is automatically merged by the core of Chart.js, and is available at this.options
2962 | initialize: function(data) {
2963 | this.segments = [];
2964 | //Declare segment class as a chart instance specific class, so it can share props for this instance
2965 | this.SegmentArc = Chart.Arc.extend({
2966 | showStroke: this.options.segmentShowStroke,
2967 | strokeWidth: this.options.segmentStrokeWidth,
2968 | strokeColor: this.options.segmentStrokeColor,
2969 | ctx: this.chart.ctx,
2970 | innerRadius: 0,
2971 | x: this.chart.width / 2,
2972 | y: this.chart.height / 2
2973 | });
2974 | this.scale = new Chart.RadialScale({
2975 | display: this.options.showScale,
2976 | fontStyle: this.options.scaleFontStyle,
2977 | fontSize: this.options.scaleFontSize,
2978 | fontFamily: this.options.scaleFontFamily,
2979 | fontColor: this.options.scaleFontColor,
2980 | showLabels: this.options.scaleShowLabels,
2981 | showLabelBackdrop: this.options.scaleShowLabelBackdrop,
2982 | backdropColor: this.options.scaleBackdropColor,
2983 | backdropPaddingY: this.options.scaleBackdropPaddingY,
2984 | backdropPaddingX: this.options.scaleBackdropPaddingX,
2985 | lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
2986 | lineColor: this.options.scaleLineColor,
2987 | lineArc: true,
2988 | width: this.chart.width,
2989 | height: this.chart.height,
2990 | xCenter: this.chart.width / 2,
2991 | yCenter: this.chart.height / 2,
2992 | ctx: this.chart.ctx,
2993 | templateString: this.options.scaleLabel,
2994 | valuesCount: data.length
2995 | });
2996 |
2997 | this.updateScaleRange(data);
2998 |
2999 | this.scale.update();
3000 |
3001 | helpers.each(data, function(segment, index) {
3002 | this.addData(segment, index, true);
3003 | }, this);
3004 |
3005 | //Set up tooltip events on the chart
3006 | // if (this.options.showTooltips){
3007 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3008 | // var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
3009 | // helpers.each(this.segments,function(segment){
3010 | // segment.restore(["fillColor"]);
3011 | // });
3012 | // helpers.each(activeSegments,function(activeSegment){
3013 | // activeSegment.fillColor = activeSegment.highlightColor;
3014 | // });
3015 | // this.showTooltip(activeSegments);
3016 | // });
3017 | // }
3018 |
3019 | // this.render();
3020 | },
3021 | bindMouseEvent: function(event) {
3022 | //Set up tooltip events on the chart
3023 | if (this.options.showTooltips) {
3024 | var activeSegments = (event.type !== 'mouseout') ? this.getSegmentsAtEvent(event) : [];
3025 | helpers.each(this.segments, function(segment) {
3026 | segment.restore(["fillColor"]);
3027 | });
3028 | helpers.each(activeSegments, function(activeSegment) {
3029 | activeSegment.fillColor = activeSegment.highlightColor;
3030 | });
3031 | this.showTooltip(activeSegments);
3032 | }
3033 | },
3034 | getSegmentsAtEvent: function(e) {
3035 | var segmentsArray = [];
3036 |
3037 | var location = helpers.getRelativePosition(e);
3038 |
3039 | helpers.each(this.segments, function(segment) {
3040 | if (segment.inRange(location.x, location.y)) segmentsArray.push(segment);
3041 | }, this);
3042 | return segmentsArray;
3043 | },
3044 | addData: function(segment, atIndex, silent) {
3045 | var index = atIndex || this.segments.length;
3046 |
3047 | this.segments.splice(index, 0, new this.SegmentArc({
3048 | fillColor: segment.color,
3049 | highlightColor: segment.highlight || segment.color,
3050 | label: segment.label,
3051 | value: segment.value,
3052 | outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
3053 | circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
3054 | startAngle: Math.PI * 1.5
3055 | }));
3056 | if (!silent) {
3057 | this.reflow();
3058 | this.update();
3059 | }
3060 | },
3061 | removeData: function(atIndex) {
3062 | var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length - 1;
3063 | this.segments.splice(indexToDelete, 1);
3064 | this.reflow();
3065 | this.update();
3066 | },
3067 | calculateTotal: function(data) {
3068 | this.total = 0;
3069 | helpers.each(data, function(segment) {
3070 | this.total += segment.value;
3071 | }, this);
3072 | this.scale.valuesCount = this.segments.length;
3073 | },
3074 | updateScaleRange: function(datapoints) {
3075 | var valuesArray = [];
3076 | helpers.each(datapoints, function(segment) {
3077 | valuesArray.push(segment.value);
3078 | });
3079 |
3080 | var scaleSizes = (this.options.scaleOverride) ? {
3081 | steps: this.options.scaleSteps,
3082 | stepValue: this.options.scaleStepWidth,
3083 | min: this.options.scaleStartValue,
3084 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3085 | } :
3086 | helpers.calculateScaleRange(
3087 | valuesArray,
3088 | helpers.min([this.chart.width, this.chart.height]) / 2,
3089 | this.options.scaleFontSize,
3090 | this.options.scaleBeginAtZero,
3091 | this.options.scaleIntegersOnly
3092 | );
3093 |
3094 | helpers.extend(
3095 | this.scale,
3096 | scaleSizes, {
3097 | size: helpers.min([this.chart.width, this.chart.height]),
3098 | xCenter: this.chart.width / 2,
3099 | yCenter: this.chart.height / 2
3100 | }
3101 | );
3102 |
3103 | },
3104 | update: function() {
3105 | this.calculateTotal(this.segments);
3106 |
3107 | helpers.each(this.segments, function(segment) {
3108 | segment.save();
3109 | });
3110 | this.render();
3111 | },
3112 | reflow: function() {
3113 | helpers.extend(this.SegmentArc.prototype, {
3114 | x: this.chart.width / 2,
3115 | y: this.chart.height / 2
3116 | });
3117 | this.updateScaleRange(this.segments);
3118 | this.scale.update();
3119 |
3120 | helpers.extend(this.scale, {
3121 | xCenter: this.chart.width / 2,
3122 | yCenter: this.chart.height / 2
3123 | });
3124 |
3125 | helpers.each(this.segments, function(segment) {
3126 | segment.update({
3127 | outerRadius: this.scale.calculateCenterOffset(segment.value)
3128 | });
3129 | }, this);
3130 |
3131 | },
3132 | draw: function(ease) {
3133 | var easingDecimal = ease || 1;
3134 | //Clear & draw the canvas
3135 | this.clear();
3136 | helpers.each(this.segments, function(segment, index) {
3137 | segment.transition({
3138 | circumference: this.scale.getCircumference(),
3139 | outerRadius: this.scale.calculateCenterOffset(segment.value)
3140 | }, easingDecimal);
3141 |
3142 | segment.endAngle = segment.startAngle + segment.circumference;
3143 |
3144 | // If we've removed the first segment we need to set the first one to
3145 | // start at the top.
3146 | if (index === 0) {
3147 | segment.startAngle = Math.PI * 1.5;
3148 | }
3149 |
3150 | //Check to see if it's the last segment, if not get the next and update the start angle
3151 | if (index < this.segments.length - 1) {
3152 | this.segments[index + 1].startAngle = segment.endAngle;
3153 | }
3154 | segment.draw();
3155 | }, this);
3156 | this.scale.draw();
3157 | }
3158 | });
3159 |
3160 | }).call(this);
3161 |
3162 | //Radar Chart
3163 | (function() {
3164 | // "use strict";
3165 |
3166 | // var root = this,
3167 | var Chart = root, //.Chart,
3168 | helpers = Chart.helpers;
3169 |
3170 |
3171 |
3172 | Chart.Type.extend({
3173 | name: "Radar",
3174 | defaults: {
3175 | //Boolean - Whether to show lines for each scale point
3176 | scaleShowLine: true,
3177 |
3178 | //Boolean - Whether we show the angle lines out of the radar
3179 | angleShowLineOut: true,
3180 |
3181 | //Boolean - Whether to show labels on the scale
3182 | scaleShowLabels: false,
3183 |
3184 | // Boolean - Whether the scale should begin at zero
3185 | scaleBeginAtZero: true,
3186 |
3187 | //String - Colour of the angle line
3188 | angleLineColor: "rgba(0,0,0,.1)",
3189 |
3190 | //Number - Pixel width of the angle line
3191 | angleLineWidth: 1,
3192 |
3193 | //String - Point label font declaration
3194 | pointLabelFontFamily: "'Arial'",
3195 |
3196 | //String - Point label font weight
3197 | pointLabelFontStyle: "normal",
3198 |
3199 | //Number - Point label font size in pixels
3200 | pointLabelFontSize: 10,
3201 |
3202 | //String - Point label font colour
3203 | pointLabelFontColor: "#666",
3204 |
3205 | //Boolean - Whether to show a dot for each point
3206 | pointDot: true,
3207 |
3208 | //Number - Radius of each point dot in pixels
3209 | pointDotRadius: 3,
3210 |
3211 | //Number - Pixel width of point dot stroke
3212 | pointDotStrokeWidth: 1,
3213 |
3214 | //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
3215 | pointHitDetectionRadius: 4,
3216 |
3217 | //Boolean - Whether to show a stroke for datasets
3218 | datasetStroke: true,
3219 |
3220 | //Number - Pixel width of dataset stroke
3221 | datasetStrokeWidth: 2,
3222 |
3223 | //Boolean - Whether to fill the dataset with a colour
3224 | datasetFill: true,
3225 |
3226 | //String - A legend template
3227 | legendTemplate: "-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"
3228 |
3229 | },
3230 |
3231 | initialize: function(data) {
3232 | this.PointClass = Chart.Point.extend({
3233 | strokeWidth: this.options.pointDotStrokeWidth,
3234 | radius: this.options.pointDotRadius,
3235 | display: this.options.pointDot,
3236 | hitDetectionRadius: this.options.pointHitDetectionRadius,
3237 | ctx: this.chart.ctx
3238 | });
3239 |
3240 | this.datasets = [];
3241 |
3242 | this.buildScale(data);
3243 |
3244 | //Set up tooltip events on the chart
3245 | // if (this.options.showTooltips){
3246 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3247 | // var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
3248 |
3249 | // this.eachPoints(function(point){
3250 | // point.restore(['fillColor', 'strokeColor']);
3251 | // });
3252 | // helpers.each(activePointsCollection, function(activePoint){
3253 | // activePoint.fillColor = activePoint.highlightFill;
3254 | // activePoint.strokeColor = activePoint.highlightStroke;
3255 | // });
3256 |
3257 | // this.showTooltip(activePointsCollection);
3258 | // });
3259 | // }
3260 |
3261 | //Iterate through each of the datasets, and build this into a property of the chart
3262 | helpers.each(data.datasets, function(dataset) {
3263 |
3264 | var datasetObject = {
3265 | label: dataset.label || null,
3266 | fillColor: dataset.fillColor,
3267 | strokeColor: dataset.strokeColor,
3268 | pointColor: dataset.pointColor,
3269 | pointStrokeColor: dataset.pointStrokeColor,
3270 | points: []
3271 | };
3272 |
3273 | this.datasets.push(datasetObject);
3274 |
3275 | helpers.each(dataset.data, function(dataPoint, index) {
3276 | //Add a new point for each piece of data, passing any required data to draw.
3277 | var pointPosition;
3278 | if (!this.scale.animation) {
3279 | pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3280 | }
3281 | datasetObject.points.push(new this.PointClass({
3282 | value: dataPoint,
3283 | label: data.labels[index],
3284 | datasetLabel: dataset.label,
3285 | x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
3286 | y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3287 | strokeColor: dataset.pointStrokeColor,
3288 | fillColor: dataset.pointColor,
3289 | highlightFill: dataset.pointHighlightFill || dataset.pointColor,
3290 | highlightStroke: dataset.pointHighlightStroke || dataset.pointStrokeColor
3291 | }));
3292 | }, this);
3293 |
3294 | }, this);
3295 |
3296 | // this.render();
3297 | },
3298 | bindMouseEvent: function(event) {
3299 | if (this.options.showTooltips) {
3300 | var activePointsCollection = (event.type !== 'mouseout') ? this.getPointsAtEvent(event) : [];
3301 |
3302 | this.eachPoints(function(point) {
3303 | point.restore(['fillColor', 'strokeColor']);
3304 | });
3305 | helpers.each(activePointsCollection, function(activePoint) {
3306 | activePoint.fillColor = activePoint.highlightFill;
3307 | activePoint.strokeColor = activePoint.highlightStroke;
3308 | });
3309 |
3310 | this.showTooltip(activePointsCollection);
3311 | }
3312 | },
3313 | eachPoints: function(callback) {
3314 | helpers.each(this.datasets, function(dataset) {
3315 | helpers.each(dataset.points, callback, this);
3316 | }, this);
3317 | },
3318 |
3319 | getPointsAtEvent: function(evt) {
3320 | var mousePosition = helpers.getRelativePosition(evt),
3321 | fromCenter = helpers.getAngleFromPoint({
3322 | x: this.scale.xCenter,
3323 | y: this.scale.yCenter
3324 | }, mousePosition);
3325 |
3326 | var anglePerIndex = (Math.PI * 2) / this.scale.valuesCount,
3327 | pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
3328 | activePointsCollection = [];
3329 |
3330 | // If we're at the top, make the pointIndex 0 to get the first of the array.
3331 | if (pointIndex >= this.scale.valuesCount || pointIndex < 0) {
3332 | pointIndex = 0;
3333 | }
3334 |
3335 | if (fromCenter.distance <= this.scale.drawingArea) {
3336 | helpers.each(this.datasets, function(dataset) {
3337 | activePointsCollection.push(dataset.points[pointIndex]);
3338 | });
3339 | }
3340 |
3341 | return activePointsCollection;
3342 | },
3343 |
3344 | buildScale: function(data) {
3345 | this.scale = new Chart.RadialScale({
3346 | display: this.options.showScale,
3347 | fontStyle: this.options.scaleFontStyle,
3348 | fontSize: this.options.scaleFontSize,
3349 | fontFamily: this.options.scaleFontFamily,
3350 | fontColor: this.options.scaleFontColor,
3351 | showLabels: this.options.scaleShowLabels,
3352 | showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3353 | backdropColor: this.options.scaleBackdropColor,
3354 | backdropPaddingY: this.options.scaleBackdropPaddingY,
3355 | backdropPaddingX: this.options.scaleBackdropPaddingX,
3356 | lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3357 | lineColor: this.options.scaleLineColor,
3358 | angleLineColor: this.options.angleLineColor,
3359 | angleLineWidth: (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
3360 | // Point labels at the edge of each line
3361 | pointLabelFontColor: this.options.pointLabelFontColor,
3362 | pointLabelFontSize: this.options.pointLabelFontSize,
3363 | pointLabelFontFamily: this.options.pointLabelFontFamily,
3364 | pointLabelFontStyle: this.options.pointLabelFontStyle,
3365 | height: this.chart.height,
3366 | width: this.chart.width,
3367 | xCenter: this.chart.width / 2,
3368 | yCenter: this.chart.height / 2,
3369 | ctx: this.chart.ctx,
3370 | templateString: this.options.scaleLabel,
3371 | labels: data.labels,
3372 | valuesCount: data.datasets[0].data.length
3373 | });
3374 |
3375 | this.scale.setScaleSize();
3376 | this.updateScaleRange(data.datasets);
3377 | this.scale.buildYLabels();
3378 | },
3379 | updateScaleRange: function(datasets) {
3380 | var valuesArray = (function() {
3381 | var totalDataArray = [];
3382 | helpers.each(datasets, function(dataset) {
3383 | if (dataset.data) {
3384 | totalDataArray = totalDataArray.concat(dataset.data);
3385 | } else {
3386 | helpers.each(dataset.points, function(point) {
3387 | totalDataArray.push(point.value);
3388 | });
3389 | }
3390 | });
3391 | return totalDataArray;
3392 | })();
3393 |
3394 |
3395 | var scaleSizes = (this.options.scaleOverride) ? {
3396 | steps: this.options.scaleSteps,
3397 | stepValue: this.options.scaleStepWidth,
3398 | min: this.options.scaleStartValue,
3399 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3400 | } :
3401 | helpers.calculateScaleRange(
3402 | valuesArray,
3403 | helpers.min([this.chart.width, this.chart.height]) / 2,
3404 | this.options.scaleFontSize,
3405 | this.options.scaleBeginAtZero,
3406 | this.options.scaleIntegersOnly
3407 | );
3408 |
3409 | helpers.extend(
3410 | this.scale,
3411 | scaleSizes
3412 | );
3413 |
3414 | },
3415 | addData: function(valuesArray, label) {
3416 | //Map the values array for each of the datasets
3417 | this.scale.valuesCount++;
3418 | helpers.each(valuesArray, function(value, datasetIndex) {
3419 | var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3420 | this.datasets[datasetIndex].points.push(new this.PointClass({
3421 | value: value,
3422 | label: label,
3423 | x: pointPosition.x,
3424 | y: pointPosition.y,
3425 | strokeColor: this.datasets[datasetIndex].pointStrokeColor,
3426 | fillColor: this.datasets[datasetIndex].pointColor
3427 | }));
3428 | }, this);
3429 |
3430 | this.scale.labels.push(label);
3431 |
3432 | this.reflow();
3433 |
3434 | this.update();
3435 | },
3436 | removeData: function() {
3437 | this.scale.valuesCount--;
3438 | this.scale.labels.shift();
3439 | helpers.each(this.datasets, function(dataset) {
3440 | dataset.points.shift();
3441 | }, this);
3442 | this.reflow();
3443 | this.update();
3444 | },
3445 | update: function() {
3446 | this.eachPoints(function(point) {
3447 | point.save();
3448 | });
3449 | this.reflow();
3450 | this.render();
3451 | },
3452 | reflow: function() {
3453 | helpers.extend(this.scale, {
3454 | width: this.chart.width,
3455 | height: this.chart.height,
3456 | size: helpers.min([this.chart.width, this.chart.height]),
3457 | xCenter: this.chart.width / 2,
3458 | yCenter: this.chart.height / 2
3459 | });
3460 | this.updateScaleRange(this.datasets);
3461 | this.scale.setScaleSize();
3462 | this.scale.buildYLabels();
3463 | },
3464 | draw: function(ease) {
3465 | var easeDecimal = ease || 1,
3466 | ctx = this.chart.ctx;
3467 | this.clear();
3468 | this.scale.draw();
3469 |
3470 | helpers.each(this.datasets, function(dataset) {
3471 |
3472 | //Transition each point first so that the line and point drawing isn't out of sync
3473 | helpers.each(dataset.points, function(point, index) {
3474 | if (point.hasValue()) {
3475 | point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3476 | }
3477 | }, this);
3478 |
3479 | //Draw the line between all the points
3480 | ctx.lineWidth = this.options.datasetStrokeWidth;
3481 | ctx.strokeStyle = dataset.strokeColor;
3482 | ctx.beginPath();
3483 | helpers.each(dataset.points, function(point, index) {
3484 | if (index === 0) {
3485 | ctx.moveTo(point.x, point.y);
3486 | } else {
3487 | ctx.lineTo(point.x, point.y);
3488 | }
3489 | }, this);
3490 | ctx.closePath();
3491 | ctx.stroke();
3492 |
3493 | ctx.fillStyle = dataset.fillColor;
3494 | ctx.fill();
3495 |
3496 | //Now draw the points over the line
3497 | //A little inefficient double looping, but better than the line
3498 | //lagging behind the point positions
3499 | helpers.each(dataset.points, function(point) {
3500 | if (point.hasValue()) {
3501 | point.draw();
3502 | }
3503 | });
3504 |
3505 | }, this);
3506 |
3507 | }
3508 |
3509 | });
3510 | }).call(this);
3511 |
3512 | //stack bar chart
3513 | (function() {
3514 | // "use strict";
3515 |
3516 | // var root = this,
3517 | var Chart = root, //.Chart,
3518 | helpers = Chart.helpers;
3519 |
3520 | var defaultConfig = {
3521 | scaleBeginAtZero: true,
3522 |
3523 | //Boolean - Whether grid lines are shown across the chart
3524 | scaleShowGridLines: true,
3525 |
3526 | //String - Colour of the grid lines
3527 | scaleGridLineColor: "rgba(0,0,0,.05)",
3528 |
3529 | //Number - Width of the grid lines
3530 | scaleGridLineWidth: 1,
3531 |
3532 | //Boolean - If there is a stroke on each bar
3533 | barShowStroke: true,
3534 |
3535 | //Number - Pixel width of the bar stroke
3536 | barStrokeWidth: 2,
3537 |
3538 | //Number - Spacing between each of the X value sets
3539 | barValueSpacing: 5,
3540 |
3541 | //Boolean - Whether bars should be rendered on a percentage base
3542 | relativeBars: false,
3543 |
3544 | //String - A legend template
3545 | legendTemplate: "-legend\"><% for (var i=0; i- \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
<%}%>
"
3546 |
3547 | };
3548 |
3549 | Chart.Type.extend({
3550 | name: "StackedBar",
3551 | defaults: defaultConfig,
3552 | initialize: function(data) {
3553 | //Expose options as a scope variable here so we can access it in the ScaleClass
3554 | var options = this.options;
3555 |
3556 | this.ScaleClass = Chart.Scale.extend({
3557 | offsetGridLines: true,
3558 | calculateBarX: function(barIndex) {
3559 | return this.calculateX(barIndex);
3560 | },
3561 | calculateBarY: function(datasets, dsIndex, barIndex, value) {
3562 | var offset = 0,
3563 | sum = 0;
3564 |
3565 | for (var i = 0; i < datasets.length; i++) {
3566 | sum += datasets[i].bars[barIndex].value;
3567 | }
3568 | for (i = dsIndex; i < datasets.length; i++) {
3569 | if (i === dsIndex && value) {
3570 | offset += value;
3571 | } else {
3572 | offset += datasets[i].bars[barIndex].value;
3573 | }
3574 | }
3575 |
3576 | if (options.relativeBars) {
3577 | offset = offset / sum * 100;
3578 | }
3579 |
3580 | return this.calculateY(offset);
3581 | },
3582 | calculateBaseWidth: function() {
3583 | return (this.calculateX(1) - this.calculateX(0)) - (2 * options.barValueSpacing);
3584 | },
3585 | calculateBaseHeight: function() {
3586 | return (this.calculateY(1) - this.calculateY(0));
3587 | },
3588 | calculateBarWidth: function(datasetCount) {
3589 | //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
3590 | return this.calculateBaseWidth();
3591 | },
3592 | calculateBarHeight: function(datasets, dsIndex, barIndex, value) {
3593 | var sum = 0;
3594 |
3595 | for (var i = 0; i < datasets.length; i++) {
3596 | sum += datasets[i].bars[barIndex].value;
3597 | }
3598 |
3599 | if (!value) {
3600 | value = datasets[dsIndex].bars[barIndex].value;
3601 | }
3602 |
3603 | if (options.relativeBars) {
3604 | value = value / sum * 100;
3605 | }
3606 |
3607 | return this.calculateY(value);
3608 | }
3609 | });
3610 |
3611 | this.datasets = [];
3612 |
3613 | // //Set up tooltip events on the chart
3614 | // if (this.options.showTooltips){
3615 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3616 | // var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
3617 |
3618 | // this.eachBars(function(bar){
3619 | // bar.restore(['fillColor', 'strokeColor']);
3620 | // });
3621 | // helpers.each(activeBars, function(activeBar){
3622 | // activeBar.fillColor = activeBar.highlightFill;
3623 | // activeBar.strokeColor = activeBar.highlightStroke;
3624 | // });
3625 | // this.showTooltip(activeBars);
3626 | // });
3627 | // }
3628 |
3629 | //Declare the extension of the default point, to cater for the options passed in to the constructor
3630 | this.BarClass = Chart.Rectangle.extend({
3631 | strokeWidth: this.options.barStrokeWidth,
3632 | showStroke: this.options.barShowStroke,
3633 | ctx: this.chart.ctx
3634 | });
3635 |
3636 | //Iterate through each of the datasets, and build this into a property of the chart
3637 | helpers.each(data.datasets, function(dataset, datasetIndex) {
3638 |
3639 | var datasetObject = {
3640 | label: dataset.label || null,
3641 | fillColor: dataset.fillColor,
3642 | strokeColor: dataset.strokeColor,
3643 | bars: []
3644 | };
3645 |
3646 | this.datasets.push(datasetObject);
3647 |
3648 | helpers.each(dataset.data, function(dataPoint, index) {
3649 | if (helpers.isNumber(dataPoint)) {
3650 | //Add a new point for each piece of data, passing any required data to draw.
3651 | datasetObject.bars.push(new this.BarClass({
3652 | value: dataPoint,
3653 | label: data.labels[index],
3654 | datasetLabel: dataset.label,
3655 | strokeColor: dataset.strokeColor,
3656 | fillColor: dataset.fillColor,
3657 | highlightFill: dataset.highlightFill || dataset.fillColor,
3658 | highlightStroke: dataset.highlightStroke || dataset.strokeColor
3659 | }));
3660 | }
3661 | }, this);
3662 |
3663 | }, this);
3664 |
3665 | this.buildScale(data.labels);
3666 |
3667 | this.eachBars(function(bar, index, datasetIndex) {
3668 | helpers.extend(bar, {
3669 | base: this.scale.endPoint,
3670 | height: 0,
3671 | width: this.scale.calculateBarWidth(this.datasets.length),
3672 | x: this.scale.calculateBarX(index),
3673 | y: this.scale.endPoint
3674 | });
3675 | bar.save();
3676 | }, this);
3677 |
3678 | // this.render();
3679 | },
3680 |
3681 | bindMouseEvent: function(event) {
3682 | //Set up tooltip events on the chart
3683 | if (this.options.showTooltips) {
3684 | // helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3685 | var activeBars = (event.type !== 'mouseout') ? this.getBarsAtEvent(event) : [];
3686 |
3687 | this.eachBars(function(bar) {
3688 | bar.restore(['fillColor', 'strokeColor']);
3689 | });
3690 | helpers.each(activeBars, function(activeBar) {
3691 | activeBar.fillColor = activeBar.highlightFill;
3692 | activeBar.strokeColor = activeBar.highlightStroke;
3693 | });
3694 | this.showTooltip(activeBars);
3695 | // });
3696 | }
3697 | },
3698 |
3699 | update: function() {
3700 | this.scale.update();
3701 | // Reset any highlight colours before updating.
3702 | helpers.each(this.activeElements, function(activeElement) {
3703 | activeElement.restore(['fillColor', 'strokeColor']);
3704 | });
3705 |
3706 | this.eachBars(function(bar) {
3707 | bar.save();
3708 | });
3709 | this.render();
3710 | },
3711 | eachBars: function(callback) {
3712 | helpers.each(this.datasets, function(dataset, datasetIndex) {
3713 | helpers.each(dataset.bars, callback, this, datasetIndex);
3714 | }, this);
3715 | },
3716 | getBarsAtEvent: function(e) {
3717 | var barsArray = [],
3718 | eventPosition = helpers.getRelativePosition(e),
3719 | datasetIterator = function(dataset) {
3720 | barsArray.push(dataset.bars[barIndex]);
3721 | },
3722 | barIndex;
3723 |
3724 | for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
3725 | for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
3726 | if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x, eventPosition.y)) {
3727 | helpers.each(this.datasets, datasetIterator);
3728 | return barsArray;
3729 | }
3730 | }
3731 | }
3732 |
3733 | return barsArray;
3734 | },
3735 | buildScale: function(labels) {
3736 | var self = this;
3737 |
3738 | var dataTotal = function() {
3739 | var values = [];
3740 | helpers.each(self.datasets, function(dataset) {
3741 | helpers.each(dataset.bars, function(bar, barIndex) {
3742 | if (!values[barIndex]) values[barIndex] = 0;
3743 | if (self.options.relativeBars) {
3744 | values[barIndex] = 100;
3745 | } else {
3746 | values[barIndex] += bar.value;
3747 | }
3748 | });
3749 | });
3750 | return values;
3751 | };
3752 |
3753 | var scaleOptions = {
3754 | templateString: this.options.scaleLabel,
3755 | height: this.chart.height,
3756 | width: this.chart.width,
3757 | ctx: this.chart.ctx,
3758 | textColor: this.options.scaleFontColor,
3759 | fontSize: this.options.scaleFontSize,
3760 | fontStyle: this.options.scaleFontStyle,
3761 | fontFamily: this.options.scaleFontFamily,
3762 | valuesCount: labels.length,
3763 | beginAtZero: this.options.scaleBeginAtZero,
3764 | integersOnly: this.options.scaleIntegersOnly,
3765 | calculateYRange: function(currentHeight) {
3766 | var updatedRanges = helpers.calculateScaleRange(
3767 | dataTotal(),
3768 | currentHeight,
3769 | this.fontSize,
3770 | this.beginAtZero,
3771 | this.integersOnly
3772 | );
3773 | helpers.extend(this, updatedRanges);
3774 | },
3775 | xLabels: labels,
3776 | font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
3777 | lineWidth: this.options.scaleLineWidth,
3778 | lineColor: this.options.scaleLineColor,
3779 | gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
3780 | gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
3781 | padding: (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
3782 | showLabels: this.options.scaleShowLabels,
3783 | display: this.options.showScale
3784 | };
3785 |
3786 | if (this.options.scaleOverride) {
3787 | helpers.extend(scaleOptions, {
3788 | calculateYRange: helpers.noop,
3789 | steps: this.options.scaleSteps,
3790 | stepValue: this.options.scaleStepWidth,
3791 | min: this.options.scaleStartValue,
3792 | max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3793 | });
3794 | }
3795 |
3796 | this.scale = new this.ScaleClass(scaleOptions);
3797 | },
3798 | addData: function(valuesArray, label) {
3799 | //Map the values array for each of the datasets
3800 | helpers.each(valuesArray, function(value, datasetIndex) {
3801 | if (helpers.isNumber(value)) {
3802 | //Add a new point for each piece of data, passing any required data to draw.
3803 | this.datasets[datasetIndex].bars.push(new this.BarClass({
3804 | value: value,
3805 | label: label,
3806 | x: this.scale.calculateBarX(this.scale.valuesCount + 1),
3807 | y: this.scale.endPoint,
3808 | width: this.scale.calculateBarWidth(this.datasets.length),
3809 | base: this.scale.endPoint,
3810 | strokeColor: this.datasets[datasetIndex].strokeColor,
3811 | fillColor: this.datasets[datasetIndex].fillColor
3812 | }));
3813 | }
3814 | }, this);
3815 |
3816 | this.scale.addXLabel(label);
3817 | //Then re-render the chart.
3818 | this.update();
3819 | },
3820 | insertData: function(valuesArray, label, indexToAdd) {
3821 | //Map the values array for each of the datasets
3822 | helpers.each(valuesArray, function(value, datasetIndex) {
3823 | if (helpers.isNumber(value)) {
3824 | //Add a new point for each piece of data, passing any required data to draw.
3825 | this.datasets[datasetIndex].bars.splice(indexToAdd, 0, new this.BarClass({
3826 | value: value,
3827 | label: label,
3828 | x: this.scale.calculateBarX(this.scale.valuesCount + 1),
3829 | y: this.scale.endPoint,
3830 | width: this.scale.calculateBarWidth(this.datasets.length),
3831 | base: this.scale.endPoint,
3832 | strokeColor: this.datasets[datasetIndex].strokeColor,
3833 | fillColor: this.datasets[datasetIndex].fillColor
3834 | }));
3835 | }
3836 | }, this);
3837 |
3838 | this.scale.addXLabel(label);
3839 | //Then re-render the chart.
3840 | this.update();
3841 | },
3842 | removeData: function() {
3843 | this.scale.removeXLabel();
3844 | //Then re-render the chart.
3845 | helpers.each(this.datasets, function(dataset) {
3846 | dataset.bars.shift();
3847 | }, this);
3848 | this.update();
3849 | },
3850 | removeAtIndex: function(indexToRemove) {
3851 | this.scale.removeXLabelAtIndex(indexToRemove);
3852 | //Then re-render the chart.
3853 | helpers.each(this.datasets, function(dataset) {
3854 | dataset.bars.splice(indexToRemove, 1);
3855 | }, this);
3856 | this.update();
3857 | },
3858 | reflow: function() {
3859 | helpers.extend(this.BarClass.prototype, {
3860 | y: this.scale.endPoint,
3861 | base: this.scale.endPoint
3862 | });
3863 | var newScaleProps = helpers.extend({
3864 | height: this.chart.height,
3865 | width: this.chart.width
3866 | });
3867 | this.scale.update(newScaleProps);
3868 | },
3869 | draw: function(ease) {
3870 | var easingDecimal = ease || 1;
3871 | this.clear();
3872 |
3873 | var ctx = this.chart.ctx;
3874 |
3875 | this.scale.draw(easingDecimal);
3876 |
3877 | //Draw all the bars for each dataset
3878 | helpers.each(this.datasets, function(dataset, datasetIndex) {
3879 | helpers.each(dataset.bars, function(bar, index) {
3880 | var y = this.scale.calculateBarY(this.datasets, datasetIndex, index, bar.value),
3881 | height = this.scale.calculateBarHeight(this.datasets, datasetIndex, index, bar.value);
3882 |
3883 | //Transition then draw
3884 | bar.transition({
3885 | base: this.scale.endPoint - (Math.abs(height) - Math.abs(y)),
3886 | x: this.scale.calculateBarX(index),
3887 | y: Math.abs(y),
3888 | height: Math.abs(height),
3889 | width: this.scale.calculateBarWidth(this.datasets.length)
3890 | }, easingDecimal).draw();
3891 | }, this);
3892 | }, this);
3893 | }
3894 | });
3895 | }).call(this);
3896 |
--------------------------------------------------------------------------------
/QChartJs.qml:
--------------------------------------------------------------------------------
1 | // QChartJs.js ---
2 | //
3 | // Author: Shuirna Wang
4 | //
5 | //
6 | import QtQuick 2.0
7 |
8 | import "Chart.js" as Chart
9 |
10 | Canvas {
11 | id: canvas
12 | width: 800
13 | height: 400
14 | property var chart
15 | property var chartRenderHandler
16 | property string chartType
17 | property double chartAnimationProgress : 0.1
18 | property bool animation: true
19 | property alias chartAnimationEasing: chartAnimator.easing.type;
20 | property alias chartAnimationDuration: chartAnimator.duration;
21 | property var chartData
22 | property var chartOptions: ({})
23 | property QtObject paintType: QtObject {
24 | property string type: "INIT"
25 | }
26 |
27 | function requestInitPaint() {
28 | paintType.type = "INIT";
29 | canvas.requestPaint();
30 | }
31 |
32 | function requestToolTipPaint() {
33 | paintType.type = "TOOLTIP";
34 | canvas.requestPaint();
35 | }
36 |
37 | onPaint: {
38 | if(!chartRenderHandler) {
39 | chartRenderHandler = Chart.newChartInstance(getContext("2d"), chartData, chartOptions, chartType);
40 | if(animation) {
41 | chartAnimator.start();
42 | } else {
43 | chartAnimationProgress = 1;
44 | }
45 | }
46 | if(paintType.type === "INIT") {
47 | chartRenderHandler.draw(chartAnimationProgress);
48 | } else if(paintType.type === "TOOLTIP") {
49 | chartRenderHandler.bindMouseEvent(event.mouseEvent);
50 | } else {
51 | console.log("Not supported paint type: " + paintType.type);
52 | }
53 | }
54 |
55 | onChartAnimationProgressChanged: {
56 | requestInitPaint();
57 | }
58 |
59 | MouseArea {
60 | id: event
61 | anchors.fill: parent
62 | hoverEnabled: true
63 | enabled: true
64 | property QtObject mouseEvent: QtObject {
65 | property int left: 0
66 | property int top: 0
67 | property int clientX: 0
68 | property int clientY: 0
69 | property string type: ""
70 | }
71 | onPositionChanged: {
72 | mouseEvent.type = "mousemove"
73 | mouseEvent.clientX = mouse.x;
74 | mouseEvent.clientY = mouse.y;
75 | mouseEvent.left = 0;
76 | mouseEvent.top = 0;
77 | canvas.requestToolTipPaint();
78 | }
79 | onExited: {
80 | mouseEvent.type = "mouseout"
81 | mouseEvent.clientX = 0;
82 | mouseEvent.clientY = 0;
83 | mouseEvent.left = 0;
84 | mouseEvent.top = 0;
85 | canvas.requestToolTipPaint();
86 | }
87 | }
88 |
89 | PropertyAnimation {
90 | id: chartAnimator;
91 | target: canvas;
92 | property: "chartAnimationProgress";
93 | alwaysRunToEnd: true
94 | to: 1;
95 | duration: 500;
96 | easing.type: Easing.InOutElastic;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/QChartJsTypes.js:
--------------------------------------------------------------------------------
1 | var QChartJSTypes = {
2 | BAR: "Bar",
3 | LINE: "Line",
4 | DOUGHNUT: "Doughnut",
5 | PIE: "Pie",
6 | POLARAREA: "PolarArea",
7 | RADAR: "Radar",
8 | STACKED_BAR:"StackedBar"
9 | }
10 |
--------------------------------------------------------------------------------
/QMLChartData.js:
--------------------------------------------------------------------------------
1 | // QMLChartData.js ---
2 | //
3 | // Author: Julien Wintz
4 | // Created: Thu Feb 13 23:43:13 2014 (+0100)
5 | // Version:
6 | // Last-Updated: Fri Sep 26
7 | // By: Shuirna Wang
8 | //
9 |
10 | // Change Log:
11 | // update all options
12 |
13 | // /////////////////////////////////////////////////////////////////
14 | // Line Chart Data Sample
15 | // /////////////////////////////////////////////////////////////////
16 | var randomScalingFactor = function(){ return Math.round(Math.random()*100)};
17 |
18 | var ChartLineData = {
19 | labels: ["January","February","March","April","May","June","July","January","February","March","April","May","June","July"],
20 | datasets: [{
21 | fillColor : "rgba(220,220,220,0.2)",
22 | strokeColor : "rgba(220,220,220,1)",
23 | pointColor : "rgba(220,220,220,1)",
24 | pointStrokeColor : "#fff",
25 | pointHighlightFill : "#fff",
26 | pointHighlightStroke : "rgba(220,220,220,1)",
27 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
28 |
29 | }/*, {
30 | fillColor: "rgba(151,187,205,0.5)",
31 | strokeColor: "rgba(151,187,205,1)",
32 | pointColor: "rgba(151,187,205,1)",
33 | pointStrokeColor: "#ffffff",
34 | pointHighlightFill : "#fff",
35 | pointHighlightStroke : "rgba(220,220,220,1)",
36 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
37 | }*/]
38 | }
39 |
40 | // /////////////////////////////////////////////////////////////////
41 | // Polar Chart Data Sample
42 | // /////////////////////////////////////////////////////////////////
43 | var ChartPolarData = [
44 | {
45 | value: 300,
46 | color:"#F7464A",
47 | highlight: "#FF5A5E",
48 | label: "Red"
49 | },
50 | {
51 | value: 50,
52 | color: "#46BFBD",
53 | highlight: "#5AD3D1",
54 | label: "Green"
55 | },
56 | {
57 | value: 100,
58 | color: "#FDB45C",
59 | highlight: "#FFC870",
60 | label: "Yellow"
61 | },
62 | {
63 | value: 40,
64 | color: "#949FB1",
65 | highlight: "#A8B3C5",
66 | label: "Grey"
67 | },
68 | {
69 | value: 120,
70 | color: "#4D5360",
71 | highlight: "#616774",
72 | label: "Dark Grey"
73 | }
74 |
75 | ];
76 |
77 | // /////////////////////////////////////////////////////////////////
78 | // Radar Chart Data Sample
79 | // /////////////////////////////////////////////////////////////////
80 |
81 | var ChartRadarData = {
82 | labels: ["Eating","Drinking","Sleeping","Designing","Coding","Partying","Running"],
83 | datasets: [{
84 | fillColor: "rgba(220,220,220,0.5)",
85 | strokeColor: "rgba(220,220,220,1)",
86 | pointColor: "rgba(220,220,220,1)",
87 | pointStrokeColor: "#fff",
88 | data: [65,59,90,81,56,55,40]
89 | }, {
90 | fillColor: "rgba(151,187,205,0.5)",
91 | strokeColor: "rgba(151,187,205,1)",
92 | pointColor: "rgba(151,187,205,1)",
93 | pointStrokeColor: "#fff",
94 | data: [28,48,40,19,96,27,100]
95 | }]
96 | }
97 |
98 | // /////////////////////////////////////////////////////////////////
99 | // Pie Chart Data Sample
100 | // /////////////////////////////////////////////////////////////////
101 |
102 | var ChartPieData = [
103 | {
104 | value: 300,
105 | color:"#F7464A",
106 | highlight: "#FF5A5E",
107 | label: "Red"
108 | },
109 | {
110 | value: 50,
111 | color: "#46BFBD",
112 | highlight: "#5AD3D1",
113 | label: "Green"
114 | },
115 | {
116 | value: 100,
117 | color: "#FDB45C",
118 | highlight: "#FFC870",
119 | label: "Yellow"
120 | },
121 | {
122 | value: 40,
123 | color: "#949FB1",
124 | highlight: "#A8B3C5",
125 | label: "Grey"
126 | },
127 | {
128 | value: 120,
129 | color: "#4D5360",
130 | highlight: "#616774",
131 | label: "Dark Grey"
132 | }
133 |
134 | ];
135 |
136 | // /////////////////////////////////////////////////////////////////
137 | // Doughnut Chart Data Sample
138 | // /////////////////////////////////////////////////////////////////
139 |
140 | var ChartDoughnutData = [
141 | {
142 | value: 300,
143 | color:"#F7464A",
144 | highlight: "#FF5A5E",
145 | label: "Red"
146 | },
147 | {
148 | value: 50,
149 | color: "#46BFBD",
150 | highlight: "#5AD3D1",
151 | label: "Green"
152 | },
153 | {
154 | value: 100,
155 | color: "#FDB45C",
156 | highlight: "#FFC870",
157 | label: "Yellow"
158 | },
159 | {
160 | value: 40,
161 | color: "#949FB1",
162 | highlight: "#A8B3C5",
163 | label: "Grey"
164 | },
165 | {
166 | value: 120,
167 | color: "#4D5360",
168 | highlight: "#616774",
169 | label: "Dark Grey"
170 | }
171 | ]
172 |
173 | // /////////////////////////////////////////////////////////////////
174 | // Bar Chart Data Sample
175 | // /////////////////////////////////////////////////////////////////
176 |
177 | var ChartBarData = {
178 | labels: ["January","February","March","April","May","June","July","January","February","March","April","May","June","July"],
179 | datasets : [/*
180 | {
181 | fillColor : "rgba(220,220,220,0.5)",
182 | strokeColor : "rgba(220,220,220,0.8)",
183 | highlightFill: "rgba(220,220,220,0.75)",
184 | highlightStroke: "rgba(220,220,220,1)",
185 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
186 | },*/
187 | {
188 | fillColor : "rgba(151,187,205,0.5)",
189 | strokeColor : "rgba(151,187,205,0.8)",
190 | highlightFill : "rgba(151,187,205,0.75)",
191 | highlightStroke : "rgba(151,187,205,1)",
192 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
193 | }
194 | ]
195 | }
196 |
197 | var StackedBarData = {
198 | labels : ["January","February","March","April","May","June","July"],
199 | datasets : [
200 | {
201 | fillColor : "rgba(220,220,220,0.5)",
202 | strokeColor : "rgba(220,220,220,0.8)",
203 | highlightFill: "rgba(220,220,220,0.75)",
204 | highlightStroke: "rgba(220,220,220,1)",
205 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
206 | },
207 | {
208 | fillColor : "rgba(151,187,205,0.5)",
209 | strokeColor : "rgba(151,187,205,0.8)",
210 | highlightFill : "rgba(151,187,205,0.75)",
211 | highlightStroke : "rgba(151,187,205,1)",
212 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
213 | },
214 | {
215 | fillColor : "rgba(240,73,73,0.5)",
216 | strokeColor : "rgba(240,73,73,0.8)",
217 | highlightFill : "rgba(240,73,73,0.75)",
218 | highlightStroke : "rgba(240,73,73,1)",
219 | data : [randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor(),randomScalingFactor()]
220 | }
221 | ]
222 | }
223 |
224 |
--------------------------------------------------------------------------------
/QMLChartJS.qmlproject:
--------------------------------------------------------------------------------
1 | /* File generated by Qt Creator, version 2.7.0 */
2 |
3 | import QmlProject 1.1
4 |
5 | Project {
6 | mainFile: "main.qml"
7 |
8 | /* Include .qml, .js, and image files from current directory and subdirectories */
9 | QmlFiles {
10 | directory: "."
11 | }
12 | JavaScriptFiles {
13 | directory: "."
14 | }
15 | ImageFiles {
16 | directory: "."
17 | }
18 | /* List of plugin directories passed to QML runtime */
19 | // importPaths: [ "../exampleplugin" ]
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QMLChartJs
2 | ==========
3 |
4 | QML Chart module, using chart.js(base on version: "1.0.1-beta.4") to render charts on QML canvas.
5 |
6 | This Project borrow ideas from(https://github.com/jwintz/qchart.js)
7 |
8 | For a quick check, here is a screenshot:
9 | 
10 |
11 | For Support features, please check(http://www.chartjs.org/)
12 |
13 | ## Usage
14 | ```QML
15 |
16 | QChartJs {
17 | id: chart_line
18 | width: 300
19 | height: 300
20 | chartType: ChartTypes.QChartJSTypes.LINE
21 | chartData: ChartsData.ChartLineData
22 | animation: true
23 | chartAnimationEasing: Easing.InOutElastic;
24 | chartAnimationDuration: 2000;
25 | }
26 |
27 | ```
28 |
29 | Check out main.qml for more details.
30 |
31 | ## Dependencies
32 | Qt >= 5.0
33 |
34 | ==========
35 |
--------------------------------------------------------------------------------
/main.qml:
--------------------------------------------------------------------------------
1 | import "."
2 | import QtQuick 2.0
3 | import QtQuick.Window 2.1
4 | import "QMLChartData.js" as ChartsData
5 | import "QChartJsTypes.js" as ChartTypes
6 |
7 | Window {
8 | visible: true
9 | property int chart_width: 300;
10 | property int chart_height: 300;
11 | property int chart_spacing: 20;
12 | property int text_height: 80;
13 | property int row_height: 8;
14 |
15 | color: "#ffffff";
16 | width: chart_width*4 + 3*chart_spacing;
17 | height: chart_height*2 + chart_spacing + 2*row_height + text_height;
18 |
19 | Grid {
20 |
21 | id: layout;
22 |
23 | x: 0;
24 | y: 0;
25 |
26 | width: parent.width;
27 | height: parent.height - 80;
28 |
29 | columns: 4;
30 | spacing: chart_spacing;
31 |
32 | QChartJs {
33 | id: chart_line
34 | width: chart_width
35 | height: chart_height
36 | chartType: ChartTypes.QChartJSTypes.LINE
37 | chartData: ChartsData.ChartLineData
38 | animation: true
39 | chartAnimationEasing: Easing.InOutElastic;
40 | chartAnimationDuration: 2000;
41 | }
42 |
43 | QChartJs {
44 | id: chart_bar
45 | width: chart_width
46 | height: chart_height
47 | chartType: ChartTypes.QChartJSTypes.BAR
48 | chartData: ChartsData.ChartBarData
49 | animation: true
50 | chartAnimationEasing: Easing.InOutElastic;
51 | chartAnimationDuration: 2000;
52 | }
53 |
54 | QChartJs {
55 | id: chart_doughnut
56 | width: chart_width
57 | height: chart_height
58 | chartType: ChartTypes.QChartJSTypes.DOUGHNUT
59 | chartData: ChartsData.ChartDoughnutData
60 | animation: true
61 | chartAnimationEasing: Easing.InOutElastic;
62 | chartAnimationDuration: 2000;
63 | }
64 |
65 | QChartJs {
66 | id: chart_pie
67 | width: chart_width
68 | height: chart_height
69 | chartType: ChartTypes.QChartJSTypes.PIE
70 | chartData: ChartsData.ChartPieData
71 | animation: true
72 | chartAnimationEasing: Easing.InOutElastic;
73 | chartAnimationDuration: 2000;
74 | }
75 |
76 | QChartJs {
77 | id: chart_polar
78 | width: chart_width
79 | height: chart_height
80 | chartType: ChartTypes.QChartJSTypes.POLARAREA
81 | chartData: ChartsData.ChartPolarData
82 | animation: true
83 | chartAnimationEasing: Easing.InOutElastic;
84 | chartAnimationDuration: 2000;
85 | }
86 |
87 | QChartJs {
88 | id: chart_radar
89 | width: chart_width
90 | height: chart_height
91 | chartType: ChartTypes.QChartJSTypes.RADAR
92 | chartData: ChartsData.ChartRadarData
93 | animation: true
94 | chartAnimationEasing: Easing.InOutElastic;
95 | chartAnimationDuration: 2000;
96 | }
97 |
98 | QChartJs {
99 | id: chart_stack_bar
100 | width: chart_width
101 | height: chart_height
102 | chartType: ChartTypes.QChartJSTypes.STACKED_BAR
103 | chartData: ChartsData.StackedBarData
104 | animation: true
105 | chartAnimationEasing: Easing.InOutElastic;
106 | chartAnimationDuration: 2000;
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/screenshot/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shuirna/QMLChartJs/e4f84c1c5acb04276f53e9203acfc1fd19fb5b9e/screenshot/screenshot.png
--------------------------------------------------------------------------------