提交 | 用户 | 时间
|
58d006
|
1 |
/**! |
A |
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.6 |
|
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.trackWidth || 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 |
trackWidth: undefined, |
|
222 |
size: 110, |
|
223 |
rotate: 0, |
|
224 |
animate: { |
|
225 |
duration: 1000, |
|
226 |
enabled: true |
|
227 |
}, |
|
228 |
easing: function (x, t, b, c, d) { // more can be found here: http://gsgd.co.uk/sandbox/jquery/easing/ |
|
229 |
t = t / (d/2); |
|
230 |
if (t < 1) { |
|
231 |
return c / 2 * t * t + b; |
|
232 |
} |
|
233 |
return -c/2 * ((--t)*(t-2) - 1) + b; |
|
234 |
}, |
|
235 |
onStart: function(from, to) { |
|
236 |
return; |
|
237 |
}, |
|
238 |
onStep: function(from, to, currentValue) { |
|
239 |
return; |
|
240 |
}, |
|
241 |
onStop: function(from, to) { |
|
242 |
return; |
|
243 |
} |
|
244 |
}; |
|
245 |
|
|
246 |
// detect present renderer |
|
247 |
if (typeof(CanvasRenderer) !== 'undefined') { |
|
248 |
defaultOptions.renderer = CanvasRenderer; |
|
249 |
} else if (typeof(SVGRenderer) !== 'undefined') { |
|
250 |
defaultOptions.renderer = SVGRenderer; |
|
251 |
} else { |
|
252 |
throw new Error('Please load either the SVG- or the CanvasRenderer'); |
|
253 |
} |
|
254 |
|
|
255 |
var options = {}; |
|
256 |
var currentValue = 0; |
|
257 |
|
|
258 |
/** |
|
259 |
* Initialize the plugin by creating the options object and initialize rendering |
|
260 |
*/ |
|
261 |
var init = function() { |
|
262 |
this.el = el; |
|
263 |
this.options = options; |
|
264 |
|
|
265 |
// merge user options into default options |
|
266 |
for (var i in defaultOptions) { |
|
267 |
if (defaultOptions.hasOwnProperty(i)) { |
|
268 |
options[i] = opts && typeof(opts[i]) !== 'undefined' ? opts[i] : defaultOptions[i]; |
|
269 |
if (typeof(options[i]) === 'function') { |
|
270 |
options[i] = options[i].bind(this); |
|
271 |
} |
|
272 |
} |
|
273 |
} |
|
274 |
|
|
275 |
// check for jQuery easing |
|
276 |
if (typeof(options.easing) === 'string' && typeof(jQuery) !== 'undefined' && jQuery.isFunction(jQuery.easing[options.easing])) { |
|
277 |
options.easing = jQuery.easing[options.easing]; |
|
278 |
} else { |
|
279 |
options.easing = defaultOptions.easing; |
|
280 |
} |
|
281 |
|
|
282 |
// process earlier animate option to avoid bc breaks |
|
283 |
if (typeof(options.animate) === 'number') { |
|
284 |
options.animate = { |
|
285 |
duration: options.animate, |
|
286 |
enabled: true |
|
287 |
}; |
|
288 |
} |
|
289 |
|
|
290 |
if (typeof(options.animate) === 'boolean' && !options.animate) { |
|
291 |
options.animate = { |
|
292 |
duration: 1000, |
|
293 |
enabled: options.animate |
|
294 |
}; |
|
295 |
} |
|
296 |
|
|
297 |
// create renderer |
|
298 |
this.renderer = new options.renderer(el, options); |
|
299 |
|
|
300 |
// initial draw |
|
301 |
this.renderer.draw(currentValue); |
|
302 |
|
|
303 |
// initial update |
|
304 |
if (el.dataset && el.dataset.percent) { |
|
305 |
this.update(parseFloat(el.dataset.percent)); |
|
306 |
} else if (el.getAttribute && el.getAttribute('data-percent')) { |
|
307 |
this.update(parseFloat(el.getAttribute('data-percent'))); |
|
308 |
} |
|
309 |
|
|
310 |
el.style['width'] = el.style['height'] = options.size + 'px';//ACE |
|
311 |
el.style['lineHeight'] = (options.size - 1) + 'px';//ACE |
|
312 |
}.bind(this); |
|
313 |
|
|
314 |
/** |
|
315 |
* Update the value of the chart |
|
316 |
* @param {number} newValue Number between 0 and 100 |
|
317 |
* @return {object} Instance of the plugin for method chaining |
|
318 |
*/ |
|
319 |
this.update = function(newValue) { |
|
320 |
newValue = parseFloat(newValue); |
|
321 |
if (options.animate.enabled) { |
|
322 |
this.renderer.animate(currentValue, newValue); |
|
323 |
} else { |
|
324 |
this.renderer.draw(newValue); |
|
325 |
} |
|
326 |
currentValue = newValue; |
|
327 |
return this; |
|
328 |
}.bind(this); |
|
329 |
|
|
330 |
/** |
|
331 |
* Disable animation |
|
332 |
* @return {object} Instance of the plugin for method chaining |
|
333 |
*/ |
|
334 |
this.disableAnimation = function() { |
|
335 |
options.animate.enabled = false; |
|
336 |
return this; |
|
337 |
}; |
|
338 |
|
|
339 |
/** |
|
340 |
* Enable animation |
|
341 |
* @return {object} Instance of the plugin for method chaining |
|
342 |
*/ |
|
343 |
this.enableAnimation = function() { |
|
344 |
options.animate.enabled = true; |
|
345 |
return this; |
|
346 |
}; |
|
347 |
|
|
348 |
init(); |
|
349 |
}; |
|
350 |
|
|
351 |
$.fn.easyPieChart = function(options) { |
|
352 |
return this.each(function() { |
|
353 |
var instanceOptions; |
|
354 |
|
|
355 |
if (!$.data(this, 'easyPieChart')) { |
|
356 |
instanceOptions = $.extend({}, options, $(this).data()); |
|
357 |
$.data(this, 'easyPieChart', new EasyPieChart(this, instanceOptions)); |
|
358 |
} |
|
359 |
}); |
|
360 |
}; |
|
361 |
|
|
362 |
|
|
363 |
})); |