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 bi-polar/age frequency constructor.
20 * @param string id The id of the canvas
21 * @param array left The left set of data points
22 * @param array right The right set of data points
24 RGraph.Bipolar = function (id, left, right)
26 // Get the canvas and context objects
28 this.canvas = document.getElementById(id);
29 this.context = this.canvas.getContext('2d');
30 this.canvas.__object__ = this;
31 this.type = 'bipolar';
38 * Compatibility with older browsers
40 RGraph.OldBrowserCompat(this.context);
43 // The left and right data respectively
46 this.data = [left, right];
50 'chart.xtickinterval': null,
52 'chart.text.size': 10,
53 'chart.text.color': 'black',
54 'chart.text.font': 'Verdana',
55 'chart.title.left': '',
56 'chart.title.right': '',
58 'chart.gutter.center': 60,
60 'chart.title.background': null,
61 'chart.title.hpos': null,
62 'chart.title.vpos': null,
63 'chart.colors': ['#0f0'],
64 'chart.contextmenu': null,
65 'chart.tooltips': null,
66 'chart.tooltips.effect': 'fade',
67 'chart.tooltips.css.class': 'RGraph_tooltip',
68 'chart.tooltips.highlight': true,
69 'chart.units.pre': '',
70 'chart.units.post': '',
71 'chart.shadow': false,
72 'chart.shadow.color': '#666',
73 'chart.shadow.offsetx': 3,
74 'chart.shadow.offsety': 3,
75 'chart.shadow.blur': 3,
76 'chart.annotatable': false,
77 'chart.annotate.color': 'black',
79 'chart.scale.decimals': null,
80 'chart.scale.point': '.',
81 'chart.scale.thousand': ',',
82 'chart.axis.color': 'black',
83 'chart.zoom.factor': 1.5,
84 'chart.zoom.fade.in': true,
85 'chart.zoom.fade.out': true,
86 'chart.zoom.hdir': 'right',
87 'chart.zoom.vdir': 'down',
88 'chart.zoom.frames': 10,
89 'chart.zoom.delay': 50,
90 'chart.zoom.shadow': true,
91 'chart.zoom.mode': 'canvas',
92 'chart.zoom.thumbnail.width': 75,
93 'chart.zoom.thumbnail.height': 75,
94 'chart.zoom.background': true,
95 'chart.zoom.action': 'zoom',
96 'chart.resizable': false,
97 'chart.strokestyle': '#333'
100 // Pad the arrays so they're the same size
101 while (this.left.length < this.right.length) this.left.push(0);
102 while (this.left.length > this.right.length) this.right.push(0);
104 // Check the common library has been included
105 if (typeof(RGraph) == 'undefined') {
106 alert('[BIPOLAR] Fatal error: The common library does not appear to have been included');
114 * @param name string The name of the parameter to set
115 * @param value mixed The value of the paraneter
117 RGraph.Bipolar.prototype.Set = function (name, value)
119 this.properties[name.toLowerCase()] = value;
126 * @param name string The name of the parameter to get
128 RGraph.Bipolar.prototype.Get = function (name)
130 return this.properties[name.toLowerCase()];
137 RGraph.Bipolar.prototype.Draw = function ()
140 * Fire the onbeforedraw event
142 RGraph.FireCustomEvent(this, 'onbeforedraw');
146 * Clear all of this canvases event handlers (the ones installed by RGraph)
148 RGraph.ClearEventListeners(this.id);
151 // Reset the data to what was initially supplied
152 this.left = this.data[0];
153 this.right = this.data[1];
156 * Reset the coords array
164 this.DrawRightBars();
166 if (this.Get('chart.axis.color') != 'black') {
167 this.DrawAxes(); // Draw the axes again (if the axes color is not black)
174 * Setup the context menu if required
176 if (this.Get('chart.contextmenu')) {
177 RGraph.ShowContext(this);
182 * Install the on* event handlers
184 if (this.Get('chart.tooltips')) {
187 // Register the object so that it gets redrawn
188 RGraph.Register(this);
192 * Install the window onclick handler
196 * Install the window event handler
198 var eventHandler_window_click = function ()
202 window.addEventListener('click', eventHandler_window_click, false);
203 RGraph.AddEventListener('window_' + this.id, 'click', eventHandler_window_click);
208 * If the cursor is over a hotspot, change the cursor to a hand
210 var eventHandler_canvas_mousemove = function (e)
212 e = RGraph.FixEventObject(e);
214 var canvas = document.getElementById(this.id);
215 var obj = canvas.__object__;
218 * Get the mouse X/Y coordinates
220 var mouseCoords = RGraph.getMouseXY(e);
223 * Loop through the bars determining if the mouse is over a bar
225 for (var i=0; i<obj.coords.length; i++) {
227 var mouseX = mouseCoords[0]; // In relation to the canvas
228 var mouseY = mouseCoords[1]; // In relation to the canvas
229 var left = obj.coords[i][0];
230 var top = obj.coords[i][1];
231 var width = obj.coords[i][2];
232 var height = obj.coords[i][3];
234 if (mouseX >= left && mouseX <= (left + width ) && mouseY >= top && mouseY <= (top + height) ) {
235 canvas.style.cursor = 'pointer';
240 canvas.style.cursor = 'default';
242 this.canvas.addEventListener('mousemove', eventHandler_canvas_mousemove, false);
243 RGraph.AddEventListener(this.id, 'mouseover', eventHandler_canvas_mousemove);
247 * Install the onclick event handler for the tooltips
249 var eventHandler_canvas_click = function (e)
251 e = RGraph.FixEventObject(e);
253 var canvas = document.getElementById(this.id)
254 var obj = canvas.__object__;
257 * Redraw the graph first, in effect resetting the graph to as it was when it was first drawn
258 * This "deselects" any already selected bar
260 RGraph.Clear(canvas);
264 * Get the mouse X/Y coordinates
266 var mouseCoords = RGraph.getMouseXY(e);
269 * Loop through the bars determining if the mouse is over a bar
271 for (var i=0; i<obj.coords.length; i++) {
273 var mouseX = mouseCoords[0]; // In relation to the canvas
274 var mouseY = mouseCoords[1]; // In relation to the canvas
275 var left = obj.coords[i][0];
276 var top = obj.coords[i][1];
277 var width = obj.coords[i][2];
278 var height = obj.coords[i][3];
280 if (mouseX >= left && mouseX <= (left + width) && mouseY >= top && mouseY <= (top + height) ) {
284 * Show a tooltip if it's defined
285 * FIXME pageX and pageY not supported in MSIE
287 if (obj.Get('chart.tooltips')) {
290 * Get the tooltip text
292 if (typeof(obj.Get('chart.tooltips')) == 'function') {
293 var text = obj.Get('chart.tooltips')(i);
295 } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[i]) == 'function') {
296 var text = obj.Get('chart.tooltips')[i](i);
298 } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
299 var text = obj.Get('chart.tooltips')[i];
305 obj.context.beginPath();
306 obj.context.strokeStyle = 'black';
307 obj.context.fillStyle = 'rgba(255,255,255,0.5)';
308 obj.context.strokeRect(left, top, width, height);
309 obj.context.fillRect(left, top, width, height);
311 obj.context.stroke();
314 RGraph.Tooltip(canvas, text, e.pageX, e.pageY, i);
320 * Stop the event bubbling
326 this.canvas.addEventListener('click', eventHandler_canvas_click, false);
327 RGraph.AddEventListener(this.id, 'click', eventHandler_canvas_click);
329 // This resets the bipolar graph
330 if (RGraph.Registry.Get('chart.tooltip')) {
331 RGraph.Registry.Get('chart.tooltip').style.display = 'none';
332 RGraph.Registry.Set('chart.tooltip', null)
337 * If the canvas is annotatable, do install the event handlers
339 if (this.Get('chart.annotatable')) {
340 RGraph.Annotate(this);
344 * This bit shows the mini zoom window if requested
346 if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
347 RGraph.ShowZoomWindow(this);
352 * This function enables resizing
354 if (this.Get('chart.resizable')) {
355 RGraph.AllowResizing(this);
359 * Fire the RGraph ondraw event
361 RGraph.FireCustomEvent(this, 'ondraw');
368 RGraph.Bipolar.prototype.DrawAxes = function ()
370 // Draw the left set of axes
371 this.context.beginPath();
372 this.context.strokeStyle = this.Get('chart.axis.color');
374 this.axisWidth = (this.canvas.width - this.Get('chart.gutter.center') ) / 2;
375 this.axisHeight = this.canvas.height - (2 * this.Get('chart.gutter'));
377 this.context.moveTo(this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
378 this.context.lineTo(this.axisWidth, this.canvas.height - this.Get('chart.gutter'));
379 this.context.lineTo(this.axisWidth, this.Get('chart.gutter'));
381 this.context.stroke();
383 // Draw the right set of axes
384 this.context.beginPath();
386 this.axisWidth = ((this.canvas.width - this.Get('chart.gutter.center')) / 2) + this.Get('chart.gutter.center');
388 this.context.moveTo(this.axisWidth, this.Get('chart.gutter'));
389 this.context.lineTo(this.axisWidth, this.canvas.height - this.Get('chart.gutter'));
390 this.context.lineTo(this.canvas.width - this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
392 this.context.stroke();
397 * Draws the tick marks on the axes
399 RGraph.Bipolar.prototype.DrawTicks = function ()
401 var numDataPoints = this.left.length;
402 var barHeight = ( (this.canvas.height - (2 * this.Get('chart.gutter')))- (this.left.length * (this.Get('chart.margin') * 2) )) / numDataPoints;
404 // Draw the left Y tick marks
405 for (var i = this.canvas.height - this.Get('chart.gutter'); i >= this.Get('chart.gutter'); i -= (barHeight + ( this.Get('chart.margin') * 2)) ) {
406 if (i < (this.canvas.height - this.Get('chart.gutter')) ) {
407 this.context.beginPath();
408 this.context.moveTo(this.axisWidth - this.Get('chart.gutter.center'), i);
409 this.context.lineTo(this.axisWidth - this.Get('chart.gutter.center') + 3, i);
410 this.context.stroke();
414 //Draw the right axis Y tick marks
415 for (var i = this.canvas.height - this.Get('chart.gutter'); i >= this.Get('chart.gutter'); i -= (barHeight + ( this.Get('chart.margin') * 2)) ) {
416 if (i < (this.canvas.height - this.Get('chart.gutter')) ) {
417 this.context.beginPath();
418 this.context.moveTo(this.axisWidth, i);
419 this.context.lineTo(this.axisWidth - 3, i);
420 this.context.stroke();
424 // Draw the left sides X tick marks
425 var xInterval = (this.canvas.width - (2 * this.Get('chart.gutter')) - this.Get('chart.gutter.center')) / 10;
427 // Is chart.xtickinterval specified ? If so, use that.
428 if (typeof(this.Get('chart.xtickinterval')) == 'number') {
429 xInterval = this.Get('chart.xtickinterval');
432 for (i=this.Get('chart.gutter'); i<(this.canvas.width - this.Get('chart.gutter.center') ) / 2; i += xInterval) {
433 this.context.beginPath();
434 this.context.moveTo(i, this.canvas.height - this.Get('chart.gutter')); // 4 is the tick height
435 this.context.lineTo(i, (this.canvas.height - this.Get('chart.gutter')) + 4);
436 this.context.closePath();
438 this.context.stroke();
441 // Draw the right sides X tick marks
442 var stoppingPoint = (this.canvas.width - (2 * this.Get('chart.gutter')) - this.Get('chart.gutter.center')) / 2;
443 var stoppingPoint = stoppingPoint + this.Get('chart.gutter.center') + this.Get('chart.gutter')
445 for (i=this.canvas.width - this.Get('chart.gutter'); i > stoppingPoint; i-=xInterval) {
446 this.context.beginPath();
447 this.context.moveTo(i, this.canvas.height - this.Get('chart.gutter'));
448 this.context.lineTo(i, (this.canvas.height - this.Get('chart.gutter')) + 4);
449 this.context.closePath();
451 this.context.stroke();
454 // Store this for later
455 this.barHeight = barHeight;
460 * Figures out the maximum value, or if defined, uses xmax
462 RGraph.Bipolar.prototype.GetMax = function()
465 var dec = this.Get('chart.scale.decimals');
467 // chart.xmax defined
468 if (this.Get('chart.xmax')) {
470 max = this.Get('chart.xmax');
473 this.scale[0] = Number((max / 5) * 1).toFixed(dec);
474 this.scale[1] = Number((max / 5) * 2).toFixed(dec);
475 this.scale[2] = Number((max / 5) * 3).toFixed(dec);
476 this.scale[3] = Number((max / 5) * 4).toFixed(dec);
477 this.scale[4] = Number(max).toFixed(dec);
482 // Generate the scale ourselves
484 this.leftmax = RGraph.array_max(this.left);
485 this.rightmax = RGraph.array_max(this.right);
486 max = Math.max(this.leftmax, this.rightmax);
488 this.scale = RGraph.getScale(max, this);
489 this.scale[0] = Number(this.scale[0]).toFixed(dec);
490 this.scale[1] = Number(this.scale[1]).toFixed(dec);
491 this.scale[2] = Number(this.scale[2]).toFixed(dec);
492 this.scale[3] = Number(this.scale[3]).toFixed(dec);
493 this.scale[4] = Number(this.scale[4]).toFixed(dec);
495 this.max = this.scale[4];
498 // Don't need to return it as it is stored in this.max
503 * Function to draw the left hand bars
505 RGraph.Bipolar.prototype.DrawLeftBars = function ()
507 // Set the stroke colour
508 this.context.strokeStyle = this.Get('chart.strokestyle');
510 for (i=0; i<this.left.length; ++i) {
513 * Turn on a shadow if requested
515 if (this.Get('chart.shadow')) {
516 this.context.shadowColor = this.Get('chart.shadow.color');
517 this.context.shadowBlur = this.Get('chart.shadow.blur');
518 this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
519 this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
522 this.context.beginPath();
525 if (this.Get('chart.colors')[i]) {
526 this.context.fillStyle = this.Get('chart.colors')[i];
530 * Work out the coordinates
532 var width = ( (this.left[i] / this.max) * ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter')) ) / 2) );
534 this.axisWidth - this.Get('chart.gutter.center') - width,
535 this.Get('chart.margin') + (i * ( (this.canvas.height - (2 * this.Get('chart.gutter')) ) / this.left.length)) + this.Get('chart.gutter'),
540 // Draw the IE shadow if necessary
541 if (document.all && this.Get('chart.shadow')) {
542 this.DrawIEShadow(coords);
546 this.context.strokeRect(coords[0], coords[1], coords[2], coords[3]);
547 this.context.fillRect(coords[0], coords[1], coords[2], coords[3]);
549 this.context.stroke();
553 * Add the coordinates to the coords array
564 * Turn off any shadow
566 RGraph.NoShadow(this);
571 * Function to draw the right hand bars
573 RGraph.Bipolar.prototype.DrawRightBars = function ()
575 // Set the stroke colour
576 this.context.strokeStyle = this.Get('chart.strokestyle');
579 * Turn on a shadow if requested
581 if (this.Get('chart.shadow')) {
582 this.context.shadowColor = this.Get('chart.shadow.color');
583 this.context.shadowBlur = this.Get('chart.shadow.blur');
584 this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
585 this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
588 for (var i=0; i<this.right.length; ++i) {
589 this.context.beginPath();
592 if (this.Get('chart.colors')[i]) {
593 this.context.fillStyle = this.Get('chart.colors')[i];
597 var width = ( (this.right[i] / this.max) * ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter')) ) / 2) );
600 this.Get('chart.margin') + (i * ((this.canvas.height - (2 * this.Get('chart.gutter'))) / this.right.length)) + this.Get('chart.gutter'),
605 // Draw the IE shadow if necessary
606 if (document.all && this.Get('chart.shadow')) {
607 this.DrawIEShadow(coords);
609 this.context.strokeRect(coords[0], coords[1], coords[2], coords[3]);
610 this.context.fillRect(coords[0], coords[1], coords[2], coords[3]);
612 this.context.closePath();
615 * Add the coordinates to the coords array
625 this.context.stroke();
628 * Turn off any shadow
630 RGraph.NoShadow(this);
637 RGraph.Bipolar.prototype.DrawLabels = function ()
639 this.context.fillStyle = this.Get('chart.text.color');
641 var labelPoints = new Array();
642 var font = this.Get('chart.text.font');
643 var size = this.Get('chart.text.size');
645 var max = Math.max(this.left.length, this.right.length);
647 for (i=0; i<max; ++i) {
648 var barAreaHeight = this.canvas.height - (2 * this.Get('chart.gutter'));
649 var barHeight = barAreaHeight / this.left.length;
650 var yPos = (i * barAreaHeight) + this.Get('chart.gutter');
652 labelPoints.push(this.Get('chart.gutter') + (i * barHeight) + (barHeight / 2) + 5);
655 for (i=0; i<labelPoints.length; ++i) {
656 RGraph.Text(this.context, this.Get('chart.text.font'),
657 this.Get('chart.text.size'),
658 this.canvas.width / 2,
660 String(this.Get('chart.labels')[i] ? this.Get('chart.labels')[i] : ''), null, 'center');
663 // Now draw the X labels for the left hand side
664 RGraph.Text(this.context, font, size, this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[4], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
665 RGraph.Text(this.context, font, size, this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (1/5), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[3], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
666 RGraph.Text(this.context, font, size, this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (2/5), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[2], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
667 RGraph.Text(this.context, font, size, this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (3/5), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[1], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
668 RGraph.Text(this.context, font, size, this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (4/5), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[0], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
670 // Now draw the X labels for the right hand side
671 RGraph.Text(this.context, font, size, this.canvas.width - this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter') + 14, RGraph.number_format(this, this.scale[4], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
672 RGraph.Text(this.context, font, size, this.canvas.width - (this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (1/5)), this.canvas.height - this.Get('chart.gutter') + 14,RGraph.number_format(this, this.scale[3], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
673 RGraph.Text(this.context, font, size, this.canvas.width - (this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (2/5)), this.canvas.height - this.Get('chart.gutter') + 14,RGraph.number_format(this, this.scale[2], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
674 RGraph.Text(this.context, font, size, this.canvas.width - (this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (3/5)), this.canvas.height - this.Get('chart.gutter') + 14,RGraph.number_format(this, this.scale[1], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
675 RGraph.Text(this.context, font, size, this.canvas.width - (this.Get('chart.gutter') + ((this.canvas.width - this.Get('chart.gutter.center') - (2 * this.Get('chart.gutter'))) / 2) * (4/5)), this.canvas.height - this.Get('chart.gutter') + 14,RGraph.number_format(this, this.scale[0], this.Get('chart.units.pre'), this.Get('chart.units.post')), null, 'center');
681 RGraph.Bipolar.prototype.DrawTitles = function ()
683 RGraph.Text(this.context, this.Get('chart.text.font'), this.Get('chart.text.size'), 30, (this.Get('chart.gutter') / 2) + 5, String(this.Get('chart.title.left')), 'center');
684 RGraph.Text(this.context,this.Get('chart.text.font'), this.Get('chart.text.size'), this.canvas.width - 30, (this.Get('chart.gutter') / 2) + 5, String(this.Get('chart.title.right')), 'center', 'right');
686 // Draw the main title for the whole chart
687 RGraph.DrawTitle(this.canvas, this.Get('chart.title'), this.Get('chart.gutter'));
692 * This function is used by MSIE only to manually draw the shadow
694 * @param array coords The coords for the bar
696 RGraph.Bipolar.prototype.DrawIEShadow = function (coords)
698 var prevFillStyle = this.context.fillStyle;
699 var offsetx = this.Get('chart.shadow.offsetx');
700 var offsety = this.Get('chart.shadow.offsety');
702 this.context.lineWidth = this.Get('chart.linewidth');
703 this.context.fillStyle = this.Get('chart.shadow.color');
704 this.context.beginPath();
707 this.context.fillRect(coords[0] + offsetx, coords[1] + offsety, coords[2],coords[3]);
711 // Change the fillstyle back to what it was
712 this.context.fillStyle = prevFillStyle;