PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 All 36 releases
vikbooking / admin / resources / donutChart.js

donutChart.js in VikBooking Hotel Booking Engine & PMS trunk, at admin/resources/donutChart.js

546 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 The MIT License (MIT)
3
4 Copyright (c) 2014 Valerio Neri
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 */
16
17
18 var donutChart = function(chartElementID){
19 var centerx = 0;
20 var centery = 0;
21 var sizex = 0;
22 var sizey = 0;
23 var scaling = 0;
24 var radius = 0;
25 var startx = 0;
26 var starty = 0;
27 var endx = 0;
28 var endy = 0;
29 var animationSpeed = 1;
30 var firedAnimation = false;
31 var innerCircleColor = "#666666";
32 var outerCircleColor = "#aade87";
33 var textColor = "#ffffff";
34 var unitText = "%";
35 var innerCircleDOM = undefined;
36 var outerCircleDOM = undefined;
37 var tNumberDOM = undefined;
38 var textDOM = undefined;
39 var tUnitDOM = undefined;
40 var titleDOM = undefined;
41 var svg = undefined;
42 var chartID = chartElementID;
43 var chart = undefined;
44 var lastValue = -1;
45 var startValue = 0;
46 var endValue = 0;
47 var loaded = false;
48 var that=this;
49 var maxValue = 100;
50 var titleText ="";
51 var titlePosition = "top";
52 var titleColor = "#ffffff";
53 var textShift = 20; // this is required for moving the chart
54 var textScaling = 1;
55
56 // update: unfortunately in some cases the event is fired, and nobody tells us - that's why we just wait for the element to be available
57 // wait for the DOM, otherwise you won't find the reference to elements
58 //document.addEventListener("DOMContentLoaded", onLoad);
59 onLoad();
60
61 // this is used for refreshing the animation
62 (function() {
63 var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
64 window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
65 window.requestAnimationFrame = requestAnimationFrame;
66 })();
67
68 this.getValue = function(){
69 if (lastValue>-1)
70 return Math.round(lastValue/360*maxValue*10)/10;
71 else
72 return 0;
73 }
74
75 this.setValue = function(value){
76 that.draw({end:value, animationSpeed:0});
77 }
78
79 function setAnimate(fromD, toD, duration){
80 dAct = fromD;
81 // determine direction (up or down)
82 if (dAct<toD)
83 doIt(dAct, toD, duration, "up");
84 else
85 doIt(dAct, toD, duration, "down");
86 }
87
88 // this function is used for the animation - it uses an exponential acceleration
89 function calculateAccValue(xx, m){
90 var mxValue = m;
91 var x=Math.round(xx);
92 if (m==0){
93 mxValue=1;
94 }
95 if (x==0){
96 x=1;
97 }
98 if (Math.abs(x)>=mxValue){
99 return mxValue;
100 }else{
101 //return ( Math.pow( (-2),( (-1*x) + (Math.log(maxValue)/Math.log(2)) ) ) + maxValue);
102 var num1 = -2;
103 var num2 = Math.round( ((-1*x)+(Math.log(mxValue)/Math.log(2))) );
104 return Math.pow(num1 , num2 )+mxValue;
105 }
106 }
107
108 function textSetter(DOM, text){
109 // text scaling
110 if ((text>=1000)&&(textScaling=1)){
111 textScaling=1-((Math.floor( (Math.log(text)/Math.log(10))-2))/10)-0.1;
112 tNumberDOM.setAttribute('font-size', 50*scaling*textScaling);
113 tUnitDOM.setAttribute('font-size', 30*scaling*textScaling);
114 } else{
115 textScaling = 1;
116 tNumberDOM.setAttribute('font-size', 50*scaling*textScaling);
117 tUnitDOM.setAttribute('font-size', 30*scaling*textScaling);
118 }
119 DOM.textContent = text;
120 }
121
122 function doIt(dAct, toD, duration, direction){
123 var toString = "";
124 // -0.0001 is used for preventing IE reach 360 and close the circle
125 toString = getD(dAct-0.0001);
126 outerCircleDOM.setAttribute('d',toString);
127
128 //force the value to be an integer
129 var anim_cur_val = Math.round(dAct/360*maxValue*10)/10;
130 //textSetter(tNumberDOM, Math.round(dAct/360*maxValue*10)/10);
131 textSetter(tNumberDOM, anim_cur_val.toFixed(0));
132
133 //console.log('dAct '+dAct+ ' toD'+toD);
134 // determine direction (up or down)
135 if (direction=="up")
136 dAct = dAct + 5.5;
137 else
138 dAct = dAct - 5.5;
139
140 duration = duration - calculateAccValue(duration, 500);
141 if (direction=="up"){
142 if (dAct < toD){
143 setTimeout(function(){
144 // animate it according to refresh rate
145 requestAnimationFrame(function(){
146 doIt(dAct, toD, duration, direction);
147 })
148 },duration);
149 } else {
150
151 outerCircleDOM.setAttribute('d',getD((toD-0.0001)));
152 textSetter(tNumberDOM, Math.round(toD/360*maxValue*10)/10);
153 }
154 } else {
155 if (dAct >= toD){
156 setTimeout(function(){
157 // animate it according to refresh rate
158 requestAnimationFrame(function(){
159 doIt(dAct, toD, duration, direction);
160 })
161 },duration);
162 } else {
163 outerCircleDOM.setAttribute('d',getD(toD-0.0001));
164 textSetter(tNumberDOM ,Math.round(toD/360*maxValue*10)/10);
165 }
166 }
167
168 };
169
170 // this function gives us the circle coordinates
171 function getCoordinates(radius,offset,degrees){
172 var radians = degreesToRadians(degrees);
173 var x = offset.x + radius * Math.cos(radians)
174 var y = offset.y + radius * Math.sin(radians)
175 return {x:x,y:y};
176 };
177
178 // small converstion beween degrees and radians
179 function degreesToRadians(degrees){
180 var radians = (degrees * Math.PI) / 180;
181 return radians
182 };
183
184 // this function returns the "d" string for the path, according to the degrees
185 function getD(degree){
186 radius = 100*scaling;
187 startx = centerx+radius;
188 starty = centery;
189 var coor = getCoordinates(radius, {x:startx,y:starty},degree);
190 endx = coor.x;
191 endy = coor.y;
192 var largearc = 0;
193 if (degree>180){
194 largearc=1;
195 } else {
196 largearc=0;
197 }
198 // don't ask me how I did it folks
199 d="M "+startx+" "+starty+" a "+radius+" "+radius+" 0 "+largearc+" 1 "+(endx-startx-radius)+" "+(endy-starty)+" L "+(centerx)+" "+(centery)+" Z";
200 //console.log('GETd '+degree+ " "+d);
201 return d;
202 }
203
204 // this function checks, if an element is fully visible (according to the scrolling)
205 function checkVisible(){
206 if (firedAnimation){
207 // animation has already fired, detach the event listeners
208 document.removeEventListener("scroll", checkVisible, false);
209 window.removeEventListener("resize", checkVisible, false);
210 return;
211 }
212 var rect = chart.getBoundingClientRect();
213 var top = window.pageYOffset || document.documentElement.scrollTop;
214 var left = window.pageXOffset || document.documentElement.scrollLeft;
215 if ((rect.top>=0)&&(rect.top+rect.height<window.innerHeight) &&
216 (rect.left>=0)&&(rect.left+rect.width<window.innerWidth)){
217 // this means that we fully see the chart
218 // fire the animation (only once)
219 firedAnimation = true;
220 setAnimate(startValue, endValue, (500/animationSpeed));
221 }
222 }
223
224 var waitingLoad =0;
225
226 function onLoad(){
227 // get the container for the chart
228 chart = document.getElementById(chartID);
229
230 // if the container is not ready in the DOM, then retry
231 // try for around 20secs, then throw an exception
232 if (chart==null){
233
234 // after 10 seconds, pause a little bit
235 if ((waitingLoad > 10000)&&(waitingLoad<20000)){
236 setTimeout(onLoad, 1000);
237 waitingLoad = waitingLoad +1000;
238 return;
239 }
240 if (waitingLoad > 20000){
241 throw('donutChart: onLoad() - The chart element "'+chartID+'" could not be loaded in the last 20 seconds');
242 }
243 setTimeout(onLoad, 500);
244 waitingLoad = waitingLoad +500;
245 return;
246 }
247 // create an svg object and all other things - thanks to Thoka
248 svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
249 innerCircleDOM = document.createElementNS('http://www.w3.org/2000/svg','circle');
250 outerCircleDOM = document.createElementNS('http://www.w3.org/2000/svg','path');
251 textDOM = document.createElementNS('http://www.w3.org/2000/svg','text');
252 tNumberDOM = document.createElementNS('http://www.w3.org/2000/svg','tspan');
253 tUnitDOM = document.createElementNS('http://www.w3.org/2000/svg','tspan');
254 titleDOM = document.createElementNS('http://www.w3.org/2000/svg','text');
255
256 // append to the chart
257 chart.appendChild(svg);
258 svg.appendChild(outerCircleDOM);
259 svg.appendChild(innerCircleDOM);
260 svg.appendChild(textDOM);
261 svg.appendChild(titleDOM);
262
263 textDOM.appendChild(tNumberDOM);
264 textDOM.appendChild(tUnitDOM);
265 loaded = true;
266 }
267
268 this.reload = function(){
269 if (!loaded)
270 onLoad();
271 }
272
273 this.clear = function(){
274 // reinitialize some parameters
275 firedAnimation = false;
276 animationSpeed = 1;
277 maxValue = 100;
278 unitText = "%";
279 lastValue = -1;
280 this.setValue(0);
281 }
282
283 this.delete = function(){
284 // remove everything
285 this.clear();
286 textDOM.removeChild(tNumberDOM);
287 textDOM.removeChild(tUnitDOM);
288 svg.removeChild(textDOM);
289 svg.removeChild(titleDOM);
290 svg.removeChild(outerCircleDOM);
291 svg.removeChild(innerCircleDOM);
292 chart.removeChild(svg);
293 loaded = false;
294 // yes but we leave chart, because it was provided
295 }
296
297 this.draw = function(options){
298 // check if main function has already loaded, if not, reiterate
299 if (!loaded){
300 setTimeout(function(){
301 that.draw(options);
302 }, 500);
303 return;
304 }
305
306 // OPTION CHECK - contains the options
307 // check if all options are set
308 var oTester = [];
309 oTester.options = typeof(options)!="undefined";
310
311 if (! (oTester.options)){
312 throw('donutChart: draw() - Not enough parameters or no parameters set in object');
313 return;
314 }
315
316 oTester.end = typeof(options.end)!="undefined";
317 oTester.start = typeof(options.start)!="undefined";
318 oTester.scaling = typeof(options.scaling)!="undefined";
319 oTester.size = typeof(options.size)!="undefined";
320 oTester.animationSpeed = typeof(options.animationSpeed)!="undefined";
321 oTester.textColor = typeof(options.textColor)!="undefined";
322 oTester.innerCircleColor = typeof(options.innerCircleColor)!="undefined";
323 oTester.outerCircleColor = typeof(options.outerCircleColor)!="undefined";
324 oTester.unitText = typeof(options.unitText)!="undefined";
325 oTester.maxValue = typeof(options.maxValue)!="undefined";
326 oTester.titleText = typeof(options.titleText)!="undefined";
327 oTester.titleColor = typeof(options.titleColor)!="undefined";
328 oTester.titlePosition = typeof(options.titlePosition)!="undefined";
329
330 if (! (oTester.end)){
331 throw('donutChart: draw() - No "end" value specified');
332 return;
333 }
334
335 // reinitialize some parameters
336 firedAnimation = false;
337 animationSpeed = 1;
338 maxValue = 100;
339 unitText = "%";
340 textShift = 20;
341 textScaling = 1;
342
343 // if the values are not set, set the standard
344 if (!oTester.size){
345 options.size = 200;
346 }
347
348 if (!oTester.scaling){
349 options.scaling = 1;
350 }
351
352 // if size ist not set, then take the options.scaling and the standard size of 200x200
353 if (!oTester.size){
354 svg.setAttribute('width', 200*options.scaling);
355 svg.setAttribute('height', 200*options.scaling);
356 options.size = 200*options.scaling;
357 }
358 // if scaling not set, then take the size anc calculate the options.scaling
359 if (!oTester.scaling){
360 svg.setAttribute('width', options.size);
361 svg.setAttribute('height', options.size);
362 if (svg.getAttribute('width')<svg.getAttribute('height')){
363 options.scaling = svg.getAttribute('width') / 200;
364 } else{
365 options.scaling = svg.getAttribute('height') / 200;
366 }
367 }
368
369 // set the colors, if set
370 if (oTester.outerCircleColor){
371 outerCircleColor = options.outerCircleColor;
372 }
373 if (oTester.innerCircleColor){
374 innerCircleColor = options.innerCircleColor;
375 }
376 if (oTester.textColor){
377 textColor = options.textColor;
378 }
379
380 // set the unitText, if set
381 if (oTester.unitText){
382 unitText = options.unitText;
383 }
384
385 // set the title, if set
386 if (oTester.titleText){
387 titleText = options.titleText;
388 }
389 if (oTester.titleColor){
390 titleColor = options.titleColor;
391 }
392 if (oTester.titlePosition){
393 titlePosition = options.titlePosition;
394 }
395
396
397 // set the maxValue, if set
398 if (oTester.maxValue){
399 maxValue = options.maxValue;
400 }
401
402 // set the starting position, if set
403 if (oTester.start){
404 options.start = Math.round(parseFloat(options.start)*10)/10;
405 if (options.start>maxValue)
406 options.start = maxValue;
407 if (options.start<0)
408 options.start = 0;
409 if (isNaN(options.start)){
410 // not a number and not parseable, ignore
411 options.start = 0;
412 oTester.start = false;
413 }
414 startValue = options.start/maxValue*360;
415 }
416
417 options.end = Math.round(parseFloat(options.end)*10)/10;
418
419 if (options.end>maxValue)
420 options.end = maxValue;
421 if (options.end<0)
422 options.end = 0;
423
424 if (isNaN(options.end)){
425 // not a number and not parseable, ignore
426 return;
427 }
428
429 options.end = options.end/maxValue*360;
430 endValue = options.end;
431
432 textShift = textShift*options.scaling;
433
434 // set the size and the center
435 sizex = options.size;
436 sizey = options.size+2*textShift;
437 centerx = sizex/2;
438 centery = (sizey/2);
439 scaling = options.scaling;
440
441 // set animation speed (0 = no animation), standard is one
442 if(oTester.animationSpeed){
443 animationSpeed = options.animationSpeed;
444 }
445
446 //e4j
447 var innerCircleStroke = typeof(options.innerCircleStroke)!="undefined" ? options.innerCircleStroke : 'none';
448 var outerCircleStroke = typeof(options.outerCircleStroke)!="undefined" ? options.outerCircleStroke : 'none';
449 //
450
451 // initialise with start position
452 outerCircleDOM.setAttribute('d', getD(startValue));
453
454 chart.style.width = sizex;
455 chart.style.height = sizey;
456
457 svg.setAttribute('height', sizey);
458 svg.setAttribute('class', 'donutChart');
459 svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
460 svg.setAttribute('version', '1.1');
461
462 innerCircleDOM.setAttribute('cx', centerx);
463 innerCircleDOM.setAttribute('cy', centery);
464 innerCircleDOM.setAttribute('r', 80*options.scaling);
465 innerCircleDOM.setAttribute('stroke', innerCircleStroke);
466 innerCircleDOM.setAttribute('stroke-width', '2');
467 innerCircleDOM.setAttribute('fill', innerCircleColor);
468
469 outerCircleDOM.setAttribute('fill', outerCircleColor);
470 outerCircleDOM.setAttribute('stroke', outerCircleStroke);
471 outerCircleDOM.setAttribute('stroke-width', '36');
472
473 tNumberDOM.setAttribute('x', centerx);
474 tNumberDOM.setAttribute('y', centery+Math.round(18.75*options.scaling));
475 tNumberDOM.setAttribute('font-size', 50*options.scaling);
476 tNumberDOM.setAttribute('fill', textColor);
477 tNumberDOM.setAttribute('font-family', 'arial');
478 tNumberDOM.setAttribute('font-weight', 'normal');
479 tNumberDOM.setAttribute('text-anchor', 'middle');
480
481 tUnitDOM.setAttribute('font-size', 30*options.scaling);
482 tUnitDOM.textContent = unitText;
483 tUnitDOM.setAttribute('fill', textColor);
484 tUnitDOM.setAttribute('text-anchor', 'middle');
485 tUnitDOM.setAttribute('font-family', 'arial');
486 tUnitDOM.setAttribute('font-weight', 'normal');
487
488 titleDOM.setAttribute('x', centerx);
489 // set the position of the title
490 switch(titlePosition){
491 case "inner-bottom":
492 if (!oTester.titleColor)
493 titleColor = "#ffffff";
494 titleDOM.setAttribute('y', centery+sizey/5+textShift/2);
495 break;
496
497 case "outer-bottom":
498 if (!oTester.titleColor)
499 titleColor = "#666666";
500
501 titleDOM.setAttribute('y', sizey-textShift/2);
502 break;
503
504 case "inner-top":
505 if (!oTester.titleColor)
506 titleColor = "#ffffff";
507 titleDOM.setAttribute('y', centery-sizey/5-textShift/2);
508 break;
509
510 default:
511 case "outer-top":
512 if (!oTester.titleColor)
513 titleColor = "#666666";
514
515 titleDOM.setAttribute('y', textShift/2);
516 break;
517 }
518
519 titleDOM.setAttribute('font-size', 12*options.scaling);
520 titleDOM.setAttribute('fill', titleColor);
521 titleDOM.setAttribute('font-family', 'arial');
522 titleDOM.setAttribute('font-weight', 'normal');
523 titleDOM.setAttribute('text-anchor', 'middle');
524 titleDOM.textContent = titleText;
525
526
527 // if the animation has been chosen
528 if (animationSpeed>0){
529 // ignore start value if lastvalue is already set
530 if (lastValue>-1){
531 startValue = lastValue;
532 }
533 window.addEventListener("resize", checkVisible, false);
534 document.addEventListener("scroll", checkVisible, false);
535 tNumberDOM.textContent = Math.round(startValue/360*maxValue*10)/10;
536 checkVisible();
537 } else {
538 // set the circle to the end position, without animation
539 // the 0.0001 is for avoiding the circle to disappear, due to some browser
540 outerCircleDOM.setAttribute('d', getD(options.end-0.0001));
541 tNumberDOM.textContent = Math.round(options.end/360*maxValue*10)/10;
542 }
543 lastValue = endValue;
544 }
545 }
546