2 * o------------------------------------------------------------------------------o
3 * | This file is part of the RGraph package - you can learn more at: |
5 * | http://www.rgraph.net |
7 * | This package is licensed under the RGraph license. For all kinds of business |
8 * | purposes there is a small one-time licensing fee to pay and for non |
9 * | commercial purposes it is free to use. You can read the full license here: |
11 * | http://www.rgraph.net/LICENSE.txt |
12 * o------------------------------------------------------------------------------o
15 if (typeof(RGraph) == 'undefined') RGraph = {};
18 * The odometer constructor. Pass it the ID of the canvas tag, the start value of the odo,
19 * the end value, and the value that the pointer should point to.
21 * @param string id The ID of the canvas tag
22 * @param int start The start value of the Odo
23 * @param int end The end value of the odo
24 * @param int value The indicated value (what the needle points to)
26 RGraph.Odometer = function (id, start, end, value)
29 this.canvas = document.getElementById(id);
30 this.context = this.canvas.getContext('2d');
31 this.canvas.__object__ = this;
40 * Compatibility with older browsers
42 RGraph.OldBrowserCompat(this.context);
46 'chart.value.text': false,
47 'chart.needle.color': 'black',
48 'chart.needle.width': 2,
49 'chart.needle.head': true,
50 'chart.needle.tail': true,
51 'chart.needle.type': 'pointer',
52 'chart.needle.extra': [],
53 'chart.text.size': 10,
54 'chart.text.color': 'black',
55 'chart.text.font': 'Verdana',
56 'chart.green.max': end * 0.75,
57 'chart.red.min': end * 0.9,
58 'chart.green.color': 'green',
59 'chart.yellow.color': 'yellow',
60 'chart.red.color': 'red',
61 'chart.label.area': 35,
64 'chart.title.background': null,
65 'chart.title.hpos': null,
66 'chart.title.vpos': null,
67 'chart.contextmenu': null,
69 'chart.shadow.inner': false,
70 'chart.shadow.inner.color': 'black',
71 'chart.shadow.inner.offsetx': 3,
72 'chart.shadow.inner.offsety': 3,
73 'chart.shadow.inner.blur': 6,
74 'chart.shadow.outer': false,
75 'chart.shadow.outer.color': '#666',
76 'chart.shadow.outer.offsetx': 0,
77 'chart.shadow.outer.offsety': 0,
78 'chart.shadow.outer.blur': 15,
79 'chart.annotatable': false,
80 'chart.annotate.color': 'black',
81 'chart.scale.decimals': 0,
82 'chart.zoom.factor': 1.5,
83 'chart.zoom.fade.in': true,
84 'chart.zoom.fade.out': true,
85 'chart.zoom.hdir': 'right',
86 'chart.zoom.vdir': 'down',
87 'chart.zoom.frames': 10,
88 'chart.zoom.delay': 50,
89 'chart.zoom.shadow': true,
90 'chart.zoom.mode': 'canvas',
91 'chart.zoom.thumbnail.width': 75,
92 'chart.zoom.thumbnail.height': 75,
93 'chart.zoom.background': true,
94 'chart.zoom.action': 'zoom',
95 'chart.resizable': false,
96 'chart.units.pre': '',
97 'chart.units.post': '',
98 'chart.border': false,
99 'chart.tickmarks.highlighted': false,
100 'chart.zerostart': false,
101 'chart.labels': null,
102 'chart.units.pre': '',
103 'chart.units.post': '',
104 'chart.value.units.pre': '',
105 'chart.value.units.post': ''
108 // Check the common library has been included
109 if (typeof(RGraph) == 'undefined') {
110 alert('[ODO] Fatal error: The common library does not appear to have been included');
118 * @param name string The name of the property to set
119 * @param value mixed The value of the property
121 RGraph.Odometer.prototype.Set = function (name, value)
123 if (name == 'chart.needle.style') {
124 alert('[RGRAPH] The RGraph property chart.needle.style has changed to chart.needle.color');
127 if (name == 'chart.needle.thickness') {
128 name = 'chart.needle.width';
131 this.properties[name.toLowerCase()] = value;
138 * @param name string The name of the property to get
140 RGraph.Odometer.prototype.Get = function (name)
142 return this.properties[name.toLowerCase()];
149 RGraph.Odometer.prototype.Draw = function ()
152 * Fire the onbeforedraw event
154 RGraph.FireCustomEvent(this, 'onbeforedraw');
156 // Work out a few things
157 this.radius = Math.min(this.canvas.width / 2, this.canvas.height / 2) - this.Get('chart.gutter') - (this.Get('chart.border') ? 25 : 0);
158 this.diameter = 2 * this.radius;
159 this.centerx = this.canvas.width / 2;
160 this.centery = this.canvas.height / 2;
161 this.range = this.end - this.start;
163 this.context.lineWidth = this.Get('chart.linewidth');
165 // Draw the background
166 this.DrawBackground();
168 // And lastly, draw the labels
172 this.DrawNeedle(this.value, this.Get('chart.needle.color'));
175 * Draw any extra needles
177 if (this.Get('chart.needle.extra').length > 0) {
178 for (var i=0; i<this.Get('chart.needle.extra').length; ++i) {
179 var needle = this.Get('chart.needle.extra')[i];
180 this.DrawNeedle(needle[0], needle[1]);
186 * Setup the context menu if required
188 if (this.Get('chart.contextmenu')) {
189 RGraph.ShowContext(this);
193 * If the canvas is annotatable, do install the event handlers
195 if (this.Get('chart.annotatable')) {
196 RGraph.Annotate(this);
200 * This bit shows the mini zoom window if requested
202 if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
203 RGraph.ShowZoomWindow(this);
208 * This function enables resizing
210 if (this.Get('chart.resizable')) {
211 RGraph.AllowResizing(this);
215 * Fire the RGraph ondraw event
217 RGraph.FireCustomEvent(this, 'ondraw');
221 * Draws the background
223 RGraph.Odometer.prototype.DrawBackground = function ()
225 this.context.beginPath();
228 * Turn on the shadow if need be
230 if (this.Get('chart.shadow.outer')) {
231 RGraph.SetShadow(this, this.Get('chart.shadow.outer.color'), this.Get('chart.shadow.outer.offsetx'), this.Get('chart.shadow.outer.offsety'), this.Get('chart.shadow.outer.blur'));
234 var backgroundColor = '#eee';
236 // Draw the grey border
237 this.context.fillStyle = backgroundColor;
238 this.context.arc(this.centerx, this.centery, this.radius, 0.0001, 6.28, false);
242 * Turn off the shadow
244 RGraph.NoShadow(this);
248 this.context.strokeStyle = '#666';
249 this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, false);
251 // Now draw a big white circle to make the lines appear as tick marks
252 // This is solely for Chrome
253 this.context.fillStyle = backgroundColor;
254 this.context.arc(this.centerx, this.centery, this.radius, 0, 6.28, false);
258 * Draw more tickmarks
260 this.context.beginPath();
261 this.context.strokeStyle = '#bbb';
263 for (var i=0; i<=360; i+=3) {
264 this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.degrees2Radians(i), false);
265 this.context.lineTo(this.centerx, this.centery);
267 this.context.stroke();
269 this.context.beginPath();
270 this.context.lineWidth = 1;
271 this.context.strokeStyle = 'black';
273 // Now draw a big white circle to make the lines appear as tick marks
274 this.context.fillStyle = backgroundColor;
275 this.context.strokeStyle = backgroundColor;
276 this.context.arc(this.centerx, this.centery, this.radius - 5, 0, 6.28, false);
278 this.context.stroke();
280 // Gray lines at 18 degree intervals
281 this.context.beginPath();
282 this.context.strokeStyle = '#ddd';
283 for (var i=0; i<360; i+=18) {
284 this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.degrees2Radians(i), false);
285 this.context.lineTo(this.centerx, this.centery);
287 this.context.stroke();
289 // Redraw the outer circle
290 this.context.beginPath();
291 this.context.strokeStyle = 'black';
292 this.context.arc(this.centerx, this.centery, this.radius, 0, 6.2830, false);
293 this.context.stroke();
296 * Now draw the center bits shadow if need be
298 if (this.Get('chart.shadow.inner')) {
299 this.context.beginPath();
300 RGraph.SetShadow(this, this.Get('chart.shadow.inner.color'), this.Get('chart.shadow.inner.offsetx'), this.Get('chart.shadow.inner.offsety'), this.Get('chart.shadow.inner.blur'));
301 this.context.arc(this.centerx, this.centery, this.radius - this.Get('chart.label.area'), 0, 6.28, 0);
303 this.context.stroke();
306 * Turn off the shadow
308 RGraph.NoShadow(this);
311 // Now draw the green area
312 var greengrad = this.canvas.getContext('2d').createRadialGradient(this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, this.canvas.width / 2, this.canvas.width / 2);
313 greengrad.addColorStop(0, 'white');
314 greengrad.addColorStop(1, this.Get('chart.green.color'));
316 // Draw the "tick highlight"
317 if (this.Get('chart.tickmarks.highlighted')) {
318 this.context.beginPath();
319 this.context.lineWidth = 5;
320 this.context.strokeStyle = greengrad;
321 this.context.arc(this.centerx, this.centery, this.radius - 2.5,
324 ((this.Get('chart.green.max') / this.end) * 6.2830) - 1.57,
327 this.context.stroke();
329 this.context.lineWidth = 1;
332 this.context.beginPath();
333 this.context.fillStyle = greengrad;
337 this.radius - this.Get('chart.label.area'),
339 ( (this.Get('chart.green.max') / this.end) * 6.2830) - 1.57,
342 this.context.lineTo(this.centerx, this.centery);
343 this.context.closePath();
347 // Now draw the yellow area
348 var yellowgrad = this.canvas.getContext('2d').createRadialGradient(this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, this.canvas.width / 2, this.canvas.width / 2);
349 yellowgrad.addColorStop(0, 'white');
350 yellowgrad.addColorStop(1, this.Get('chart.yellow.color'));
352 // Draw the "tick highlight"
353 if (this.Get('chart.tickmarks.highlighted')) {
354 this.context.beginPath();
355 this.context.lineWidth = 5;
356 this.context.strokeStyle = yellowgrad;
357 this.context.arc(this.centerx, this.centery, this.radius - 2.5, (
359 (this.Get('chart.green.max') / this.end) * 6.2830) - 1.57,
360 ((this.Get('chart.red.min') / this.end) * 6.2830) - 1.57,
363 this.context.stroke();
365 this.context.lineWidth = 1;
368 this.context.beginPath();
369 this.context.fillStyle = yellowgrad;
373 this.radius - this.Get('chart.label.area'),
374 ( (this.Get('chart.green.max') / this.end) * 6.2830) - 1.57,
375 ( (this.Get('chart.red.min') / this.end) * 6.2830) - 1.57,
378 this.context.lineTo(this.centerx, this.centery);
379 this.context.closePath();
383 // Now draw the red area if they're defined
384 var redgrad = this.canvas.getContext('2d').createRadialGradient(this.canvas.width / 2, this.canvas.height / 2, 0, this.canvas.width / 2, this.canvas.height / 2, this.canvas.width / 2, this.canvas.width / 2);
385 redgrad.addColorStop(0, 'white');
386 redgrad.addColorStop(1, this.Get('chart.red.color'));
389 // Draw the "tick highlight"
390 if (this.Get('chart.tickmarks.highlighted')) {
391 this.context.beginPath();
392 this.context.lineWidth = 5;
393 this.context.strokeStyle = redgrad;
394 this.context.arc(this.centerx, this.centery, this.radius - 2.5, ( (this.Get('chart.red.min') / this.end) * 6.2830) - 1.57,(2 * Math.PI) - (0.5 * Math.PI),0);
395 this.context.stroke();
397 this.context.lineWidth = 1;
400 this.context.beginPath();
401 this.context.fillStyle = redgrad;
402 this.context.strokeStyle = redgrad;
406 this.radius - this.Get('chart.label.area'),
407 ( (this.Get('chart.red.min') / this.end) * 6.2830) - 1.57,
408 6.2830 - (0.25 * 6.2830),
411 this.context.lineTo(this.centerx, this.centery);
412 this.context.closePath();
417 * Draw the thick border
419 if (this.Get('chart.border')) {
420 var grad = this.context.createRadialGradient(this.centerx, this.centery, this.radius, this.centerx, this.centery, this.radius + 15);
421 grad.addColorStop(1, '#BEBCB0');
422 grad.addColorStop(0.5, '#F0EFEA');
423 grad.addColorStop(0, '#BEBCB0');
425 this.context.beginPath();
426 this.context.fillStyle = grad;
427 this.context.strokeStyle = grad;
428 this.context.lineWidth = 22;
429 this.context.arc(this.centerx, this.centery, this.radius + 9, 0, 2 * Math.PI, 0);
430 this.context.stroke();
433 // Put the linewidth back to what it was
434 this.context.lineWidth = this.Get('chart.linewidth');
438 * Draw the title if specified
440 if (this.Get('chart.title')) {
441 RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'), null, this.Get('chart.text.size') + 2);
445 // Draw the big tick marks
446 for (var i=18; i<=360; i+=36) {
447 this.context.beginPath();
448 this.context.strokeStyle = '#999';
449 this.context.lineWidth = 2;
450 this.context.arc(this.centerx, this.centery, this.radius - 1, RGraph.degrees2Radians(i), RGraph.degrees2Radians(i+0.01), false);
451 this.context.arc(this.centerx, this.centery, this.radius - 7, RGraph.degrees2Radians(i), RGraph.degrees2Radians(i+0.01), false);
452 this.context.stroke();
459 * Draws the needle of the odometer
461 RGraph.Odometer.prototype.DrawNeedle = function (value, color)
463 // ===== First draw a grey background circle =====
465 this.context.fillStyle = '#999';
467 this.context.beginPath();
468 this.context.moveTo(this.centerx, this.centery);
469 this.context.arc(this.centerx, this.centery, 10, 0, 6.28, false);
471 this.context.closePath();
475 // ===============================================
477 this.context.fillStyle = color
478 this.context.strokeStyle = '#666';
480 // Draw the centre bit
481 this.context.beginPath();
482 this.context.moveTo(this.centerx, this.centery);
483 this.context.arc(this.centerx, this.centery, 8, 0, 6.28, false);
485 this.context.closePath();
487 this.context.stroke();
490 if (this.Get('chart.needle.type') == 'pointer') {
492 this.context.strokeStyle = color;
493 this.context.lineWidth = this.Get('chart.needle.width');
494 this.context.lineCap = 'round';
495 this.context.lineJoin = 'round';
498 this.context.beginPath();
499 // The trailing bit on the opposite side of the dial
500 this.context.beginPath();
501 this.context.moveTo(this.centerx, this.centery);
503 if (this.Get('chart.needle.tail')) {
504 this.context.arc(this.centerx,
507 (((value / this.range) * 360) + 90) / 57.3,
508 (((value / this.range) * 360) + 90 + 0.01) / 57.3, // The 0.01 avoids a bug in ExCanvas and Chrome 6
513 // Draw the long bit on the opposite side
514 this.context.arc(this.centerx,
516 this.radius - this.Get('chart.label.area') - 10,
517 (((value / this.range) * 360) - 90) / 57.3,
518 (((value / this.range) * 360) - 90 + 0.1 ) / 57.3, // The 0.1 avoids a bug in ExCanvas and Chrome 6
521 this.context.closePath();
523 //this.context.stroke();
524 //this.context.fill();
527 } else if (this.Get('chart.needle.type') == 'triangle') {
529 this.context.lineWidth = 0.01;
530 this.context.lineEnd = 'square';
531 this.context.lineJoin = 'miter';
533 this.context.beginPath();
534 this.context.strokeStyle = 'black';
535 this.context.fillStyle = color;
536 this.context.arc(this.centerx, this.centery, 7, (((value / this.range) * 360)) / 57.3, ((((value / this.range) * 360)) + 0.01) / 57.3, 0);
537 this.context.arc(this.centerx, this.centery, 7, (((value / this.range) * 360) + 180) / 57.3, ((((value / this.range) * 360) + 180) + 0.01)/ 57.3, 0);
538 this.context.arc(this.centerx, this.centery, this.radius - this.Get('chart.label.area') - 10, (((value / this.range) * 360) - 90) / 57.3, ((((value / this.range) * 360) - 90) / 57.3) + 0.01, 0);
539 this.context.closePath();
540 this.context.stroke();
544 * This is here to accomodate the MSIE/ExCanvas combo
546 this.context.beginPath();
547 this.context.arc(this.centerx, this.centery, 7, 0, 6.28, 0);
548 this.context.closePath();
553 this.context.stroke();
556 // Draw the mini center circle
557 this.context.beginPath();
558 this.context.fillStyle = color;
559 this.context.arc(this.centerx, this.centery, this.Get('chart.needle.type') == 'pointer' ? 7 : 12, 0.01, 6.2830, false);
562 // This draws the arrow at the end of the line
563 if (this.Get('chart.needle.head') && this.Get('chart.needle.type') == 'pointer') {
564 this.context.lineWidth = 1;
565 this.context.fillStyle = color;
567 // round, bevel, miter
568 this.context.lineJoin = 'miter';
569 this.context.lineCap = 'butt';
571 this.context.beginPath();
572 this.context.arc(this.centerx, this.centery, this.radius - this.Get('chart.label.area')-5, (((value / this.range) * 360) - 90) / 57.3, (((value / this.range) * 360) - 90 + 0.1) / 57.3, false);
573 this.context.arc(this.centerx, this.centery, this.radius - this.Get('chart.label.area') - 20, RGraph.degrees2Radians( ((value / this.range) * 360) - 85), RGraph.degrees2Radians( ((value / this.range) * 360) - 95), 1);
574 this.context.closePath();
577 //this.context.stroke();
581 * Draw a white circle at the centre
583 this.context.beginPath();
584 this.context.fillStyle = 'gray';
585 this.context.moveTo(this.centerx, this.centery);
586 this.context.arc(this.centerx,this.centery,2,0,6.2795,false);
587 this.context.closePath();
593 * Draws the labels for the Odo
595 RGraph.Odometer.prototype.DrawLabels = function ()
597 var context = this.context;
598 var size = this.Get('chart.text.size');
599 var font = this.Get('chart.text.font');
600 var centerx = this.centerx;
601 var centery = this.centery;
602 var r = this.radius - (this.Get('chart.label.area') / 2);
604 var decimals = this.Get('chart.scale.decimals');
605 var labels = this.Get('chart.labels');
606 var units_pre = this.Get('chart.units.pre');
607 var units_post = this.Get('chart.units.post');
610 context.fillStyle = this.Get('chart.text.color');
613 * If label are specified, use those
616 for (var i=0; i<labels.length; ++i) {
621 centerx + (Math.cos(((i / labels.length) * 6.28) - 1.57) * (this.radius - (this.Get('chart.label.area') / 2) ) ), // Sin A = Opp / Hyp
622 centery + (Math.sin(((i / labels.length) * 6.28) - 1.57) * (this.radius - (this.Get('chart.label.area') / 2) ) ), // Cos A = Adj / Hyp
623 String(units_pre + labels[i] + units_post),
629 * If not, use the maximum value
632 RGraph.Text(context, font, size, centerx + (0.588 * r ), centery - (0.809 * r ), String(units_pre + (end * (1/10)).toFixed(decimals) + units_post), 'center', 'center', false, 36);
633 RGraph.Text(context, font, size, centerx + (0.951 * r ), centery - (0.309 * r), String(units_pre + (end * (2/10)).toFixed(decimals) + units_post), 'center', 'center', false, 72);
634 RGraph.Text(context, font, size, centerx + (0.949 * r), centery + (0.287 * r), String(units_pre + (end * (3/10)).toFixed(decimals) + units_post), 'center', 'center', false, 108);
635 RGraph.Text(context, font, size, centerx + (0.588 * r ), centery + (0.809 * r ), String(units_pre + (end * (4/10)).toFixed(decimals) + units_post), 'center', 'center', false, 144);
636 RGraph.Text(context, font, size, centerx, centery + r, String(units_pre + (end * (5/10)).toFixed(decimals) + units_post), 'center', 'center', false, 180);
637 RGraph.Text(context, font, size, centerx - (0.588 * r ), centery + (0.809 * r ), String(units_pre + (end * (6/10)).toFixed(decimals) + units_post), 'center', 'center', false, 216);
638 RGraph.Text(context, font, size, centerx - (0.949 * r), centery + (0.300 * r), a = String(units_pre + (end * (7/10)).toFixed(decimals) + units_post), 'center', 'center', false, 252);
639 RGraph.Text(context, font, size, centerx - (0.951 * r), centery - (0.309 * r), String(units_pre + (end * (8/10)).toFixed(decimals) + units_post), 'center', 'center', false, 288);
640 RGraph.Text(context, font, size, centerx - (0.588 * r ), centery - (0.809 * r ), String(units_pre + (end * (9/10)).toFixed(decimals) + units_post), 'center', 'center', false, 324);
641 RGraph.Text(context, font, size, centerx, centery - r, this.Get('chart.zerostart') ? units_pre + '0' : String(units_pre + (end * (10/10)).toFixed(decimals) + units_post), 'center', 'center', false, 360);
647 * Draw the text label below the center point
649 if (this.Get('chart.value.text')) {
650 context.strokeStyle = 'black';
651 RGraph.Text(context, font, size + 2, centerx, centery + size + 2 + 10, String(this.Get('chart.value.units.pre') + this.value + this.Get('chart.value.units.post')), 'center', 'center', true, null, 'white');