| 776 |
lars |
1 |
/**!
|
|
|
2 |
* easyPieChart
|
|
|
3 |
* Lightweight plugin to render simple, animated and retina optimized pie charts
|
|
|
4 |
*
|
|
|
5 |
* @license
|
|
|
6 |
* @author Robert Fleischmann <rendro87@gmail.com> (http://robert-fleischmann.de)
|
|
|
7 |
* @version 2.1.5
|
|
|
8 |
**/
|
|
|
9 |
|
|
|
10 |
(function(root, factory) {
|
|
|
11 |
if(typeof exports === 'object') {
|
|
|
12 |
module.exports = factory(require('jquery'));
|
|
|
13 |
}
|
|
|
14 |
else if(typeof define === 'function' && define.amd) {
|
|
|
15 |
define(['jquery'], factory);
|
|
|
16 |
}
|
|
|
17 |
else {
|
|
|
18 |
factory(root.jQuery);
|
|
|
19 |
}
|
|
|
20 |
}(this, function($) {
|
|
|
21 |
|
|
|
22 |
/**
|
|
|
23 |
* Renderer to render the chart on a canvas object
|
|
|
24 |
* @param {DOMElement} el DOM element to host the canvas (root of the plugin)
|
|
|
25 |
* @param {object} options options object of the plugin
|
|
|
26 |
*/
|
|
|
27 |
var CanvasRenderer = function(el, options) {
|
|
|
28 |
var cachedBackground;
|
|
|
29 |
var canvas = document.createElement('canvas');
|
|
|
30 |
|
|
|
31 |
el.appendChild(canvas);
|
|
|
32 |
|
|
|
33 |
if (typeof(G_vmlCanvasManager) !== 'undefined') {
|
|
|
34 |
G_vmlCanvasManager.initElement(canvas);
|
|
|
35 |
}
|
|
|
36 |
|
|
|
37 |
var ctx = canvas.getContext('2d');
|
|
|
38 |
|
|
|
39 |
canvas.width = canvas.height = options.size;
|
|
|
40 |
|
|
|
41 |
// canvas on retina devices
|
|
|
42 |
var scaleBy = 1;
|
|
|
43 |
if (window.devicePixelRatio > 1) {
|
|
|
44 |
scaleBy = window.devicePixelRatio;
|
|
|
45 |
canvas.style.width = canvas.style.height = [options.size, 'px'].join('');
|
|
|
46 |
canvas.width = canvas.height = options.size * scaleBy;
|
|
|
47 |
ctx.scale(scaleBy, scaleBy);
|
|
|
48 |
}
|
|
|
49 |
|
|
|
50 |
// move 0,0 coordinates to the center
|
|
|
51 |
ctx.translate(options.size / 2, options.size / 2);
|
|
|
52 |
|
|
|
53 |
// rotate canvas -90deg
|
|
|
54 |
ctx.rotate((-1 / 2 + options.rotate / 180) * Math.PI);
|
|
|
55 |
|
|
|
56 |
var radius = (options.size - options.lineWidth) / 2;
|
|
|
57 |
if (options.scaleColor && options.scaleLength) {
|
|
|
58 |
radius -= options.scaleLength + 2; // 2 is the distance between scale and bar
|
|
|
59 |
}
|
|
|
60 |
|
|
|
61 |
// IE polyfill for Date
|
|
|
62 |
Date.now = Date.now || function() {
|
|
|
63 |
return +(new Date());
|
|
|
64 |
};
|
|
|
65 |
|
|
|
66 |
/**
|
|
|
67 |
* Draw a circle around the center of the canvas
|
|
|
68 |
* @param {strong} color Valid CSS color string
|
|
|
69 |
* @param {number} lineWidth Width of the line in px
|
|
|
70 |
* @param {number} percent Percentage to draw (float between -1 and 1)
|
|
|
71 |
*/
|
|
|
72 |
var drawCircle = function(color, lineWidth, percent) {
|
|
|
73 |
percent = Math.min(Math.max(-1, percent || 0), 1);
|
|
|
74 |
var isNegative = percent <= 0 ? true : false;
|
|
|
75 |
|
|
|
76 |
ctx.beginPath();
|
|
|
77 |
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, isNegative);
|
|
|
78 |
|
|
|
79 |
ctx.strokeStyle = color;
|
|
|
80 |
ctx.lineWidth = lineWidth;
|
|
|
81 |
|
|
|
82 |
ctx.stroke();
|
|
|
83 |
};
|
|
|
84 |
|
|
|
85 |
/**
|
|
|
86 |
* Draw the scale of the chart
|
|
|
87 |
*/
|
|
|
88 |
var drawScale = function() {
|
|
|
89 |
var offset;
|
|
|
90 |
var length;
|
|
|
91 |
|
|
|
92 |
ctx.lineWidth = 1;
|
|
|
93 |
ctx.fillStyle = options.scaleColor;
|
|
|
94 |
|
|
|
95 |
ctx.save();
|
|
|
96 |
for (var i = 24; i > 0; --i) {
|
|
|
97 |
if (i % 6 === 0) {
|
|
|
98 |
length = options.scaleLength;
|
|
|
99 |
offset = 0;
|
|
|
100 |
} else {
|
|
|
101 |
length = options.scaleLength * 0.6;
|
|
|
102 |
offset = options.scaleLength - length;
|
|
|
103 |
}
|
|
|
104 |
ctx.fillRect(-options.size/2 + offset, 0, length, 1);
|
|
|
105 |
ctx.rotate(Math.PI / 12);
|
|
|
106 |
}
|
|
|
107 |
ctx.restore();
|
|
|
108 |
};
|
|
|
109 |
|
|
|
110 |
/**
|
|
|
111 |
* Request animation frame wrapper with polyfill
|
|
|
112 |
* @return {function} Request animation frame method or timeout fallback
|
|
|
113 |
*/
|
|
|
114 |
var reqAnimationFrame = (function() {
|
|
|
115 |
return window.requestAnimationFrame ||
|
|
|
116 |
window.webkitRequestAnimationFrame ||
|
|
|
117 |
window.mozRequestAnimationFrame ||
|
|
|
118 |
function(callback) {
|
|
|
119 |
window.setTimeout(callback, 1000 / 60);
|
|
|
120 |
};
|
|
|
121 |
}());
|
|
|
122 |
|
|
|
123 |
/**
|
|
|
124 |
* Draw the background of the plugin including the scale and the track
|
|
|
125 |
*/
|
|
|
126 |
var drawBackground = function() {
|
|
|
127 |
if(options.scaleColor) drawScale();
|
|
|
128 |
if(options.trackColor) drawCircle(options.trackColor, options.lineWidth, 1);
|
|
|
129 |
};
|
|
|
130 |
|
|
|
131 |
/**
|
|
|
132 |
* Canvas accessor
|
|
|
133 |
*/
|
|
|
134 |
this.getCanvas = function() {
|
|
|
135 |
return canvas;
|
|
|
136 |
};
|
|
|
137 |
|
|
|
138 |
/**
|
|
|
139 |
* Canvas 2D context 'ctx' accessor
|
|
|
140 |
*/
|
|
|
141 |
this.getCtx = function() {
|
|
|
142 |
return ctx;
|
|
|
143 |
};
|
|
|
144 |
|
|
|
145 |
/**
|
|
|
146 |
* Clear the complete canvas
|
|
|
147 |
*/
|
|
|
148 |
this.clear = function() {
|
|
|
149 |
ctx.clearRect(options.size / -2, options.size / -2, options.size, options.size);
|
|
|
150 |
};
|
|
|
151 |
|
|
|
152 |
/**
|
|
|
153 |
* Draw the complete chart
|
|
|
154 |
* @param {number} percent Percent shown by the chart between -100 and 100
|
|
|
155 |
*/
|
|
|
156 |
this.draw = function(percent) {
|
|
|
157 |
// do we need to render a background
|
|
|
158 |
if (!!options.scaleColor || !!options.trackColor) {
|
|
|
159 |
// getImageData and putImageData are supported
|
|
|
160 |
if (ctx.getImageData && ctx.putImageData) {
|
|
|
161 |
if (!cachedBackground) {
|
|
|
162 |
drawBackground();
|
|
|
163 |
cachedBackground = ctx.getImageData(0, 0, options.size * scaleBy, options.size * scaleBy);
|
|
|
164 |
} else {
|
|
|
165 |
ctx.putImageData(cachedBackground, 0, 0);
|
|
|
166 |
}
|
|
|
167 |
} else {
|
|
|
168 |
this.clear();
|
|
|
169 |
drawBackground();
|
|
|
170 |
}
|
|
|
171 |
} else {
|
|
|
172 |
this.clear();
|
|
|
173 |
}
|
|
|
174 |
|
|
|
175 |
ctx.lineCap = options.lineCap;
|
|
|
176 |
|
|
|
177 |
// if barcolor is a function execute it and pass the percent as a value
|
|
|
178 |
var color;
|
|
|
179 |
if (typeof(options.barColor) === 'function') {
|
|
|
180 |
color = options.barColor(percent);
|
|
|
181 |
} else {
|
|
|
182 |
color = options.barColor;
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
// draw bar
|
|
|
186 |
drawCircle(color, options.lineWidth, percent / 100);
|
|
|
187 |
}.bind(this);
|
|
|
188 |
|
|
|
189 |
/**
|
|
|
190 |
* Animate from some percent to some other percentage
|
|
|
191 |
* @param {number} from Starting percentage
|
|
|
192 |
* @param {number} to Final percentage
|
|
|
193 |
*/
|
|
|
194 |
this.animate = function(from, to) {
|
|
|
195 |
var startTime = Date.now();
|
|
|
196 |
options.onStart(from, to);
|
|
|
197 |
var animation = function() {
|
|
|
198 |
var process = Math.min(Date.now() - startTime, options.animate.duration);
|
|
|
199 |
var currentValue = options.easing(this, process, from, to - from, options.animate.duration);
|
|
|
200 |
this.draw(currentValue);
|
|
|
201 |
options.onStep(from, to, currentValue);
|
|
|
202 |
if (process >= options.animate.duration) {
|
|
|
203 |
options.onStop(from, to);
|
|
|
204 |
} else {
|
|
|
205 |
reqAnimationFrame(animation);
|
|
|
206 |
}
|
|
|
207 |
}.bind(this);
|
|
|
208 |
|
|
|
209 |
reqAnimationFrame(animation);
|
|
|
210 |
}.bind(this);
|
|
|
211 |
};
|
|
|
212 |
|
|
|
213 |
var EasyPieChart = function(el, opts) {
|
|
|
214 |
var defaultOptions = {
|
|
|
215 |
barColor: '#ef1e25',
|
|
|
216 |
trackColor: '#f9f9f9',
|
|
|
217 |
scaleColor: '#dfe0e0',
|
|
|
218 |
scaleLength: 5,
|
|
|
219 |
lineCap: 'round',
|
|
|
220 |
lineWidth: 3,
|
|
|
221 |
size: 110,
|
|
|
222 |
rotate: 0,
|
|
|
223 |
animate: {
|
|
|
224 |
duration: 1000,
|
|
|
225 |
enabled: true
|
|
|
226 |
},
|
|
|
227 |
easing: function (x, t, b, c, d) { // more can be found here: http://gsgd.co.uk/sandbox/jquery/easing/
|
|
|
228 |
t = t / (d/2);
|
|
|
229 |
if (t < 1) {
|
|
|
230 |
return c / 2 * t * t + b;
|
|
|
231 |
}
|
|
|
232 |
return -c/2 * ((--t)*(t-2) - 1) + b;
|
|
|
233 |
},
|
|
|
234 |
onStart: function(from, to) {
|
|
|
235 |
return;
|
|
|
236 |
},
|
|
|
237 |
onStep: function(from, to, currentValue) {
|
|
|
238 |
return;
|
|
|
239 |
},
|
|
|
240 |
onStop: function(from, to) {
|
|
|
241 |
return;
|
|
|
242 |
}
|
|
|
243 |
};
|
|
|
244 |
|
|
|
245 |
// detect present renderer
|
|
|
246 |
if (typeof(CanvasRenderer) !== 'undefined') {
|
|
|
247 |
defaultOptions.renderer = CanvasRenderer;
|
|
|
248 |
} else if (typeof(SVGRenderer) !== 'undefined') {
|
|
|
249 |
defaultOptions.renderer = SVGRenderer;
|
|
|
250 |
} else {
|
|
|
251 |
throw new Error('Please load either the SVG- or the CanvasRenderer');
|
|
|
252 |
}
|
|
|
253 |
|
|
|
254 |
var options = {};
|
|
|
255 |
var currentValue = 0;
|
|
|
256 |
|
|
|
257 |
/**
|
|
|
258 |
* Initialize the plugin by creating the options object and initialize rendering
|
|
|
259 |
*/
|
|
|
260 |
var init = function() {
|
|
|
261 |
this.el = el;
|
|
|
262 |
this.options = options;
|
|
|
263 |
|
|
|
264 |
// merge user options into default options
|
|
|
265 |
for (var i in defaultOptions) {
|
|
|
266 |
if (defaultOptions.hasOwnProperty(i)) {
|
|
|
267 |
options[i] = opts && typeof(opts[i]) !== 'undefined' ? opts[i] : defaultOptions[i];
|
|
|
268 |
if (typeof(options[i]) === 'function') {
|
|
|
269 |
options[i] = options[i].bind(this);
|
|
|
270 |
}
|
|
|
271 |
}
|
|
|
272 |
}
|
|
|
273 |
|
|
|
274 |
// check for jQuery easing
|
|
|
275 |
if (typeof(options.easing) === 'string' && typeof(jQuery) !== 'undefined' && jQuery.isFunction(jQuery.easing[options.easing])) {
|
|
|
276 |
options.easing = jQuery.easing[options.easing];
|
|
|
277 |
} else {
|
|
|
278 |
options.easing = defaultOptions.easing;
|
|
|
279 |
}
|
|
|
280 |
|
|
|
281 |
// process earlier animate option to avoid bc breaks
|
|
|
282 |
if (typeof(options.animate) === 'number') {
|
|
|
283 |
options.animate = {
|
|
|
284 |
duration: options.animate,
|
|
|
285 |
enabled: true
|
|
|
286 |
};
|
|
|
287 |
}
|
|
|
288 |
|
|
|
289 |
if (typeof(options.animate) === 'boolean' && !options.animate) {
|
|
|
290 |
options.animate = {
|
|
|
291 |
duration: 1000,
|
|
|
292 |
enabled: options.animate
|
|
|
293 |
};
|
|
|
294 |
}
|
|
|
295 |
|
|
|
296 |
// create renderer
|
|
|
297 |
this.renderer = new options.renderer(el, options);
|
|
|
298 |
|
|
|
299 |
// initial draw
|
|
|
300 |
this.renderer.draw(currentValue);
|
|
|
301 |
|
|
|
302 |
// initial update
|
|
|
303 |
if (el.dataset && el.dataset.percent) {
|
|
|
304 |
this.update(parseFloat(el.dataset.percent));
|
|
|
305 |
} else if (el.getAttribute && el.getAttribute('data-percent')) {
|
|
|
306 |
this.update(parseFloat(el.getAttribute('data-percent')));
|
|
|
307 |
}
|
|
|
308 |
}.bind(this);
|
|
|
309 |
|
|
|
310 |
/**
|
|
|
311 |
* Update the value of the chart
|
|
|
312 |
* @param {number} newValue Number between 0 and 100
|
|
|
313 |
* @return {object} Instance of the plugin for method chaining
|
|
|
314 |
*/
|
|
|
315 |
this.update = function(newValue) {
|
|
|
316 |
newValue = parseFloat(newValue);
|
|
|
317 |
if (options.animate.enabled) {
|
|
|
318 |
this.renderer.animate(currentValue, newValue);
|
|
|
319 |
} else {
|
|
|
320 |
this.renderer.draw(newValue);
|
|
|
321 |
}
|
|
|
322 |
currentValue = newValue;
|
|
|
323 |
return this;
|
|
|
324 |
}.bind(this);
|
|
|
325 |
|
|
|
326 |
/**
|
|
|
327 |
* Disable animation
|
|
|
328 |
* @return {object} Instance of the plugin for method chaining
|
|
|
329 |
*/
|
|
|
330 |
this.disableAnimation = function() {
|
|
|
331 |
options.animate.enabled = false;
|
|
|
332 |
return this;
|
|
|
333 |
};
|
|
|
334 |
|
|
|
335 |
/**
|
|
|
336 |
* Enable animation
|
|
|
337 |
* @return {object} Instance of the plugin for method chaining
|
|
|
338 |
*/
|
|
|
339 |
this.enableAnimation = function() {
|
|
|
340 |
options.animate.enabled = true;
|
|
|
341 |
return this;
|
|
|
342 |
};
|
|
|
343 |
|
|
|
344 |
init();
|
|
|
345 |
};
|
|
|
346 |
|
|
|
347 |
$.fn.easyPieChart = function(options) {
|
|
|
348 |
return this.each(function() {
|
|
|
349 |
var instanceOptions;
|
|
|
350 |
|
|
|
351 |
if (!$.data(this, 'easyPieChart')) {
|
|
|
352 |
instanceOptions = $.extend({}, options, $(this).data());
|
|
|
353 |
$.data(this, 'easyPieChart', new EasyPieChart(this, instanceOptions));
|
|
|
354 |
}
|
|
|
355 |
});
|
|
|
356 |
};
|
|
|
357 |
|
|
|
358 |
|
|
|
359 |
}));
|