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 line chart constructor
20 * @param object canvas The cxanvas object
21 * @param array data The chart data
22 * @param array ... Other lines to plot
24 RGraph.Line = function (id)
26 // Get the canvas and context objects
28 this.canvas = document.getElementById(id);
29 this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null;
30 this.canvas.__object__ = this;
34 this.hasnegativevalues = false;
40 * Compatibility with older browsers
42 RGraph.OldBrowserCompat(this.context);
45 // Various config type stuff
47 'chart.background.barcolor1': 'rgba(0,0,0,0)',
48 'chart.background.barcolor2': 'rgba(0,0,0,0)',
49 'chart.background.grid': 1,
50 'chart.background.grid.width': 1,
51 'chart.background.grid.hsize': 25,
52 'chart.background.grid.vsize': 25,
53 'chart.background.grid.color': '#ddd',
54 'chart.background.grid.vlines': true,
55 'chart.background.grid.hlines': true,
56 'chart.background.grid.border': true,
57 'chart.background.grid.autofit':false,
58 'chart.background.grid.autofit.numhlines': 7,
59 'chart.background.grid.autofit.numvlines': 20,
60 'chart.background.hbars': null,
62 'chart.labels.ingraph': null,
63 'chart.labels.above': false,
64 'chart.labels.above.size': 8,
66 'chart.smallxticks': 3,
67 'chart.largexticks': 5,
69 'chart.smallyticks': 3,
70 'chart.largeyticks': 5,
72 'chart.colors': ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff'],
74 'chart.tickmarks.dot.color': 'white',
75 'chart.tickmarks': null,
78 'chart.tickdirection': -1,
79 'chart.yaxispoints': 5,
80 'chart.fillstyle': null,
81 'chart.xaxispos': 'bottom',
82 'chart.yaxispos': 'left',
84 'chart.text.size': 10,
85 'chart.text.angle': 0,
86 'chart.text.color': 'black',
87 'chart.text.font': 'Verdana',
91 'chart.title.background': null,
92 'chart.title.hpos': null,
93 'chart.title.vpos': null,
94 'chart.title.xaxis': '',
95 'chart.title.yaxis': '',
96 'chart.title.xaxis.pos': 0.25,
97 'chart.title.yaxis.pos': 0.25,
98 'chart.shadow': false,
99 'chart.shadow.offsetx': 2,
100 'chart.shadow.offsety': 2,
101 'chart.shadow.blur': 3,
102 'chart.shadow.color': 'rgba(0,0,0,0.5)',
103 'chart.tooltips': null,
104 'chart.tooltips.effect': 'fade',
105 'chart.tooltips.css.class': 'RGraph_tooltip',
106 'chart.tooltips.coords.adjust': [0,0],
107 'chart.tooltips.highlight': true,
108 'chart.stepped': false,
111 'chart.key.background': 'white',
112 'chart.key.position': 'graph',
113 'chart.key.shadow': false,
114 'chart.key.shadow.color': '#666',
115 'chart.key.shadow.blur': 3,
116 'chart.key.shadow.offsetx': 2,
117 'chart.key.shadow.offsety': 2,
118 'chart.key.position.gutter.boxed': true,
119 'chart.key.position.x': null,
120 'chart.key.position.y': null,
121 'chart.key.color.shape': 'square',
122 'chart.key.rounded': true,
124 'chart.contextmenu': null,
126 'chart.ylabels': true,
127 'chart.ylabels.count': 5,
128 'chart.ylabels.inside': false,
129 'chart.ylabels.invert': false,
130 'chart.ylabels.specific': null,
132 'chart.xlabels.inside': false,
133 'chart.xlabels.inside.color': 'rgba(255,255,255,0.5)',
135 'chart.noaxes': false,
136 'chart.noyaxis': false,
137 'chart.noxaxis': false,
139 'chart.noendxtick': false,
140 'chart.units.post': '',
141 'chart.units.pre': '',
142 'chart.scale.decimals': null,
143 'chart.scale.point': '.',
144 'chart.scale.thousand': ',',
145 'chart.crosshairs': false,
146 'chart.crosshairs.color': '#333',
147 'chart.annotatable': false,
148 'chart.annotate.color': 'black',
149 'chart.axesontop': false,
150 'chart.filled.range': false,
151 'chart.variant': null,
152 'chart.axis.color': 'black',
153 'chart.zoom.factor': 1.5,
154 'chart.zoom.fade.in': true,
155 'chart.zoom.fade.out': true,
156 'chart.zoom.hdir': 'right',
157 'chart.zoom.vdir': 'down',
158 'chart.zoom.frames': 15,
159 'chart.zoom.delay': 33,
160 'chart.zoom.shadow': true,
161 'chart.zoom.mode': 'canvas',
162 'chart.zoom.thumbnail.width': 75,
163 'chart.zoom.thumbnail.height': 75,
164 'chart.zoom.background': true,
165 'chart.zoom.action': 'zoom',
166 'chart.backdrop': false,
167 'chart.backdrop.size': 30,
168 'chart.backdrop.alpha': 0.2,
169 'chart.resizable': false,
170 'chart.adjustable': false,
171 'chart.noredraw': false,
172 'chart.outofbounds': false,
173 'chart.chromefix': true
177 * Change null arguments to empty arrays
179 for (var i=1; i<arguments.length; ++i) {
180 if (typeof(arguments[i]) == 'null' || !arguments[i]) {
185 // Check the common library has been included
186 if (typeof(RGraph) == 'undefined') {
187 alert('[LINE] Fatal error: The common library does not appear to have been included');
192 * Store the original data. Thiss aqlso allows for giving arguments as one big array.
194 this.original_data = [];
196 for (var i=1; i<arguments.length; ++i) {
197 if (arguments[1] && typeof(arguments[1]) == 'object' && arguments[1][0] && typeof(arguments[1][0]) == 'object' && typeof(arguments[1][0][0]) == 'number') {
201 for (var i=0; i<arguments[1].length; ++i) {
202 tmp[i] = RGraph.array_clone(arguments[1][i]);
205 for (var j=0; j<tmp.length; ++j) {
206 this.original_data[j] = RGraph.array_clone(tmp[j]);
210 this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
216 alert('[LINE] Fatal error: no canvas support');
221 * Store the data here as one big array
225 for (var i=1; i<arguments.length; ++i) {
226 for (var j=0; j<arguments[i].length; ++j) {
227 this.data_arr.push(arguments[i][j]);
234 * An all encompassing accessor
236 * @param string name The name of the property
237 * @param mixed value The value of the property
239 RGraph.Line.prototype.Set = function (name, value)
241 // Consolidate the tooltips
242 if (name == 'chart.tooltips') {
246 for (var i=1; i<arguments.length; i++) {
247 if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
248 for (var j=0; j<arguments[i].length; j++) {
249 tooltips.push(arguments[i][j]);
252 } else if (typeof(arguments[i]) == 'function') {
253 tooltips = arguments[i];
256 tooltips.push(arguments[i]);
260 // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
265 * Reverse the tickmarks to make them correspond to the right line
267 if (name == 'chart.tickmarks' && typeof(value) == 'object' && value) {
268 value = RGraph.array_reverse(value);
272 * Inverted Y axis should show the bottom end of the scale
274 if (name == 'chart.ylabels.invert' && value && this.Get('chart.ymin') == null) {
275 this.Set('chart.ymin', 0);
279 * If (buggy) Chrome and the linewidth is 1, change it to 1.01
281 if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/) && value == 1) {
285 this.properties[name] = value;
290 * An all encompassing accessor
292 * @param string name The name of the property
294 RGraph.Line.prototype.Get = function (name)
296 return this.properties[name];
301 * The function you call to draw the line chart
303 RGraph.Line.prototype.Draw = function ()
306 * Fire the onbeforedraw event
308 RGraph.FireCustomEvent(this, 'onbeforedraw');
311 * Clear all of this canvases event handlers (the ones installed by RGraph)
313 RGraph.ClearEventListeners(this.id);
317 * Check for Chrome 6 and shadow
319 * TODO Remove once it's been fixed (for a while)
320 * SEARCH TAGS: CHROME FIX SHADOW BUG
322 if ( this.Get('chart.shadow')
323 && navigator.userAgent.match(/Chrome/)
324 && this.Get('chart.linewidth') <= 1
325 && this.Get('chart.chromefix')
326 && this.Get('chart.shadow.blur') > 0) {
327 alert('[RGRAPH WARNING] Chrome 6 has a shadow bug, meaning you should increase the linewidth to at least 1.01');
331 // Cache the gutter as an object variable
332 this.gutter = this.Get('chart.gutter');
334 // Reset the data back to that which was initially supplied
335 this.data = RGraph.array_clone(this.original_data);
338 // Reset the max value
342 * Reverse the datasets so that the data and the labels tally
344 this.data = RGraph.array_reverse(this.data);
346 if (this.Get('chart.filled') && !this.Get('chart.filled.range') && this.data.length > 1) {
348 var accumulation = [];
350 for (var set=0; set<this.data.length; ++set) {
351 for (var point=0; point<this.data[set].length; ++point) {
352 this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
353 accumulation[point] = this.data[set][point];
359 * Get the maximum Y scale value
361 if (this.Get('chart.ymax')) {
363 this.max = this.Get('chart.ymax');
364 this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
367 ( ((this.max - this.min) * (1/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
368 ( ((this.max - this.min) * (2/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
369 ( ((this.max - this.min) * (3/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
370 ( ((this.max - this.min) * (4/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
371 this.max.toFixed(this.Get('chart.scale.decimals'))
374 // Check for negative values
375 if (!this.Get('chart.outofbounds')) {
376 for (dataset=0; dataset<this.data.length; ++dataset) {
377 for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
379 // Check for negative values
380 this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
387 this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
389 // Work out the max Y value
390 for (dataset=0; dataset<this.data.length; ++dataset) {
391 for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
393 this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);
395 // Check for negative values
396 if (!this.Get('chart.outofbounds')) {
397 this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
402 // 20th April 2009 - moved out of the above loop
403 this.scale = RGraph.getScale(Math.abs(parseFloat(this.max)), this);
404 this.max = this.scale[4] ? this.scale[4] : 0;
406 if (this.Get('chart.ymin')) {
407 this.scale[0] = ((this.max - this.Get('chart.ymin')) * (1/5)) + this.Get('chart.ymin');
408 this.scale[1] = ((this.max - this.Get('chart.ymin')) * (2/5)) + this.Get('chart.ymin');
409 this.scale[2] = ((this.max - this.Get('chart.ymin')) * (3/5)) + this.Get('chart.ymin');
410 this.scale[3] = ((this.max - this.Get('chart.ymin')) * (4/5)) + this.Get('chart.ymin');
411 this.scale[4] = ((this.max - this.Get('chart.ymin')) * (5/5)) + this.Get('chart.ymin');
414 if (typeof(this.Get('chart.scale.decimals')) == 'number') {
415 this.scale[0] = Number(this.scale[0]).toFixed(this.Get('chart.scale.decimals'));
416 this.scale[1] = Number(this.scale[1]).toFixed(this.Get('chart.scale.decimals'));
417 this.scale[2] = Number(this.scale[2]).toFixed(this.Get('chart.scale.decimals'));
418 this.scale[3] = Number(this.scale[3]).toFixed(this.Get('chart.scale.decimals'));
419 this.scale[4] = Number(this.scale[4]).toFixed(this.Get('chart.scale.decimals'));
424 * Setup the context menu if required
426 if (this.Get('chart.contextmenu')) {
427 RGraph.ShowContext(this);
431 * Reset the coords array otherwise it will keep growing
436 * Work out a few things. They need to be here because they depend on things you can change before you
437 * call Draw() but after you instantiate the object
439 this.grapharea = this.canvas.height - ( (2 * this.gutter));
440 this.halfgrapharea = this.grapharea / 2;
441 this.halfTextHeight = this.Get('chart.text.size') / 2;
443 // Check the combination of the X axis position and if there any negative values
445 // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
446 // are multiple graphs on the page
447 if (this.Get('chart.xaxispos') == 'bottom' && this.hasnegativevalues && navigator.userAgent.indexOf('Opera') == -1) {
448 alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
451 if (this.Get('chart.variant') == '3d') {
452 RGraph.Draw3DAxes(this);
455 // Progressively Draw the chart
456 RGraph.background.Draw(this);
459 * Draw any horizontal bars that have been defined
461 if (this.Get('chart.background.hbars') && this.Get('chart.background.hbars').length > 0) {
462 RGraph.DrawBars(this);
465 if (this.Get('chart.axesontop') == false) {
470 * Handle the appropriate shadow color. This now facilitates an array of differing
473 var shadowColor = this.Get('chart.shadow.color');
475 if (typeof(shadowColor) == 'object') {
476 shadowColor = RGraph.array_reverse(RGraph.array_clone(this.Get('chart.shadow.color')));
479 for (var i=(this.data.length - 1), j=0; i>=0; i--, j++) {
481 this.context.beginPath();
484 * Turn on the shadow if required
486 if (this.Get('chart.shadow') && !this.Get('chart.filled')) {
489 * Accommodate an array of shadow colors as well as a single string
491 if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
492 this.context.shadowColor = shadowColor[i];
493 } else if (typeof(shadowColor) == 'object') {
494 this.context.shadowColor = shadowColor[0];
495 } else if (typeof(shadowColor) == 'string') {
496 this.context.shadowColor = shadowColor;
499 this.context.shadowBlur = this.Get('chart.shadow.blur');
500 this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
501 this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
503 } else if (this.Get('chart.filled') && this.Get('chart.shadow')) {
504 alert('[LINE] Shadows are not permitted when the line is filled');
511 if (this.Get('chart.fillstyle')) {
512 if (typeof(this.Get('chart.fillstyle')) == 'object' && this.Get('chart.fillstyle')[j]) {
513 var fill = this.Get('chart.fillstyle')[j];
515 } else if (typeof(this.Get('chart.fillstyle')) == 'string') {
516 var fill = this.Get('chart.fillstyle');
519 alert('[LINE] Warning: chart.fillstyle must be either a string or an array with the same number of elements as you have sets of data');
521 } else if (this.Get('chart.filled')) {
522 var fill = this.Get('chart.colors')[j];
529 * Figure out the tickmark to use
531 if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'object') {
532 var tickmarks = this.Get('chart.tickmarks')[i];
533 } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'string') {
534 var tickmarks = this.Get('chart.tickmarks');
535 } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'function') {
536 var tickmarks = this.Get('chart.tickmarks');
538 var tickmarks = null;
542 this.DrawLine(this.data[i],
543 this.Get('chart.colors')[j],
545 this.GetLineWidth(j),
548 this.context.stroke();
563 * If tooltips are defined, handle them
565 if (this.Get('chart.tooltips') && (this.Get('chart.tooltips').length || typeof(this.Get('chart.tooltips')) == 'function')) {
567 // Need to register this object for redrawing
568 if (this.Get('chart.tooltips.highlight')) {
569 RGraph.Register(this);
572 canvas_onmousemove_func = function (e)
574 e = RGraph.FixEventObject(e);
576 var canvas = e.target;
577 var context = canvas.getContext('2d');
578 var obj = canvas.__object__;
579 var point = obj.getPoint(e);
581 if (obj.Get('chart.tooltips.highlight')) {
582 RGraph.Register(obj);
586 && typeof(point[0]) == 'object'
587 && typeof(point[1]) == 'number'
588 && typeof(point[2]) == 'number'
589 && typeof(point[3]) == 'number'
592 // point[0] is the graph object
593 var xCoord = point[1];
594 var yCoord = point[2];
597 if ((obj.Get('chart.tooltips')[idx] || typeof(obj.Get('chart.tooltips')) == 'function')) {
599 // Get the tooltip text
600 if (typeof(obj.Get('chart.tooltips')) == 'function') {
601 var text = obj.Get('chart.tooltips')(idx);
603 } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[idx]) == 'function') {
604 var text = obj.Get('chart.tooltips')[idx](idx);
606 } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
607 var text = String(obj.Get('chart.tooltips')[idx]);
613 // Chnage the pointer to a hand
614 canvas.style.cursor = 'pointer';
617 * If the tooltip is the same one as is currently visible (going by the array index), don't do squat and return.
619 if (RGraph.Registry.Get('chart.tooltip') && RGraph.Registry.Get('chart.tooltip').__index__ == idx && RGraph.Registry.Get('chart.tooltip').__canvas__.id == canvas.id) {
626 if (obj.Get('chart.tooltips.highlight')) {
631 // SHOW THE CORRECT TOOLTIP
632 RGraph.Tooltip(canvas, text, e.pageX, e.pageY, idx);
634 // Store the tooltip index on the tooltip object
635 RGraph.Registry.Get('chart.tooltip').__index__ = Number(idx);
638 * Highlight the graph
640 if (obj.Get('chart.tooltips.highlight')) {
642 context.moveTo(xCoord, yCoord);
643 context.arc(xCoord, yCoord, 2, 0, 6.28, 0);
644 context.strokeStyle = '#999';
645 context.fillStyle = 'white';
656 * Not over a hotspot?
658 canvas.style.cursor = 'default';
661 this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
662 RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
679 * If the axes have been requested to be on top, do that
681 if (this.Get('chart.axesontop')) {
691 * Draw the range if necessary
695 // Draw a key if necessary
696 if (this.Get('chart.key').length) {
697 RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
701 * Draw " above" labels if enabled
703 if (this.Get('chart.labels.above')) {
704 this.DrawAboveLabels();
708 * Draw the "in graph" labels
710 RGraph.DrawInGraphLabels(this);
715 RGraph.DrawCrosshairs(this);
718 * If the canvas is annotatable, do install the event handlers
720 if (this.Get('chart.annotatable')) {
721 RGraph.Annotate(this);
725 * Redraw the lines if a filled range is on the cards
727 if (this.Get('chart.filled') && this.Get('chart.filled.range') && this.data.length == 2) {
729 this.context.beginPath();
730 var len = this.coords.length / 2;
731 this.context.lineWidth = this.Get('chart.linewidth');
732 this.context.strokeStyle = this.Get('chart.colors')[0];
734 for (var i=0; i<len; ++i) {
736 this.context.moveTo(this.coords[i][0], this.coords[i][1]);
738 this.context.lineTo(this.coords[i][0], this.coords[i][1]);
742 this.context.stroke();
745 this.context.beginPath();
747 if (this.Get('chart.colors')[1]) {
748 this.context.strokeStyle = this.Get('chart.colors')[1];
751 for (var i=this.coords.length - 1; i>=len; --i) {
752 if (i == (this.coords.length - 1) ) {
753 this.context.moveTo(this.coords[i][0], this.coords[i][1]);
755 this.context.lineTo(this.coords[i][0], this.coords[i][1]);
759 this.context.stroke();
760 } else if (this.Get('chart.filled') && this.Get('chart.filled.range')) {
761 alert('[LINE] You must have only two sets of data for a filled range chart');
765 * This bit shows the mini zoom window if requested
767 if (this.Get('chart.zoom.mode') == 'thumbnail') {
768 RGraph.ShowZoomWindow(this);
772 * This function enables the zoom in area mode
774 if (this.Get('chart.zoom.mode') == 'area') {
775 RGraph.ZoomArea(this);
779 * This function enables resizing
781 if (this.Get('chart.resizable')) {
782 RGraph.AllowResizing(this);
786 * This function enables adjustments
788 if (this.Get('chart.adjustable')) {
789 RGraph.AllowAdjusting(this);
793 * Fire the RGraph ondraw event
795 RGraph.FireCustomEvent(this, 'ondraw');
802 RGraph.Line.prototype.DrawAxes = function ()
804 var gutter = this.gutter;
806 // Don't draw the axes?
807 if (this.Get('chart.noaxes')) {
811 // Turn any shadow off
812 RGraph.NoShadow(this);
814 this.context.lineWidth = 1;
815 this.context.strokeStyle = this.Get('chart.axis.color');
816 this.context.beginPath();
819 if (this.Get('chart.noxaxis') == false) {
820 if (this.Get('chart.xaxispos') == 'center') {
821 this.context.moveTo(gutter, this.grapharea / 2 + gutter);
822 this.context.lineTo(this.canvas.width - gutter, this.grapharea / 2 + gutter);
824 this.context.moveTo(gutter, this.canvas.height - gutter);
825 this.context.lineTo(this.canvas.width - gutter, this.canvas.height - gutter);
830 if (this.Get('chart.noyaxis') == false) {
831 if (this.Get('chart.yaxispos') == 'left') {
832 this.context.moveTo(gutter, gutter);
833 this.context.lineTo(gutter, this.canvas.height - (gutter) );
835 this.context.moveTo(this.canvas.width - gutter, gutter);
836 this.context.lineTo(this.canvas.width - gutter, this.canvas.height - gutter );
841 * Draw the X tickmarks
843 if (this.Get('chart.noxaxis') == false) {
844 var xTickInterval = (this.canvas.width - (2 * gutter)) / (this.Get('chart.xticks') ? this.Get('chart.xticks') : this.data[0].length);
846 for (x=gutter + (this.Get('chart.yaxispos') == 'left' ? xTickInterval : 0); x<=(this.canvas.width - gutter + 1 ); x+=xTickInterval) {
848 if (this.Get('chart.yaxispos') == 'right' && x >= (this.canvas.width - gutter - 1) ) {
852 // If the last tick is not desired...
853 if (this.Get('chart.noendxtick')) {
854 if (this.Get('chart.yaxispos') == 'left' && x >= (this.canvas.width - gutter)) {
856 } else if (this.Get('chart.yaxispos') == 'right' && x == gutter) {
861 var yStart = this.Get('chart.xaxispos') == 'center' ? (this.canvas.height / 2) - 3 : this.canvas.height - gutter;
862 var yEnd = this.Get('chart.xaxispos') == 'center' ? yStart + 6 : this.canvas.height - gutter - (x % 60 == 0 ? this.Get('chart.largexticks') * this.Get('chart.tickdirection') : this.Get('chart.smallxticks') * this.Get('chart.tickdirection'));
864 this.context.moveTo(x, yStart);
865 this.context.lineTo(x, yEnd);
868 // Draw an extra tickmark if there is no X axis, but there IS a Y axis
869 } else if (this.Get('chart.noyaxis') == false) {
871 if (this.Get('chart.yaxispos') == 'left') {
872 this.context.moveTo(this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
873 this.context.lineTo(this.Get('chart.gutter') - this.Get('chart.smallyticks'), this.canvas.height - this.Get('chart.gutter'));
875 this.context.moveTo(this.canvas.width - this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
876 this.context.lineTo(this.canvas.width - this.Get('chart.gutter') + this.Get('chart.smallyticks'), this.canvas.height - this.Get('chart.gutter'));
881 * Draw the Y tickmarks
883 if (this.Get('chart.noyaxis') == false) {
887 if (this.Get('chart.yaxispos') == 'right') {
888 adjustment = (this.canvas.width - (2 * gutter));
891 if (this.Get('chart.xaxispos') == 'center') {
892 var interval = (this.grapharea / 10);
893 var lineto = (this.Get('chart.yaxispos') == 'left' ? gutter : this.canvas.width - gutter + this.Get('chart.smallyticks'));
895 // Draw the upper halves Y tick marks
896 for (y=gutter; y < (this.grapharea / 2) + gutter; y+=interval) {
897 this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? gutter - this.Get('chart.smallyticks') : this.canvas.width - gutter), y);
898 this.context.lineTo(lineto, y);
901 // Draw the lower halves Y tick marks
902 for (y=gutter + (this.halfgrapharea) + interval; y <= this.grapharea + gutter; y+=interval) {
903 this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? gutter - this.Get('chart.smallyticks') : this.canvas.width - gutter), y);
904 this.context.lineTo(lineto, y);
908 var lineto = (this.Get('chart.yaxispos') == 'left' ? gutter - this.Get('chart.smallyticks') : this.canvas.width - gutter + this.Get('chart.smallyticks'));
910 for (y=gutter; y < (this.canvas.height - gutter) && counter < 10; y+=( (this.canvas.height - (2 * gutter)) / 10) ) {
912 this.context.moveTo(gutter + adjustment, y);
913 this.context.lineTo(lineto, y);
915 var counter = counter +1;
919 // Draw an extra X tickmark
920 } else if (this.Get('chart.noxaxis') == false) {
921 if (this.Get('chart.yaxispos') == 'left') {
922 this.context.moveTo(this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
923 this.context.lineTo(this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter') + this.Get('chart.smallxticks'));
925 this.context.moveTo(this.canvas.width - this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter'));
926 this.context.lineTo(this.canvas.width - this.Get('chart.gutter'), this.canvas.height - this.Get('chart.gutter') + this.Get('chart.smallxticks'));
930 this.context.stroke();
935 * Draw the text labels for the axes
937 RGraph.Line.prototype.DrawLabels = function ()
939 this.context.strokeStyle = 'black';
940 this.context.fillStyle = this.Get('chart.text.color');
941 this.context.lineWidth = 1;
943 // Turn off any shadow
944 RGraph.NoShadow(this);
946 // This needs to be here
947 var font = this.Get('chart.text.font');
948 var gutter = this.Get('chart.gutter');
949 var text_size = this.Get('chart.text.size');
950 var context = this.context;
951 var canvas = this.canvas;
953 // Draw the Y axis labels
954 if (this.Get('chart.ylabels') && this.Get('chart.ylabels.specific') == null) {
956 var units_pre = this.Get('chart.units.pre');
957 var units_post = this.Get('chart.units.post');
958 var xpos = this.Get('chart.yaxispos') == 'left' ? gutter - 5 : this.canvas.width - gutter + 5;
959 var align = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
961 var numYLabels = this.Get('chart.ylabels.count');
962 var bounding = false;
963 var bgcolor = this.Get('chart.ylabels.inside') ? this.Get('chart.ylabels.inside.color') : null;
967 * If the Y labels are inside the Y axis, invert the alignment
969 if (this.Get('chart.ylabels.inside') == true && align == 'left') {
975 } else if (this.Get('chart.ylabels.inside') == true && align == 'right') {
983 if (this.Get('chart.xaxispos') == 'center') {
984 var half = this.grapharea / 2;
986 if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
987 // Draw the upper halves labels
988 RGraph.Text(context, font, text_size, xpos, gutter + ( (0/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
990 if (numYLabels == 5) {
991 RGraph.Text(context, font, text_size, xpos, gutter + ( (1/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
992 RGraph.Text(context, font, text_size, xpos, gutter + ( (3/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
995 if (numYLabels >= 3) {
996 RGraph.Text(context, font, text_size, xpos, gutter + ( (2/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
997 RGraph.Text(context, font, text_size, xpos, gutter + ( (4/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1000 // Draw the lower halves labels
1001 if (numYLabels >= 3) {
1002 RGraph.Text(context, font, text_size, xpos, gutter + ( (6/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1003 RGraph.Text(context, font, text_size, xpos, gutter + ( (8/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1006 if (numYLabels == 5) {
1007 RGraph.Text(context, font, text_size, xpos, gutter + ( (7/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1008 RGraph.Text(context, font, text_size, xpos, gutter + ( (9/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1011 RGraph.Text(context, font, text_size, xpos, gutter + ( (10/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, (this.scale[4] == '1.0' ? '1.0' : this.scale[4]), units_pre, units_post), null, align, bounding, null, bgcolor);
1013 } else if (numYLabels == 10) {
1016 var interval = (this.grapharea / numYLabels) / 2;
1018 for (var i=0; i<numYLabels; ++i) {
1019 // This draws the upper halves labels
1020 RGraph.Text(context,font, text_size, xpos, gutter + this.halfTextHeight + ((i/20) * (this.grapharea) ), RGraph.number_format(this, ((this.scale[4] / numYLabels) * (numYLabels - i)).toFixed((this.Get('chart.scale.decimals'))),units_pre, units_post), null, align, bounding, null, bgcolor);
1022 // And this draws the lower halves labels
1023 RGraph.Text(context, font, text_size, xpos,
1025 gutter + this.halfTextHeight + ((i/20) * this.grapharea) + (this.grapharea / 2) + (this.grapharea / 20),
1027 '-' + RGraph.number_format(this, (this.scale[4] - ((this.scale[4] / numYLabels) * (numYLabels - i - 1))).toFixed((this.Get('chart.scale.decimals'))),units_pre, units_post), null, align, bounding, null, bgcolor);
1031 alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
1034 // Draw the lower limit if chart.ymin is specified
1035 if (typeof(this.Get('chart.ymin')) == 'number') {
1036 RGraph.Text(context, font, text_size, xpos, this.canvas.height / 2, RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post), 'center', align, bounding, null, bgcolor);
1039 // No X axis - so draw 0
1040 if (this.Get('chart.noxaxis') == true) {
1041 RGraph.Text(context,font,text_size,xpos,gutter + ( (5/5) * half ) + this.halfTextHeight,'0',null, align, bounding, null, bgcolor);
1047 * Accommodate reversing the Y labels
1049 if (this.Get('chart.ylabels.invert')) {
1050 this.scale = RGraph.array_reverse(this.scale);
1051 this.context.translate(0, this.grapharea * 0.2);
1052 if (typeof(this.Get('chart.ymin')) == null) {
1053 this.Set('chart.ymin', 0);
1057 if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
1058 RGraph.Text(context, font, text_size, xpos, gutter + this.halfTextHeight + ((0/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
1060 if (numYLabels == 5) {
1061 RGraph.Text(context, font, text_size, xpos, gutter + this.halfTextHeight + ((3/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1062 RGraph.Text(context, font, text_size, xpos, gutter + this.halfTextHeight + ((1/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1065 if (numYLabels >= 3) {
1066 RGraph.Text(context, font, text_size, xpos, gutter + this.halfTextHeight + ((2/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1067 RGraph.Text(context, font, text_size, xpos, gutter + this.halfTextHeight + ((4/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1070 } else if (numYLabels == 10) {
1073 var interval = (this.grapharea / numYLabels) / 2;
1075 for (var i=0; i<numYLabels; ++i) {
1076 RGraph.Text(context,font,text_size,xpos,gutter + this.halfTextHeight + ((i/10) * (this.grapharea) ),RGraph.number_format(this,((this.scale[4] / numYLabels) * (numYLabels - i)).toFixed((this.Get('chart.scale.decimals'))),units_pre,units_post),null,align,bounding,null,bgcolor);
1080 alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
1085 * Accommodate translating back after reversing the labels
1087 if (this.Get('chart.ylabels.invert')) {
1088 this.context.translate(0, 0 - (this.grapharea * 0.2));
1091 // Draw the lower limit if chart.ymin is specified
1092 if (typeof(this.Get('chart.ymin')) == 'number') {
1093 RGraph.Text(context,font,text_size,xpos,this.Get('chart.ylabels.invert') ? gutter : this.canvas.height - gutter,RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center',align,bounding,null,bgcolor);
1097 // No X axis - so draw 0
1098 if ( this.Get('chart.noxaxis') == true
1099 && this.Get('chart.ymin') == null
1102 RGraph.Text(context,font,text_size,xpos,this.canvas.height - gutter + this.halfTextHeight,'0',null, align, bounding, null, bgcolor);
1105 } else if (this.Get('chart.ylabels') && typeof(this.Get('chart.ylabels.specific')) == 'object') {
1108 var gap = this.grapharea / this.Get('chart.ylabels.specific').length;
1109 var halign = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
1110 var bounding = false;
1113 // Figure out the X coord based on the position of the axis
1114 if (this.Get('chart.yaxispos') == 'left') {
1117 if (this.Get('chart.ylabels.inside')) {
1121 bgcolor = 'rgba(255,255,255,0.5)';
1124 } else if (this.Get('chart.yaxispos') == 'right') {
1125 var x = this.canvas.width - gutter + 5;
1127 if (this.Get('chart.ylabels.inside')) {
1131 bgcolor = 'rgba(255,255,255,0.5)';
1137 if (this.Get('chart.xaxispos') == 'center') {
1139 // Draw the top halfs labels
1140 for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
1141 var y = gutter + ((this.grapharea / (this.Get('chart.ylabels.specific').length * 2) ) * i);
1142 RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
1145 // Now reverse the labels and draw the bottom half
1146 var reversed_labels = RGraph.array_reverse(this.Get('chart.ylabels.specific'));
1148 // Draw the bottom halfs labels
1149 for (var i=0; i<reversed_labels.length; ++i) {
1150 var y = (this.grapharea / 2) + gutter + ((this.grapharea / (reversed_labels.length * 2) ) * (i + 1));
1151 RGraph.Text(context, font, text_size,x,y,String(reversed_labels[i]), 'center', halign, bounding, 0, bgcolor);
1155 for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
1156 var y = gutter + ((this.grapharea / this.Get('chart.ylabels.specific').length) * i);
1157 RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
1162 // Draw the X axis labels
1163 if (this.Get('chart.labels') && this.Get('chart.labels').length > 0) {
1166 var bordered = false;
1169 if (this.Get('chart.xlabels.inside')) {
1172 bgcolor = this.Get('chart.xlabels.inside.color');
1180 var halign = 'center';
1182 if (typeof(this.Get('chart.text.angle')) == 'number' && this.Get('chart.text.angle') > 0) {
1183 angle = -1 * this.Get('chart.text.angle');
1189 this.context.fillStyle = this.Get('chart.text.color');
1190 var numLabels = this.Get('chart.labels').length;
1192 for (i=0; i<numLabels; ++i) {
1194 // Changed 8th Nov 2010 to be not reliant on the coords
1195 //if (this.Get('chart.labels')[i] && this.coords && this.coords[i] && this.coords[i][0]) {
1196 if (this.Get('chart.labels')[i]) {
1198 var labelX = ((this.canvas.width - (2 * this.Get('chart.gutter')) - (2 * this.Get('chart.hmargin'))) / (numLabels - 1) ) * i;
1199 labelX += this.Get('chart.gutter') + this.Get('chart.hmargin');
1202 * Account for an unrelated number of labels
1204 if (this.Get('chart.labels').length != this.data[0].length) {
1205 labelX = this.gutter + this.Get('chart.hmargin') + ((this.canvas.width - (2 * this.gutter) - (2 * this.Get('chart.hmargin'))) * (i / (this.Get('chart.labels').length - 1)));
1208 // This accounts for there only being one point on the chart
1210 labelX = this.gutter + this.Get('chart.hmargin');
1213 RGraph.Text(context, font,text_size,labelX,(this.canvas.height - gutter) + yOffset,String(this.Get('chart.labels')[i]),valign,halign,bordered,angle,bgcolor);
1219 this.context.stroke();
1220 this.context.fill();
1228 RGraph.Line.prototype.DrawLine = function (lineData, color, fill, linewidth, tickmarks)
1233 this.context.lineWidth = 1;
1234 var lineCoords = [];
1235 var gutter = this.Get('chart.gutter');
1237 // Work out the X interval
1238 var xInterval = (this.canvas.width - (2 * this.Get('chart.hmargin')) - ( (2 * this.gutter)) ) / (lineData.length - 1);
1240 // Loop thru each value given, plotting the line
1241 for (i=0; i<lineData.length; i++) {
1243 yPos = this.canvas.height - ( ( (lineData[i] - (lineData[i] > 0 ? this.Get('chart.ymin') : (-1 * this.Get('chart.ymin')) ) ) / (this.max - this.min) ) * ((this.canvas.height - (2 * this.gutter)) ));
1245 if (this.Get('chart.ylabels.invert')) {
1248 yPos = this.canvas.height - yPos;
1251 // Make adjustments depending on the X axis position
1252 if (this.Get('chart.xaxispos') == 'center') {
1254 } else if (this.Get('chart.xaxispos') == 'bottom') {
1255 yPos -= this.gutter; // Without this the line is out of place due to the gutter
1259 if (lineData[i] == null) {
1263 // Not always very noticeable, but it does have an effect
1265 this.context.lineCap = 'round';
1266 this.context.lineJoin = 'round';
1268 // Plot the line if we're at least on the second iteration
1270 xPos = xPos + xInterval;
1272 xPos = this.Get('chart.hmargin') + gutter; // Might need to be this.gutter - 27th August 2010
1276 * Add the coords to an array
1278 this.coords.push([xPos, yPos]);
1279 lineCoords.push([xPos, yPos]);
1282 this.context.stroke();
1285 * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
1287 if (RGraph.isIE8() && this.Get('chart.shadow')) {
1288 this.DrawIEShadow(lineCoords, this.context.shadowColor);
1292 * Now draw the actual line [FORMERLY SECOND]
1294 this.context.beginPath();
1295 this.context.strokeStyle = 'rgba(240,240,240,0.9)'; // Almost transparent - changed on 10th May 2010
1296 //this.context.strokeStyle = fill;
1297 if (fill) this.context.fillStyle = fill;
1299 var isStepped = this.Get('chart.stepped');
1300 var isFilled = this.Get('chart.filled');
1303 for (var i=0; i<lineCoords.length; ++i) {
1305 xPos = lineCoords[i][0];
1306 yPos = lineCoords[i][1];
1308 var prevY = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
1309 var isLast = (i + 1) == lineCoords.length;
1312 * This nullifys values which are out-of-range
1314 if (prevY < this.Get('chart.gutter') || prevY > (this.canvas.height - this.Get('chart.gutter')) ) {
1318 if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutter) {
1319 if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
1320 this.context.moveTo(xPos + 1, this.canvas.height - this.gutter - (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - (2 * this.gutter)) / 2 : 0) -1);
1321 this.context.lineTo(xPos + 1, yPos);
1324 this.context.moveTo(xPos, yPos);
1331 // Draw the stepped part of stepped lines
1333 this.context.lineTo(xPos, lineCoords[i - 1][1]);
1336 if ((yPos >= this.gutter && yPos <= (this.canvas.height - this.gutter)) || this.Get('chart.outofbounds')) {
1338 if (isLast && this.Get('chart.filled') && !this.Get('chart.filled.range') && this.Get('chart.yaxispos') == 'right') {
1343 // Added 8th September 2009
1344 if (!isStepped || !isLast) {
1345 this.context.lineTo(xPos, yPos);
1347 if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) {
1348 this.context.lineTo(xPos, this.canvas.height - this.gutter);
1351 // Added August 2010
1352 } else if (isStepped && isLast) {
1353 this.context.lineTo(xPos,yPos);
1364 if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
1365 var fillStyle = this.Get('chart.fillstyle');
1367 this.context.lineTo(xPos, this.canvas.height - this.gutter - 1 - + (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - (2 * this.gutter)) / 2 : 0));
1368 this.context.fillStyle = fill;
1370 this.context.fill();
1371 this.context.beginPath();
1375 * FIXME this may need removing when Chrome is fixed
1376 * SEARCH TAGS: CHROME SHADOW BUG
1378 if (navigator.userAgent.match(/Chrome/) && this.Get('chart.shadow') && this.Get('chart.chromefix') && this.Get('chart.shadow.blur') > 0) {
1380 for (var i=lineCoords.length - 1; i>=0; --i) {
1382 typeof(lineCoords[i][1]) != 'number'
1383 || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
1385 this.context.moveTo(lineCoords[i][0],lineCoords[i][1]);
1387 this.context.lineTo(lineCoords[i][0],lineCoords[i][1]);
1392 this.context.stroke();
1395 if (this.Get('chart.backdrop')) {
1396 this.DrawBackdrop(lineCoords, color);
1399 // Now redraw the lines with the correct line width
1400 this.RedrawLine(lineCoords, color, linewidth);
1402 this.context.stroke();
1404 // Draw the tickmarks
1405 for (var i=0; i<lineCoords.length; ++i) {
1409 if (isStepped && i == (lineCoords.length - 1)) {
1410 this.context.beginPath();
1416 tickmarks != 'endcircle'
1417 && tickmarks != 'endsquare'
1418 && tickmarks != 'filledendsquare'
1419 && tickmarks != 'endtick'
1420 && tickmarks != 'arrow'
1421 && tickmarks != 'filledarrow'
1423 || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
1424 || i == (lineCoords.length - 1)
1427 var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
1428 var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
1430 this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);
1432 // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
1434 //if (this.Get('chart.stepped') && lineCoords[i + 1] && this.Get('chart.tickmarks') != 'endsquare' && this.Get('chart.tickmarks') != 'endcircle' && this.Get('chart.tickmarks') != 'endtick') {
1435 // this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
1440 // Draw something off canvas to skirt an annoying bug
1441 this.context.beginPath();
1442 this.context.arc(this.canvas.width + 50, this.canvas.height + 50, 2, 0, 6.38, 1);
1447 * This functions draws a tick mark on the line
1449 * @param xPos int The x position of the tickmark
1450 * @param yPos int The y position of the tickmark
1451 * @param color str The color of the tickmark
1452 * @param bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
1454 RGraph.Line.prototype.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index)
1456 var gutter = this.Get('chart.gutter');
1458 // If the yPos is null - no tick
1459 if ((yPos == null || yPos > (this.canvas.height - gutter) || yPos < gutter) && !this.Get('chart.outofbounds')) {
1463 this.context.beginPath();
1467 // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
1468 this.context.lineWidth = this.Get('chart.linewidth');
1469 this.context.strokeStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1470 this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1472 // Cicular tick marks
1473 if ( tickmarks == 'circle'
1474 || tickmarks == 'filledcircle'
1475 || tickmarks == 'endcircle') {
1477 if (tickmarks == 'circle'|| tickmarks == 'filledcircle' || (tickmarks == 'endcircle') ) {
1478 this.context.beginPath();
1479 this.context.arc(xPos + offset, yPos + offset, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
1481 if (tickmarks == 'filledcircle') {
1482 this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1484 this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
1487 this.context.fill();
1488 this.context.stroke();
1491 // Halfheight "Line" style tick marks
1492 } else if (tickmarks == 'halftick') {
1493 this.context.beginPath();
1494 this.context.moveTo(xPos, yPos);
1495 this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1497 this.context.stroke();
1499 // Tick style tickmarks
1500 } else if (tickmarks == 'tick') {
1501 this.context.beginPath();
1502 this.context.moveTo(xPos, yPos - this.Get('chart.ticksize'));
1503 this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1505 this.context.stroke();
1507 // Endtick style tickmarks
1508 } else if (tickmarks == 'endtick') {
1509 this.context.beginPath();
1510 this.context.moveTo(xPos, yPos - this.Get('chart.ticksize'));
1511 this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1513 this.context.stroke();
1515 // "Cross" style tick marks
1516 } else if (tickmarks == 'cross') {
1517 this.context.beginPath();
1518 this.context.moveTo(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
1519 this.context.lineTo(xPos + this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1520 this.context.moveTo(xPos + this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
1521 this.context.lineTo(xPos - this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1523 this.context.stroke();
1525 // A white bordered circle
1526 } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
1527 this.context.lineWidth = 1;
1528 this.context.strokeStyle = this.Get('chart.tickmarks.dot.color');
1529 this.context.fillStyle = this.Get('chart.tickmarks.dot.color');
1531 // The outer white circle
1532 this.context.beginPath();
1533 this.context.arc(xPos, yPos, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
1534 this.context.closePath();
1537 this.context.fill();
1538 this.context.stroke();
1540 // Now do the inners
1541 this.context.beginPath();
1542 this.context.fillStyle = color;
1543 this.context.strokeStyle = color;
1544 this.context.arc(xPos, yPos, this.Get('chart.ticksize') - 2, 0, 360 / (180 / Math.PI), false);
1546 this.context.closePath();
1548 this.context.fill();
1549 this.context.stroke();
1551 } else if ( tickmarks == 'square'
1552 || tickmarks == 'filledsquare'
1553 || (tickmarks == 'endsquare')
1554 || (tickmarks == 'filledendsquare') ) {
1556 this.context.fillStyle = 'white';
1557 this.context.strokeStyle = this.context.strokeStyle; // FIXME Is this correct?
1559 this.context.beginPath();
1560 this.context.strokeRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
1563 if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
1564 this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1565 this.context.fillRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
1567 } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
1568 this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
1569 this.context.fillRect((xPos - this.Get('chart.ticksize')) + 1, (yPos - this.Get('chart.ticksize')) + 1, (this.Get('chart.ticksize') * 2) - 2, (this.Get('chart.ticksize') * 2) - 2);
1572 this.context.stroke();
1573 this.context.fill();
1578 } else if (tickmarks == 'filledarrow') {
1580 var x = Math.abs(xPos - prevX);
1581 var y = Math.abs(yPos - prevY);
1584 var a = Math.atan(x / y) + 1.57;
1586 var a = Math.atan(y / x) + 3.14;
1589 this.context.beginPath();
1590 this.context.moveTo(xPos, yPos);
1591 this.context.arc(xPos, yPos, 7, a - 0.5, a + 0.5, false);
1592 this.context.closePath();
1594 this.context.stroke();
1595 this.context.fill();
1598 * Arrow head, NOT filled
1600 } else if (tickmarks == 'arrow') {
1602 var x = Math.abs(xPos - prevX);
1603 var y = Math.abs(yPos - prevY);
1606 var a = Math.atan(x / y) + 1.57;
1608 var a = Math.atan(y / x) + 3.14;
1611 this.context.beginPath();
1612 this.context.moveTo(xPos, yPos);
1613 this.context.arc(xPos, yPos, 7, a - 0.5 - (document.all ? 0.1 : 0.01), a - 0.4, false);
1615 this.context.moveTo(xPos, yPos);
1616 this.context.arc(xPos, yPos, 7, a + 0.5 + (document.all ? 0.1 : 0.01), a + 0.5, true);
1619 this.context.stroke();
1622 * Custom tick drawing function
1624 } else if (typeof(tickmarks) == 'function') {
1625 tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
1631 * Draws a filled range if necessary
1633 RGraph.Line.prototype.DrawRange = function ()
1636 * Fill the range if necessary
1638 if (this.Get('chart.filled.range') && this.Get('chart.filled')) {
1639 this.context.beginPath();
1640 this.context.fillStyle = this.Get('chart.fillstyle');
1641 this.context.strokeStyle = this.Get('chart.fillstyle');
1642 this.context.lineWidth = 1;
1643 var len = (this.coords.length / 2);
1645 for (var i=0; i<len; ++i) {
1647 this.context.moveTo(this.coords[i][0], this.coords[i][1])
1649 this.context.lineTo(this.coords[i][0], this.coords[i][1])
1653 for (var i=this.coords.length - 1; i>=len; --i) {
1654 this.context.lineTo(this.coords[i][0], this.coords[i][1])
1656 this.context.stroke();
1657 this.context.fill();
1663 * Redraws the line with the correct line width etc
1665 * @param array coords The coordinates of the line
1667 RGraph.Line.prototype.RedrawLine = function (coords, color, linewidth)
1669 if (this.Get('chart.noredraw')) {
1673 this.context.beginPath();
1674 this.context.strokeStyle = (typeof(color) == 'object' && color ? color[0] : color);
1675 this.context.lineWidth = linewidth;
1677 var len = coords.length;
1678 var gutter = this.gutter;
1679 var width = this.canvas.width;
1680 var height = this.canvas.height;
1683 for (var i=0; i<len; ++i) {
1685 var xPos = coords[i][0];
1686 var yPos = coords[i][1];
1689 var prevX = coords[i - 1][0];
1690 var prevY = coords[i - 1][1];
1695 (i == 0 && coords[i])
1698 || (yPos > (height - gutter))
1699 || (i > 0 && prevX > (width - gutter))
1700 || (i > 0 && prevY > (height - gutter))
1703 ) && !this.Get('chart.outofbounds')) {
1705 this.context.moveTo(coords[i][0], coords[i][1]);
1711 if (this.Get('chart.stepped') && i > 0) {
1712 this.context.lineTo(coords[i][0], coords[i - 1][1]);
1715 // Don't draw the last bit of a stepped chart. Now DO
1716 //if (!this.Get('chart.stepped') || i < (coords.length - 1)) {
1717 this.context.lineTo(coords[i][0], coords[i][1]);
1724 * If two colors are specified instead of one, go over the up bits
1726 if (this.Get('chart.colors.alternate') && typeof(color) == 'object' && color[0] && color[1]) {
1727 for (var i=1; i<len; ++i) {
1729 var prevX = coords[i - 1][0];
1730 var prevY = coords[i - 1][1];
1732 this.context.beginPath();
1733 this.context.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
1734 this.context.lineWidth = this.Get('chart.linewidth');
1735 this.context.moveTo(prevX, prevY);
1736 this.context.lineTo(coords[i][0], coords[i][1]);
1737 this.context.stroke();
1744 * This function is used by MSIE only to manually draw the shadow
1746 * @param array coords The coords for the line
1748 RGraph.Line.prototype.DrawIEShadow = function (coords, color)
1750 var offsetx = this.Get('chart.shadow.offsetx');
1751 var offsety = this.Get('chart.shadow.offsety');
1753 this.context.lineWidth = this.Get('chart.linewidth');
1754 this.context.strokeStyle = color;
1755 this.context.beginPath();
1757 for (var i=0; i<coords.length; ++i) {
1759 this.context.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
1761 this.context.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
1765 this.context.stroke();
1772 RGraph.Line.prototype.DrawBackdrop = function (coords, color)
1774 var size = this.Get('chart.backdrop.size');
1775 this.context.lineWidth = size;
1776 this.context.globalAlpha = this.Get('chart.backdrop.alpha');
1777 this.context.strokeStyle = color;
1778 this.context.lineJoin = 'miter';
1780 this.context.beginPath();
1781 this.context.moveTo(coords[0][0], coords[0][1]);
1782 for (var j=1; j<coords.length; ++j) {
1783 this.context.lineTo(coords[j][0], coords[j][1]);
1786 this.context.stroke();
1788 // Reset the alpha value
1789 this.context.globalAlpha = 1;
1790 this.context.lineJoin = 'round';
1791 RGraph.NoShadow(this);
1796 * Returns the linewidth
1798 RGraph.Line.prototype.GetLineWidth = function (i)
1800 var linewidth = this.Get('chart.linewidth');
1802 if (typeof(linewidth) == 'number') {
1805 } else if (typeof(linewidth) == 'object') {
1807 return linewidth[i];
1809 return linewidth[0];
1812 alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
1818 * The getPoint() method - used to get the point the mouse is currently over, if any
1820 * @param object e The event object
1822 RGraph.Line.prototype.getPoint = function (e)
1824 var canvas = e.target;
1825 var context = canvas.getContext('2d');
1826 var obj = e.target.__object__;
1827 var mouseXY = RGraph.getMouseXY(e);
1828 var mouseX = mouseXY[0];
1829 var mouseY = mouseXY[1];
1831 for (var i=0; i<obj.coords.length; ++i) {
1833 var xCoord = obj.coords[i][0];
1834 var yCoord = obj.coords[i][1];
1836 if ( mouseX <= (xCoord + 5 + obj.Get('chart.tooltips.coords.adjust')[0])
1837 && mouseX >= (xCoord - 5 + obj.Get('chart.tooltips.coords.adjust')[0])
1838 && mouseY <= (yCoord + 5 + obj.Get('chart.tooltips.coords.adjust')[1])
1839 && mouseY >= (yCoord - 5 + obj.Get('chart.tooltips.coords.adjust')[1])) {
1841 return [obj, xCoord, yCoord, i];
1848 * Draws the above line labels
1850 RGraph.Line.prototype.DrawAboveLabels = function ()
1852 var context = this.context;
1853 var size = this.Get('chart.labels.above.size');
1854 var font = this.Get('chart.text.font');
1855 var units_pre = this.Get('chart.units.pre');
1856 var units_post = this.Get('chart.units.post');
1858 context.beginPath();
1860 // Don't need to check that chart.labels.above is enabled here, it's been done already
1861 for (var i=0; i<this.coords.length; ++i) {
1862 var coords = this.coords[i];
1864 RGraph.Text(context, font, size, coords[0], coords[1] - 5 - size, RGraph.number_format(this, this.data_arr[i], units_pre, units_post), 'center', 'center', true, null, 'rgba(255, 255, 255, 0.7)');