summaryrefslogtreecommitdiff
path: root/schall/static/RGraph/libraries/RGraph.line.js
diff options
context:
space:
mode:
Diffstat (limited to 'schall/static/RGraph/libraries/RGraph.line.js')
-rw-r--r--schall/static/RGraph/libraries/RGraph.line.js2217
1 files changed, 2217 insertions, 0 deletions
diff --git a/schall/static/RGraph/libraries/RGraph.line.js b/schall/static/RGraph/libraries/RGraph.line.js
new file mode 100644
index 0000000..e641f87
--- /dev/null
+++ b/schall/static/RGraph/libraries/RGraph.line.js
@@ -0,0 +1,2217 @@
+ /**
+ * o------------------------------------------------------------------------------o
+ * | This file is part of the RGraph package - you can learn more at: |
+ * | |
+ * | http://www.rgraph.net |
+ * | |
+ * | This package is licensed under the RGraph license. For all kinds of business |
+ * | purposes there is a small one-time licensing fee to pay and for non |
+ * | commercial purposes it is free to use. You can read the full license here: |
+ * | |
+ * | http://www.rgraph.net/LICENSE.txt |
+ * o------------------------------------------------------------------------------o
+ */
+
+ if (typeof(RGraph) == 'undefined') RGraph = {};
+
+ /**
+ * The line chart constructor
+ *
+ * @param object canvas The cxanvas object
+ * @param array data The chart data
+ * @param array ... Other lines to plot
+ */
+ RGraph.Line = function (id)
+ {
+ // Get the canvas and context objects
+ this.id = id;
+ this.canvas = document.getElementById(id);
+ this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null;
+ this.canvas.__object__ = this;
+ this.type = 'line';
+ this.max = 0;
+ this.coords = [];
+ this.coords2 = [];
+ this.coordsKey = [];
+ this.hasnegativevalues = false;
+ this.isRGraph = true;
+
+
+
+ /**
+ * Compatibility with older browsers
+ */
+ RGraph.OldBrowserCompat(this.context);
+
+
+ // Various config type stuff
+ this.properties = {
+ 'chart.background.barcolor1': 'rgba(0,0,0,0)',
+ 'chart.background.barcolor2': 'rgba(0,0,0,0)',
+ 'chart.background.grid': 1,
+ 'chart.background.grid.width': 1,
+ 'chart.background.grid.hsize': 25,
+ 'chart.background.grid.vsize': 25,
+ 'chart.background.grid.color': '#ddd',
+ 'chart.background.grid.vlines': true,
+ 'chart.background.grid.hlines': true,
+ 'chart.background.grid.border': true,
+ 'chart.background.grid.autofit': true,
+ 'chart.background.grid.autofit.align': false,
+ 'chart.background.grid.autofit.numhlines': 5,
+ 'chart.background.grid.autofit.numvlines': 20,
+ 'chart.background.hbars': null,
+ 'chart.background.image': null,
+ 'chart.labels': null,
+ 'chart.labels.ingraph': null,
+ 'chart.labels.above': false,
+ 'chart.labels.above.size': 8,
+ 'chart.xtickgap': 20,
+ 'chart.smallxticks': 3,
+ 'chart.largexticks': 5,
+ 'chart.ytickgap': 20,
+ 'chart.smallyticks': 3,
+ 'chart.largeyticks': 5,
+ 'chart.numyticks': 10,
+ 'chart.linewidth': 1.01,
+ 'chart.colors': ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff'],
+ 'chart.hmargin': 0,
+ 'chart.tickmarks.dot.color': 'white',
+ 'chart.tickmarks': null,
+ 'chart.tickmarks.linewidth': null,
+ 'chart.ticksize': 3,
+ 'chart.gutter.left': 25,
+ 'chart.gutter.right': 25,
+ 'chart.gutter.top': 25,
+ 'chart.gutter.bottom': 25,
+ 'chart.tickdirection': -1,
+ 'chart.yaxispoints': 5,
+ 'chart.fillstyle': null,
+ 'chart.xaxispos': 'bottom',
+ 'chart.yaxispos': 'left',
+ 'chart.xticks': null,
+ 'chart.text.size': 10,
+ 'chart.text.angle': 0,
+ 'chart.text.color': 'black',
+ 'chart.text.font': 'Verdana',
+ 'chart.ymin': null,
+ 'chart.ymax': null,
+ 'chart.title': '',
+ 'chart.title.background': null,
+ 'chart.title.hpos': null,
+ 'chart.title.vpos': 0.5,
+ 'chart.title.bold': true,
+ 'chart.title.font': null,
+ 'chart.title.xaxis': '',
+ 'chart.title.xaxis.bold': true,
+ 'chart.title.xaxis.size': null,
+ 'chart.title.xaxis.font': null,
+ 'chart.title.yaxis': '',
+ 'chart.title.yaxis.bold': true,
+ 'chart.title.yaxis.size': null,
+ 'chart.title.yaxis.font': null,
+ 'chart.title.xaxis.pos': null,
+ 'chart.title.yaxis.pos': null,
+ 'chart.shadow': false,
+ 'chart.shadow.offsetx': 2,
+ 'chart.shadow.offsety': 2,
+ 'chart.shadow.blur': 3,
+ 'chart.shadow.color': 'rgba(0,0,0,0.5)',
+ 'chart.tooltips': null,
+ 'chart.tooltips.hotspot.xonly': false,
+ 'chart.tooltips.effect': 'fade',
+ 'chart.tooltips.css.class': 'RGraph_tooltip',
+ 'chart.tooltips.highlight': true,
+ 'chart.highlight.stroke': '#999',
+ 'chart.highlight.fill': 'white',
+ 'chart.stepped': false,
+ 'chart.key': [],
+ 'chart.key.background': 'white',
+ 'chart.key.position': 'graph',
+ 'chart.key.halign': null,
+ 'chart.key.shadow': false,
+ 'chart.key.shadow.color': '#666',
+ 'chart.key.shadow.blur': 3,
+ 'chart.key.shadow.offsetx': 2,
+ 'chart.key.shadow.offsety': 2,
+ 'chart.key.position.gutter.boxed': true,
+ 'chart.key.position.x': null,
+ 'chart.key.position.y': null,
+ 'chart.key.color.shape': 'square',
+ 'chart.key.rounded': true,
+ 'chart.key.linewidth': 1,
+ 'chart.key.interactive': false,
+ 'chart.contextmenu': null,
+ 'chart.ylabels': true,
+ 'chart.ylabels.count': 5,
+ 'chart.ylabels.inside': false,
+ 'chart.ylabels.invert': false,
+ 'chart.xlabels.inside': false,
+ 'chart.xlabels.inside.color': 'rgba(255,255,255,0.5)',
+ 'chart.noaxes': false,
+ 'chart.noyaxis': false,
+ 'chart.noxaxis': false,
+ 'chart.noendxtick': false,
+ 'chart.units.post': '',
+ 'chart.units.pre': '',
+ 'chart.scale.decimals': null,
+ 'chart.scale.point': '.',
+ 'chart.scale.thousand': ',',
+ 'chart.crosshairs': false,
+ 'chart.crosshairs.color': '#333',
+ 'chart.crosshairs.hline': true,
+ 'chart.crosshairs.vline': true,
+ 'chart.annotatable': false,
+ 'chart.annotate.color': 'black',
+ 'chart.axesontop': false,
+ 'chart.filled': false,
+ 'chart.filled.range': false,
+ 'chart.filled.accumulative': true,
+ 'chart.variant': null,
+ 'chart.axis.color': 'black',
+ 'chart.zoom.factor': 1.5,
+ 'chart.zoom.fade.in': true,
+ 'chart.zoom.fade.out': true,
+ 'chart.zoom.hdir': 'right',
+ 'chart.zoom.vdir': 'down',
+ 'chart.zoom.frames': 25,
+ 'chart.zoom.delay': 16.666,
+ 'chart.zoom.shadow': true,
+ 'chart.zoom.mode': 'canvas',
+ 'chart.zoom.thumbnail.width': 75,
+ 'chart.zoom.thumbnail.height': 75,
+ 'chart.zoom.background': true,
+ 'chart.zoom.action': 'zoom',
+ 'chart.backdrop': false,
+ 'chart.backdrop.size': 30,
+ 'chart.backdrop.alpha': 0.2,
+ 'chart.resizable': false,
+ 'chart.resize.handle.adjust': [0,0],
+ 'chart.resize.handle.background': null,
+ 'chart.adjustable': false,
+ 'chart.noredraw': false,
+ 'chart.outofbounds': false,
+ 'chart.chromefix': true,
+ 'chart.animation.factor': 1,
+ 'chart.animation.unfold.x': false,
+ 'chart.animation.unfold.y': true,
+ 'chart.animation.unfold.initial': 2,
+ 'chart.curvy': false,
+ 'chart.curvy.factor': 0.25,
+ 'chart.line.visible': true
+ }
+
+ /**
+ * Change null arguments to empty arrays
+ */
+ for (var i=1; i<arguments.length; ++i) {
+ if (typeof(arguments[i]) == 'null' || !arguments[i]) {
+ arguments[i] = [];
+ }
+ }
+
+
+ /**
+ * Store the original data. Thiss also allows for giving arguments as one big array.
+ */
+ this.original_data = [];
+
+ for (var i=1; i<arguments.length; ++i) {
+ if (arguments[1] && typeof(arguments[1]) == 'object' && arguments[1][0] && typeof(arguments[1][0]) == 'object' && arguments[1][0].length) {
+
+ var tmp = [];
+
+ for (var i=0; i<arguments[1].length; ++i) {
+ tmp[i] = RGraph.array_clone(arguments[1][i]);
+ }
+
+ for (var j=0; j<tmp.length; ++j) {
+ this.original_data[j] = RGraph.array_clone(tmp[j]);
+ }
+
+ } else {
+ this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
+ }
+ }
+
+ // Check for support
+ if (!this.canvas) {
+ alert('[LINE] Fatal error: no canvas support');
+ return;
+ }
+
+ /**
+ * Store the data here as one big array
+ */
+ this.data_arr = [];
+
+ for (var i=1; i<arguments.length; ++i) {
+ for (var j=0; j<arguments[i].length; ++j) {
+ this.data_arr.push(arguments[i][j]);
+ }
+ }
+
+
+ /**
+ * Set the .getShape commonly named method
+ */
+ this.getShape = this.getPoint;
+ }
+
+
+ /**
+ * An all encompassing accessor
+ *
+ * @param string name The name of the property
+ * @param mixed value The value of the property
+ */
+ RGraph.Line.prototype.Set = function (name, value)
+ {
+ // Consolidate the tooltips
+ if (name == 'chart.tooltips') {
+
+ var tooltips = [];
+
+ for (var i=1; i<arguments.length; i++) {
+ if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
+ for (var j=0; j<arguments[i].length; j++) {
+ tooltips.push(arguments[i][j]);
+ }
+
+ } else if (typeof(arguments[i]) == 'function') {
+ tooltips = arguments[i];
+
+ } else {
+ tooltips.push(arguments[i]);
+ }
+ }
+
+ // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
+ value = tooltips;
+ }
+
+ /**
+ * Reverse the tickmarks to make them correspond to the right line
+ */
+ if (name == 'chart.tickmarks' && typeof(value) == 'object' && value) {
+ value = RGraph.array_reverse(value);
+ }
+
+ /**
+ * Inverted Y axis should show the bottom end of the scale
+ */
+ if (name == 'chart.ylabels.invert' && value && this.Get('chart.ymin') == null) {
+ this.Set('chart.ymin', 0);
+ }
+
+ /**
+ * If (buggy) Chrome and the linewidth is 1, change it to 1.01
+ */
+ if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/)) {
+ if (value == 1) {
+ value = 1.01;
+
+ } else if (RGraph.is_array(value)) {
+ for (var i=0; i<value.length; ++i) {
+ if (typeof(value[i]) == 'number' && value[i] == 1) {
+ value[i] = 1.01;
+ }
+ }
+ }
+ }
+
+ /**
+ * Check for xaxispos
+ */
+ if (name == 'chart.xaxispos' ) {
+ if (value != 'bottom' && value != 'center' && value != 'top') {
+ alert('[LINE] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
+ value = 'center';
+ }
+ }
+
+ /**
+ * Check chart.curvy.factor - should be 0 - 0.5
+ */
+ if (name == 'chart.curvy.factor' && (value < 0 || value > 0.5)) {
+ alert('[LINE] chart.curvy.factor should be between 0 and 0.5 - setting it to 0.25');
+ value = 0.25;
+ }
+
+ this.properties[name] = value;
+ }
+
+
+ /**
+ * An all encompassing accessor
+ *
+ * @param string name The name of the property
+ */
+ RGraph.Line.prototype.Get = function (name)
+ {
+ return this.properties[name];
+ }
+
+
+ /**
+ * The function you call to draw the line chart
+ *
+ * @param bool An optional bool used internally to ditinguish whether the
+ * line chart is being called by the bar chart
+ */
+ RGraph.Line.prototype.Draw = function ()
+ {
+ // MUST be the FIRST thing done - Hand over drawing to the bar chart if it's a combo back to the Bar chart
+ if (this.__bar__ && !arguments[0]) {
+ this.__bar__.Draw();
+ return;
+ }
+
+ // MUST be the SECOND thing done!
+ if (typeof(this.Get('chart.background.image')) == 'string' && !this.__background_image__) {
+ RGraph.DrawBackgroundImage(this);
+ return;
+ }
+
+ if (this.__bar__ && this.Get('chart.tooltips.highlight')) {
+ //this.Set('chart.tooltips.highlight', false);
+ }
+
+ /**
+ * Fire the onbeforedraw event
+ */
+ RGraph.FireCustomEvent(this, 'onbeforedraw');
+
+ /**
+ * Clear all of this canvases event handlers (the ones installed by RGraph)
+ */
+ RGraph.ClearEventListeners(this.id);
+
+ /**
+ * This is new in May 2011 and facilitates indiviual gutter settings,
+ * eg chart.gutter.left
+ */
+ this.gutterLeft = this.Get('chart.gutter.left');
+ this.gutterRight = this.Get('chart.gutter.right');
+ this.gutterTop = this.Get('chart.gutter.top');
+ this.gutterBottom = this.Get('chart.gutter.bottom');
+
+
+ /**
+ * Check for Chrome 6 and shadow
+ *
+ * TODO Remove once it's been fixed (for a while)
+ * SEARCH TAGS: CHROME FIX SHADOW BUG
+ */
+ if ( this.Get('chart.shadow')
+ && navigator.userAgent.match(/Chrome/)
+ && this.Get('chart.linewidth') <= 1
+ && this.Get('chart.chromefix')
+ && this.Get('chart.shadow.blur') > 0) {
+ alert('[RGRAPH WARNING] Chrome has a shadow bug, meaning you should increase the linewidth to at least 1.01');
+ }
+
+
+ // Reset the data back to that which was initially supplied
+ this.data = RGraph.array_clone(this.original_data);
+
+
+ // Reset the max value
+ this.max = 0;
+
+ /**
+ * Reverse the datasets so that the data and the labels tally
+ * COMMENTED OUT 15TH AUGUST 2011
+ */
+ //this.data = RGraph.array_reverse(this.data);
+
+ if (this.Get('chart.filled') && !this.Get('chart.filled.range') && this.data.length > 1 && this.Get('chart.filled.accumulative')) {
+
+ var accumulation = [];
+
+ for (var set=0; set<this.data.length; ++set) {
+ for (var point=0; point<this.data[set].length; ++point) {
+ this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
+ accumulation[point] = this.data[set][point];
+ }
+ }
+ }
+
+ /**
+ * Get the maximum Y scale value
+ */
+ if (this.Get('chart.ymax')) {
+
+ this.max = this.Get('chart.ymax');
+ this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
+
+ this.scale = [
+ ( ((this.max - this.min) * (1/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
+ ( ((this.max - this.min) * (2/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
+ ( ((this.max - this.min) * (3/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
+ ( ((this.max - this.min) * (4/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
+ this.max.toFixed(this.Get('chart.scale.decimals'))
+ ];
+
+ // Check for negative values
+ if (!this.Get('chart.outofbounds')) {
+ for (dataset=0; dataset<this.data.length; ++dataset) {
+ for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
+
+ // Check for negative values
+ this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
+ }
+ }
+ }
+
+ } else {
+
+ this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
+
+ // Work out the max Y value
+ for (dataset=0; dataset<this.data.length; ++dataset) {
+ for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
+
+ this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);
+
+ // Check for negative values
+ if (!this.Get('chart.outofbounds')) {
+ this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
+ }
+ }
+ }
+
+ // 20th April 2009 - moved out of the above loop
+ this.scale = RGraph.getScale(Math.abs(parseFloat(this.max)), this);
+ this.max = this.scale[4] ? this.scale[4] : 0;
+
+ if (this.Get('chart.ymin')) {
+ this.scale[0] = ((this.max - this.Get('chart.ymin')) * (1/5)) + this.Get('chart.ymin');
+ this.scale[1] = ((this.max - this.Get('chart.ymin')) * (2/5)) + this.Get('chart.ymin');
+ this.scale[2] = ((this.max - this.Get('chart.ymin')) * (3/5)) + this.Get('chart.ymin');
+ this.scale[3] = ((this.max - this.Get('chart.ymin')) * (4/5)) + this.Get('chart.ymin');
+ this.scale[4] = ((this.max - this.Get('chart.ymin')) * (5/5)) + this.Get('chart.ymin');
+ }
+
+ if (typeof(this.Get('chart.scale.decimals')) == 'number') {
+ this.scale[0] = Number(this.scale[0]).toFixed(this.Get('chart.scale.decimals'));
+ this.scale[1] = Number(this.scale[1]).toFixed(this.Get('chart.scale.decimals'));
+ this.scale[2] = Number(this.scale[2]).toFixed(this.Get('chart.scale.decimals'));
+ this.scale[3] = Number(this.scale[3]).toFixed(this.Get('chart.scale.decimals'));
+ this.scale[4] = Number(this.scale[4]).toFixed(this.Get('chart.scale.decimals'));
+ }
+ }
+
+ /**
+ * Setup the context menu if required
+ */
+ if (this.Get('chart.contextmenu')) {
+ RGraph.ShowContext(this);
+ }
+
+ /**
+ * Reset the coords array otherwise it will keep growing
+ */
+ this.coords = [];
+
+ /**
+ * Work out a few things. They need to be here because they depend on things you can change before you
+ * call Draw() but after you instantiate the object
+ */
+ this.grapharea = this.canvas.height - this.gutterTop - this.gutterBottom;
+ this.halfgrapharea = this.grapharea / 2;
+ this.halfTextHeight = this.Get('chart.text.size') / 2;
+
+ // Check the combination of the X axis position and if there any negative values
+ //
+ // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
+ // are multiple graphs on the page
+ if (this.Get('chart.xaxispos') == 'bottom' && this.hasnegativevalues && navigator.userAgent.indexOf('Opera') == -1) {
+ alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
+ }
+
+ if (this.Get('chart.variant') == '3d') {
+ RGraph.Draw3DAxes(this);
+ }
+
+ // Progressively Draw the chart
+ RGraph.background.Draw(this);
+
+ /**
+ * Draw any horizontal bars that have been defined
+ */
+ if (this.Get('chart.background.hbars') && this.Get('chart.background.hbars').length > 0) {
+ RGraph.DrawBars(this);
+ }
+
+ if (this.Get('chart.axesontop') == false) {
+ this.DrawAxes();
+ }
+
+ /**
+ * Handle the appropriate shadow color. This now facilitates an array of differing
+ * shadow colors
+ */
+ var shadowColor = this.Get('chart.shadow.color');
+
+ //if (typeof(shadowColor) == 'object') {
+ // shadowColor = RGraph.array_reverse(RGraph.array_clone(this.Get('chart.shadow.color')));
+ //}
+
+
+ for (var i=0, j=0; i<this.data.length; i++, j++) {
+
+ this.context.beginPath();
+
+ /**
+ * Turn on the shadow if required
+ */
+ if (this.Get('chart.shadow') && !this.Get('chart.filled')) {
+
+ /**
+ * Accommodate an array of shadow colors as well as a single string
+ */
+ if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
+ this.context.shadowColor = shadowColor[i];
+ } else if (typeof(shadowColor) == 'object') {
+ this.context.shadowColor = shadowColor[0];
+ } else if (typeof(shadowColor) == 'string') {
+ this.context.shadowColor = shadowColor;
+ }
+
+ this.context.shadowBlur = this.Get('chart.shadow.blur');
+ this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
+ this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
+
+ } else if (this.Get('chart.filled') && this.Get('chart.shadow')) {
+ alert('[LINE] Shadows are not permitted when the line is filled');
+ }
+
+ /**
+ * Draw the line
+ */
+
+ if (this.Get('chart.fillstyle')) {
+ if (typeof(this.Get('chart.fillstyle')) == 'object' && this.Get('chart.fillstyle')[j]) {
+ var fill = this.Get('chart.fillstyle')[j];
+
+ } else if (typeof(this.Get('chart.fillstyle')) == 'string') {
+ var fill = this.Get('chart.fillstyle');
+
+ } else {
+ 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');
+ }
+ } else if (this.Get('chart.filled')) {
+ var fill = this.Get('chart.colors')[j];
+
+ } else {
+ var fill = null;
+ }
+
+ /**
+ * Figure out the tickmark to use
+ */
+ if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'object') {
+ var tickmarks = this.Get('chart.tickmarks')[i];
+ } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'string') {
+ var tickmarks = this.Get('chart.tickmarks');
+ } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'function') {
+ var tickmarks = this.Get('chart.tickmarks');
+ } else {
+ var tickmarks = null;
+ }
+
+
+ this.DrawLine(this.data[i],
+ this.Get('chart.colors')[j],
+ fill,
+ this.GetLineWidth(j),
+ tickmarks,
+ i);
+
+ this.context.stroke();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /**
+ * If tooltips are defined, handle them
+ */
+ if (this.Get('chart.tooltips') && (this.Get('chart.tooltips').length || typeof(this.Get('chart.tooltips')) == 'function')) {
+
+ // Need to register this object for redrawing
+ if (this.Get('chart.tooltips.highlight')) {
+ RGraph.Register(this);
+ }
+
+ RGraph.InstallLineTooltipEventListeners(this);
+
+ /**
+ * If there's a bar, install the event listeners
+ */
+ if (this.__bar__) {
+ RGraph.InstallBarTooltipEventListeners(this.__bar__);
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /**
+ * If the axes have been requested to be on top, do that
+ */
+ if (this.Get('chart.axesontop')) {
+ this.DrawAxes();
+ }
+
+ /**
+ * Draw the labels
+ */
+ this.DrawLabels();
+
+ /**
+ * Draw the range if necessary
+ */
+ this.DrawRange();
+
+ // Draw a key if necessary
+ if (this.Get('chart.key').length) {
+ RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
+ }
+
+ /**
+ * Draw " above" labels if enabled
+ */
+ if (this.Get('chart.labels.above')) {
+ this.DrawAboveLabels();
+ }
+
+ /**
+ * Draw the "in graph" labels
+ */
+ RGraph.DrawInGraphLabels(this);
+
+ /**
+ * Draw crosschairs
+ */
+ RGraph.DrawCrosshairs(this);
+
+ /**
+ * If the canvas is annotatable, do install the event handlers
+ */
+ if (this.Get('chart.annotatable')) {
+ RGraph.Annotate(this);
+ }
+
+ /**
+ * Redraw the lines if a filled range is on the cards
+ */
+ if (this.Get('chart.filled') && this.Get('chart.filled.range') && this.data.length == 2) {
+
+ this.context.beginPath();
+ var len = this.coords.length / 2;
+ this.context.lineWidth = this.Get('chart.linewidth');
+ this.context.strokeStyle = this.Get('chart.colors')[0];
+
+ for (var i=0; i<len; ++i) {
+ if (i == 0) {
+ this.context.moveTo(this.coords[i][0], this.coords[i][1]);
+ } else {
+ this.context.lineTo(this.coords[i][0], this.coords[i][1]);
+ }
+ }
+
+ this.context.stroke();
+
+
+ this.context.beginPath();
+
+ if (this.Get('chart.colors')[1]) {
+ this.context.strokeStyle = this.Get('chart.colors')[1];
+ }
+
+ for (var i=this.coords.length - 1; i>=len; --i) {
+ if (i == (this.coords.length - 1) ) {
+ this.context.moveTo(this.coords[i][0], this.coords[i][1]);
+ } else {
+ this.context.lineTo(this.coords[i][0], this.coords[i][1]);
+ }
+ }
+
+ this.context.stroke();
+ } else if (this.Get('chart.filled') && this.Get('chart.filled.range')) {
+ alert('[LINE] You must have only two sets of data for a filled range chart');
+ }
+
+ /**
+ * This bit shows the mini zoom window if requested
+ */
+ if (this.Get('chart.zoom.mode') == 'thumbnail') {
+ RGraph.ShowZoomWindow(this);
+ }
+
+ /**
+ * This function enables the zoom in area mode
+ */
+ if (this.Get('chart.zoom.mode') == 'area') {
+ RGraph.ZoomArea(this);
+ }
+
+ /**
+ * This function enables resizing
+ */
+ if (this.Get('chart.resizable')) {
+ RGraph.AllowResizing(this);
+ }
+
+ /**
+ * This function enables adjustments
+ */
+ if (this.Get('chart.adjustable')) {
+ RGraph.AllowAdjusting(this);
+ }
+
+ /**
+ * Fire the RGraph ondraw event
+ */
+ RGraph.FireCustomEvent(this, 'ondraw');
+ }
+
+
+ /**
+ * Draws the axes
+ */
+ RGraph.Line.prototype.DrawAxes = function ()
+ {
+ // Don't draw the axes?
+ if (this.Get('chart.noaxes')) {
+ return;
+ }
+
+ // Turn any shadow off
+ RGraph.NoShadow(this);
+
+ this.context.lineWidth = 1;
+ this.context.strokeStyle = this.Get('chart.axis.color');
+ this.context.beginPath();
+
+ // Draw the X axis
+ if (this.Get('chart.noxaxis') == false) {
+ if (this.Get('chart.xaxispos') == 'center') {
+ this.context.moveTo(this.gutterLeft, (this.grapharea / 2) + this.gutterTop);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, (this.grapharea / 2) + this.gutterTop);
+ } else if (this.Get('chart.xaxispos') == 'top') {
+ this.context.moveTo(this.gutterLeft, this.gutterTop);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, this.gutterTop);
+ } else {
+ this.context.moveTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
+ }
+ }
+
+ // Draw the Y axis
+ if (this.Get('chart.noyaxis') == false) {
+ if (this.Get('chart.yaxispos') == 'left') {
+ this.context.moveTo(this.gutterLeft, this.gutterTop);
+ this.context.lineTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom );
+ } else {
+ this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, this.gutterTop);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
+ }
+ }
+
+ /**
+ * Draw the X tickmarks
+ */
+ if (this.Get('chart.noxaxis') == false) {
+
+ if (this.data[0].length > 0) {
+ var xTickInterval = (this.canvas.width - this.gutterLeft - this.gutterRight) / (this.Get('chart.xticks') ? this.Get('chart.xticks') : (this.data[0].length - 1));
+ }
+
+ if (!xTickInterval || xTickInterval <= 0) {
+ xTickInterval = (this.canvas.width - this.gutterLeft - this.gutterRight) / (this.Get('chart.labels') && this.Get('chart.labels').length ? this.Get('chart.labels').length - 1 : 10);
+ }
+
+ for (x=this.gutterLeft + (this.Get('chart.yaxispos') == 'left' ? xTickInterval : 0); x<=(this.canvas.width - this.gutterRight + 1 ); x+=xTickInterval) {
+
+ if (this.Get('chart.yaxispos') == 'right' && x >= (this.canvas.width - this.gutterRight - 1) ) {
+ break;
+ }
+
+ // If the last tick is not desired...
+ if (this.Get('chart.noendxtick')) {
+ if (this.Get('chart.yaxispos') == 'left' && x >= (this.canvas.width - this.gutterRight)) {
+ break;
+ } else if (this.Get('chart.yaxispos') == 'right' && x == this.gutterLeft) {
+ continue;
+ }
+ }
+
+ var yStart = this.Get('chart.xaxispos') == 'center' ? (this.gutterTop + (this.grapharea / 2)) - 3 : RGraph.GetHeight(this) - this.gutterBottom;
+ var yEnd = this.Get('chart.xaxispos') == 'center' ? yStart + 6 : RGraph.GetHeight(this) - this.gutterBottom - (x % 60 == 0 ? this.Get('chart.largexticks') * this.Get('chart.tickdirection') : this.Get('chart.smallxticks') * this.Get('chart.tickdirection'));
+
+ if (this.Get('chart.xaxispos') == 'top') {
+ yStart = this.gutterTop - 3;
+ yEnd = this.gutterTop;
+ }
+
+ this.context.moveTo(x, yStart);
+ this.context.lineTo(x, yEnd);
+ }
+
+ // Draw an extra tickmark if there is no X axis, but there IS a Y axis
+ } else if (this.Get('chart.noyaxis') == false) {
+ if (this.Get('chart.yaxispos') == 'left') {
+ this.context.moveTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom);
+ this.context.lineTo(this.gutterLeft - this.Get('chart.smallyticks'), RGraph.GetHeight(this) - this.gutterBottom);
+ } else {
+ this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'), RGraph.GetHeight(this) - this.gutterBottom);
+ }
+ }
+
+ /**
+ * Draw the Y tickmarks
+ */
+ var numyticks = this.Get('chart.numyticks');
+
+ if (this.Get('chart.noyaxis') == false) {
+ var counter = 0;
+ var adjustment = 0;
+
+ if (this.Get('chart.yaxispos') == 'right') {
+ adjustment = (RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight);
+ }
+
+ // X axis at the center
+ if (this.Get('chart.xaxispos') == 'center') {
+ var interval = (this.grapharea / numyticks);
+ var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft : RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'));
+
+ // Draw the upper halves Y tick marks
+ for (y=this.gutterTop; y < (this.grapharea / 2) + this.gutterTop; y+=interval) {
+ this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
+ this.context.lineTo(lineto, y);
+ }
+
+ // Draw the lower halves Y tick marks
+ for (y=this.gutterTop + (this.halfgrapharea) + interval; y <= this.grapharea + this.gutterTop; y+=interval) {
+ this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
+ this.context.lineTo(lineto, y);
+ }
+
+ // X axis at the top
+ } else if (this.Get('chart.xaxispos') == 'top') {
+ var interval = (this.grapharea / numyticks);
+ var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft : RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'));
+
+ // Draw the Y tick marks
+ for (y=this.gutterTop + interval; y <=this.grapharea + this.gutterTop; y+=interval) {
+ this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
+ this.context.lineTo(lineto, y);
+ }
+
+ // If there's no X axis draw an extra tick
+ if (this.Get('chart.noxaxis')) {
+ this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), this.gutterTop);
+ this.context.lineTo(lineto, this.gutterTop);
+ }
+
+ // X axis at the bottom
+ } else {
+ var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : this.canvas.width - this.gutterRight + this.Get('chart.smallyticks'));
+
+ for (y=this.gutterTop; y < (this.canvas.height - this.gutterBottom) && counter < numyticks; y+=( (this.canvas.height - this.gutterTop - this.gutterBottom) / numyticks) ) {
+
+ this.context.moveTo(this.gutterLeft + adjustment, y);
+ this.context.lineTo(lineto, y);
+
+ var counter = counter +1;
+ }
+ }
+
+ // Draw an extra X tickmark
+ } else if (this.Get('chart.noxaxis') == false) {
+
+ if (this.Get('chart.yaxispos') == 'left') {
+ this.context.moveTo(this.gutterLeft, this.Get('chart.xaxispos') == 'top' ? this.gutterTop : RGraph.GetHeight(this) - this.gutterBottom);
+ this.context.lineTo(this.gutterLeft, this.Get('chart.xaxispos') == 'top' ? this.gutterTop - this.Get('chart.smallxticks') : RGraph.GetHeight(this) - this.gutterBottom + this.Get('chart.smallxticks'));
+ } else {
+ this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
+ this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom + this.Get('chart.smallxticks'));
+ }
+ }
+
+ this.context.stroke();
+ }
+
+
+ /**
+ * Draw the text labels for the axes
+ */
+ RGraph.Line.prototype.DrawLabels = function ()
+ {
+ this.context.strokeStyle = 'black';
+ this.context.fillStyle = this.Get('chart.text.color');
+ this.context.lineWidth = 1;
+
+ // Turn off any shadow
+ RGraph.NoShadow(this);
+
+ // This needs to be here
+ var font = this.Get('chart.text.font');
+ var text_size = this.Get('chart.text.size');
+ var context = this.context;
+ var canvas = this.canvas;
+
+ // Draw the Y axis labels
+ if (this.Get('chart.ylabels') && this.Get('chart.ylabels.specific') == null) {
+
+ var units_pre = this.Get('chart.units.pre');
+ var units_post = this.Get('chart.units.post');
+ var xpos = this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - 5 : RGraph.GetWidth(this) - this.gutterRight + 5;
+ var align = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
+
+ var numYLabels = this.Get('chart.ylabels.count');
+ var bounding = false;
+ var bgcolor = this.Get('chart.ylabels.inside') ? this.Get('chart.ylabels.inside.color') : null;
+
+
+ /**
+ * If the Y labels are inside the Y axis, invert the alignment
+ */
+ if (this.Get('chart.ylabels.inside') == true && align == 'left') {
+ xpos -= 10;
+ align = 'right';
+ bounding = true;
+
+
+ } else if (this.Get('chart.ylabels.inside') == true && align == 'right') {
+ xpos += 10;
+ align = 'left';
+ bounding = true;
+ }
+
+
+
+ if (this.Get('chart.xaxispos') == 'center') {
+ var half = this.grapharea / 2;
+
+ if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
+ // Draw the upper halves labels
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (0/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
+
+ if (numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (1/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (3/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ if (numYLabels >= 3) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (2/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (4/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ // Draw the lower halves labels
+ if (numYLabels >= 3) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (6/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (8/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ if (numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (7/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (9/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (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);
+
+ } else if (numYLabels == 10) {
+
+ // 10 Y labels
+ var interval = (this.grapharea / numYLabels) / 2;
+
+ for (var i=0; i<numYLabels; ++i) {
+ // This draws the upper halves labels
+ RGraph.Text(context,font, text_size, xpos, this.gutterTop + 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);
+
+ // And this draws the lower halves labels
+ RGraph.Text(context, font, text_size, xpos,
+
+ this.gutterTop + this.halfTextHeight + ((i/20) * this.grapharea) + (this.grapharea / 2) + (this.grapharea / 20),
+
+ '-' + 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);
+ }
+
+ } else {
+ alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
+ }
+
+ // Draw the lower limit if chart.ymin is specified
+ if (typeof(this.Get('chart.ymin')) == 'number') {
+ RGraph.Text(context, font, text_size, xpos, RGraph.GetHeight(this) / 2, RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post), 'center', align, bounding, null, bgcolor);
+ }
+
+ // No X axis - so draw 0
+ if (this.Get('chart.noxaxis') == true) {
+ RGraph.Text(context,font,text_size,xpos,this.gutterTop + ( (5/5) * half ) + this.halfTextHeight,this.Get('chart.units.pre') + '0' + this.Get('chart.units.post'),null, align, bounding, null, bgcolor);
+ }
+
+ // X axis at the top
+ } else if (this.Get('chart.xaxispos') == 'top') {
+
+ var scale = RGraph.array_reverse(this.scale);
+
+ /**
+ * Accommodate reversing the Y labels
+ */
+ if (this.Get('chart.ylabels.invert')) {
+
+ scale = RGraph.array_reverse(scale);
+
+ this.context.translate(0, this.grapharea * -0.2);
+ if (typeof(this.Get('chart.ymin')) == null) {
+ this.Set('chart.ymin', 0);
+ }
+ }
+
+ if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((1/5) * (this.grapharea ) ), '-' + RGraph.number_format(this, scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
+
+ if (numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((4/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((2/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ if (numYLabels >= 3) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((3/5) * (this.grapharea ) ), '-' + RGraph.number_format(this, scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((5/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ } else if (numYLabels == 10) {
+
+ // 10 Y labels
+ var interval = (this.grapharea / numYLabels) / 2;
+
+ for (var i=0; i<numYLabels; ++i) {
+
+ RGraph.Text(context,font,text_size,xpos,(2 * interval) + this.gutterTop + this.halfTextHeight + ((i/10) * (this.grapharea) ),'-' + RGraph.number_format(this,(scale[0] - (((scale[0] - this.min) / numYLabels) * (numYLabels - i - 1))).toFixed((this.Get('chart.scale.decimals'))),units_pre,units_post),null,align,bounding,null,bgcolor);
+ }
+
+ } else {
+ alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
+ }
+
+
+ /**
+ * Accommodate translating back after reversing the labels
+ */
+ if (this.Get('chart.ylabels.invert')) {
+ this.context.translate(0, 0 - (this.grapharea * -0.2));
+ }
+
+ // Draw the lower limit if chart.ymin is specified
+ if (typeof(this.Get('chart.ymin')) == 'number') {
+ RGraph.Text(context,font,text_size,xpos,this.Get('chart.ylabels.invert') ? this.canvas.height - this.gutterBottom : this.gutterTop,'-' + RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center',align,bounding,null,bgcolor);
+ }
+
+ } else {
+
+ /**
+ * Accommodate reversing the Y labels
+ */
+ if (this.Get('chart.ylabels.invert')) {
+ this.scale = RGraph.array_reverse(this.scale);
+ this.context.translate(0, this.grapharea * 0.2);
+ if (typeof(this.Get('chart.ymin')) == null) {
+ this.Set('chart.ymin', 0);
+ }
+ }
+
+ if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((0/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
+
+ if (numYLabels == 5) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((3/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((1/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ if (numYLabels >= 3) {
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((2/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
+ RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((4/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
+ }
+
+ } else if (numYLabels == 10) {
+
+ // 10 Y labels
+ var interval = (this.grapharea / numYLabels) / 2;
+
+ for (var i=0; i<numYLabels; ++i) {
+ RGraph.Text(context,font,text_size,xpos,this.gutterTop + this.halfTextHeight + ((i/10) * (this.grapharea) ),RGraph.number_format(this,((((this.scale[4] - this.min) / numYLabels) * (numYLabels - i)) + this.min).toFixed((this.Get('chart.scale.decimals'))),units_pre,units_post),null,align,bounding,null,bgcolor);
+ }
+
+ } else {
+ alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
+ }
+
+
+ /**
+ * Accommodate translating back after reversing the labels
+ */
+ if (this.Get('chart.ylabels.invert')) {
+ this.context.translate(0, 0 - (this.grapharea * 0.2));
+ }
+
+ // Draw the lower limit if chart.ymin is specified
+ if (typeof(this.Get('chart.ymin')) == 'number') {
+ RGraph.Text(context,font,text_size,xpos,this.Get('chart.ylabels.invert') ? this.gutterTop : RGraph.GetHeight(this) - this.gutterBottom,RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center',align,bounding,null,bgcolor);
+ }
+ }
+
+ // No X axis - so draw 0 - but not if the X axis is in the center
+ if ( this.Get('chart.noxaxis') == true
+ && this.Get('chart.ymin') == null
+ && this.Get('chart.xaxispos') != 'center'
+ ) {
+
+ RGraph.Text(context,font,text_size,xpos,this.Get('chart.xaxispos') == 'top' ? this.gutterTop + this.halfTextHeight: (this.canvas.height - this.gutterBottom + this.halfTextHeight),this.Get('chart.units.pre') + '0' + this.Get('chart.units.post'),null, align, bounding, null, bgcolor);
+ }
+
+ } else if (this.Get('chart.ylabels') && typeof(this.Get('chart.ylabels.specific')) == 'object') {
+
+ // A few things
+ var gap = this.grapharea / this.Get('chart.ylabels.specific').length;
+ var halign = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
+ var bounding = false;
+ var bgcolor = null;
+ var ymin = this.Get('chart.ymin') != null && this.Get('chart.ymin');
+
+ // Figure out the X coord based on the position of the axis
+ if (this.Get('chart.yaxispos') == 'left') {
+ var x = this.gutterLeft - 5;
+
+ if (this.Get('chart.ylabels.inside')) {
+ x += 10;
+ halign = 'left';
+ bounding = true;
+ bgcolor = 'rgba(255,255,255,0.5)';
+ }
+
+ } else if (this.Get('chart.yaxispos') == 'right') {
+ var x = this.canvas.width - this.gutterRight + 5;
+
+ if (this.Get('chart.ylabels.inside')) {
+ x -= 10;
+ halign = 'right';
+ bounding = true;
+ bgcolor = 'rgba(255,255,255,0.5)';
+ }
+ }
+
+
+ // Draw the labels
+ if (this.Get('chart.xaxispos') == 'center') {
+
+ // Draw the top halfs labels
+ for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
+ var y = this.gutterTop + (this.grapharea / ((this.Get('chart.ylabels.specific').length ) * 2) * i);
+
+ if (ymin && ymin > 0) {
+ var y = ((this.grapharea / 2) / (this.Get('chart.ylabels.specific').length - (ymin ? 1 : 0)) ) * i;
+ y += this.gutterTop;
+ }
+
+ RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
+ }
+
+ // Now reverse the labels and draw the bottom half
+ var reversed_labels = RGraph.array_reverse(this.Get('chart.ylabels.specific'));
+
+ // Draw the bottom halfs labels
+ for (var i=0; i<reversed_labels.length; ++i) {
+ var y = (this.grapharea / 2) + this.gutterTop + ((this.grapharea / (reversed_labels.length * 2) ) * (i + 1));
+
+ if (ymin && ymin > 0) {
+ var y = ((this.grapharea / 2) / (reversed_labels.length - (ymin ? 1 : 0)) ) * i;
+ y += this.gutterTop;
+ y += (this.grapharea / 2);
+ }
+
+ RGraph.Text(context, font, text_size,x,y,String(reversed_labels[i]), 'center', halign, bounding, 0, bgcolor);
+ }
+
+ } else if (this.Get('chart.xaxispos') == 'top') {
+
+ // Reverse the labels and draw
+ var reversed_labels = RGraph.array_reverse(this.Get('chart.ylabels.specific'));
+
+ // Draw the bottom halfs labels
+ for (var i=0; i<reversed_labels.length; ++i) {
+
+ var y = ((this.grapharea / (reversed_labels.length - (ymin ? 1 : 0)) ) * (i + (ymin ? 0 : 1)));
+ y = y + this.gutterTop;
+
+ RGraph.Text(context, font, text_size,x,y,String(reversed_labels[i]), 'center', halign, bounding, 0, bgcolor);
+ }
+
+ } else {
+ for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
+ var y = this.gutterTop + ((this.grapharea / (this.Get('chart.ylabels.specific').length - (ymin ? 1 : 0) )) * i);
+ RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
+ }
+ }
+ }
+
+ // Draw the X axis labels
+ if (this.Get('chart.labels') && this.Get('chart.labels').length > 0) {
+
+
+ var yOffset = 13;
+ var bordered = false;
+ var bgcolor = null;
+
+ if (this.Get('chart.xlabels.inside')) {
+ yOffset = -5;
+ bordered = true;
+ bgcolor = this.Get('chart.xlabels.inside.color');
+ }
+
+ /**
+ * Text angle
+ */
+ var angle = 0;
+ var valign = null;
+ var halign = 'center';
+
+ if (typeof(this.Get('chart.text.angle')) == 'number' && this.Get('chart.text.angle') > 0) {
+ angle = -1 * this.Get('chart.text.angle');
+ valign = 'center';
+ halign = 'right';
+ yOffset = 10;
+
+ if (this.Get('chart.xaxispos') == 'top') {
+ yOffset = 10;
+ }
+ }
+
+ this.context.fillStyle = this.Get('chart.text.color');
+ var numLabels = this.Get('chart.labels').length;
+
+ for (i=0; i<numLabels; ++i) {
+
+ // Changed 8th Nov 2010 to be not reliant on the coords
+ //if (this.Get('chart.labels')[i] && this.coords && this.coords[i] && this.coords[i][0]) {
+ if (this.Get('chart.labels')[i]) {
+
+ var labelX = ((RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight - (2 * this.Get('chart.hmargin'))) / (numLabels - 1) ) * i;
+ labelX += this.gutterLeft + this.Get('chart.hmargin');
+
+ /**
+ * Account for an unrelated number of labels
+ */
+ if (this.Get('chart.labels').length != this.data[0].length) {
+ labelX = this.gutterLeft + this.Get('chart.hmargin') + ((RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight - (2 * this.Get('chart.hmargin'))) * (i / (this.Get('chart.labels').length - 1)));
+ }
+
+ // This accounts for there only being one point on the chart
+ if (!labelX) {
+ labelX = this.gutterLeft + this.Get('chart.hmargin');
+ }
+
+ if (this.Get('chart.xaxispos') == 'top' && this.Get('chart.text.angle') > 0) {
+ halign = 'left';
+ }
+
+ RGraph.Text(context,
+ font,
+ text_size,
+ labelX,
+ (this.Get('chart.xaxispos') == 'top') ? this.gutterTop - yOffset - (this.Get('chart.xlabels.inside') ? -22 : 0) : (RGraph.GetHeight(this) - this.gutterBottom) + yOffset,
+ String(this.Get('chart.labels')[i]),
+ valign,
+ halign,
+ bordered,
+ angle,
+ bgcolor);
+ }
+ }
+
+ }
+
+ this.context.stroke();
+ this.context.fill();
+ }
+
+
+ /**
+ * Draws the line
+ */
+ RGraph.Line.prototype.DrawLine = function (lineData, color, fill, linewidth, tickmarks, index)
+ {
+ // This facilitates the Rise animation (the Y value only)
+ if (this.Get('chart.animation.unfold.y') && this.Get('chart.animation.factor') != 1) {
+ for (var i=0; i<lineData.length; ++i) {
+ lineData[i] *= this.Get('chart.animation.factor');
+ }
+ }
+
+ var penUp = false;
+ var yPos = null;
+ var xPos = 0;
+ this.context.lineWidth = 1;
+ var lineCoords = [];
+
+ /**
+ * Get the previous line data
+ */
+ if (index > 0) {
+ var prevLineCoords = this.coords2[index - 1];
+ }
+
+ // Work out the X interval
+ var xInterval = (this.canvas.width - (2 * this.Get('chart.hmargin')) - this.gutterLeft - this.gutterRight) / (lineData.length - 1);
+
+ // Loop thru each value given, plotting the line
+ // (FORMERLY FIRST)
+ for (i=0; i<lineData.length; i++) {
+
+ var data_point = lineData[i];
+
+ yPos = this.canvas.height - (((data_point - (data_point > 0 ? this.Get('chart.ymin') : (-1 * this.Get('chart.ymin')))) / (this.max - this.min) ) * this.grapharea);
+ yPos = (this.grapharea / (this.max - this.min)) * (data_point - this.min);
+ yPos = this.canvas.height - yPos;
+
+ /**
+ * This skirts an annoying JS rounding error
+ * SEARCH TAGS: JS ROUNDING ERROR DECIMALS
+ */
+ if (data_point == this.max) {
+ yPos = Math.round(yPos);
+ }
+
+ if (this.Get('chart.ylabels.invert')) {
+ yPos -= this.gutterBottom;
+ yPos -= this.gutterTop;
+ yPos = this.canvas.height - yPos;
+ }
+
+ // Make adjustments depending on the X axis position
+ if (this.Get('chart.xaxispos') == 'center') {
+ yPos = (yPos - this.gutterBottom - this.gutterTop) / 2;
+ yPos = yPos + this.gutterTop;
+
+ // TODO Check this
+ } else if (this.Get('chart.xaxispos') == 'top') {
+
+ yPos = (this.grapharea / (this.max - this.min)) * (Math.abs(data_point) - this.min);
+ yPos += this.gutterTop;
+
+ if (this.Get('chart.ylabels.invert')) {
+ yPos -= this.gutterTop;
+ yPos = this.grapharea - yPos;
+ yPos += this.gutterTop;
+ }
+
+ } else if (this.Get('chart.xaxispos') == 'bottom') {
+ // TODO
+ yPos -= this.gutterBottom; // Without this the line is out of place due to the gutter
+ }
+
+
+
+ // Null data points, and a special case for this bug:http://dev.rgraph.net/tests/ymin.html
+ if ( lineData[i] == null
+ || (this.Get('chart.xaxispos') == 'bottom' && lineData[i] < this.min && !this.Get('chart.outofbounds'))
+ || (this.Get('chart.xaxispos') == 'center' && lineData[i] < (-1 * this.max) && !this.Get('chart.outofbounds'))) {
+
+ yPos = null;
+ }
+
+ // Not always very noticeable, but it does have an effect
+ // with thick lines
+ this.context.lineCap = 'round';
+ this.context.lineJoin = 'round';
+
+ // Plot the line if we're at least on the second iteration
+ if (i > 0) {
+ xPos = xPos + xInterval;
+ } else {
+ xPos = this.Get('chart.hmargin') + this.gutterLeft;
+ }
+
+ if (this.Get('chart.animation.unfold.x')) {
+ xPos *= this.Get('chart.animation.factor');
+
+ if (xPos < this.Get('chart.gutter.left')) {
+ xPos = this.Get('chart.gutter.left');
+ }
+ }
+
+ /**
+ * Add the coords to an array
+ */
+ this.coords.push([xPos, yPos]);
+ lineCoords.push([xPos, yPos]);
+ }
+
+ this.context.stroke();
+
+ // Store the coords in another format, indexed by line number
+ this.coords2[index] = lineCoords;
+
+ /**
+ * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
+ */
+ if (RGraph.isIE8() && this.Get('chart.shadow')) {
+ this.DrawIEShadow(lineCoords, this.context.shadowColor);
+ }
+
+ /**
+ * Now draw the actual line [FORMERLY SECOND]
+ */
+ this.context.beginPath();
+ // Transparent now as of 11/19/2011
+ this.context.strokeStyle = 'rgba(0,0,0,0)';
+ //this.context.strokeStyle = fill;
+ if (fill) {
+ this.context.fillStyle = fill;
+ }
+
+ var isStepped = this.Get('chart.stepped');
+ var isFilled = this.Get('chart.filled');
+
+
+ for (var i=0; i<lineCoords.length; ++i) {
+
+ xPos = lineCoords[i][0];
+ yPos = lineCoords[i][1];
+ var set = index;
+
+ var prevY = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
+ var isLast = (i + 1) == lineCoords.length;
+
+ /**
+ * This nullifys values which are out-of-range
+ */
+ if (prevY < this.gutterTop || prevY > (RGraph.GetHeight(this) - this.gutterBottom) ) {
+ penUp = true;
+ }
+
+ if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutterTop) {
+
+ if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
+
+ this.context.moveTo(xPos + 1, this.canvas.height - this.gutterBottom - (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - this.gutterTop - this.gutterBottom) / 2 : 0) -1);
+
+ // This facilitates the X axis being at the top
+ // NOTE: Also done below
+ if (this.Get('chart.xaxispos') == 'top') {
+ this.context.moveTo(xPos + 1, this.Get('chart.gutter.top'));
+ }
+
+ this.context.lineTo(xPos + 1, yPos);
+
+ } else {
+
+ if (RGraph.isIE8() && yPos == null) {
+ // Nada
+ } else {
+ this.context.moveTo(xPos, yPos);
+ }
+ }
+
+ if (yPos == null) {
+ penUp = true;
+
+ } else {
+ penUp = false;
+ }
+
+ } else {
+
+ // Draw the stepped part of stepped lines
+ if (isStepped) {
+ this.context.lineTo(xPos, lineCoords[i - 1][1]);
+ }
+
+ if ((yPos >= this.gutterTop && yPos <= (this.canvas.height - this.gutterBottom)) || this.Get('chart.outofbounds') ) {
+
+ if (isLast && this.Get('chart.filled') && !this.Get('chart.filled.range') && this.Get('chart.yaxispos') == 'right') {
+ xPos -= 1;
+ }
+
+
+ // Added 8th September 2009
+ if (!isStepped || !isLast) {
+ this.context.lineTo(xPos, yPos);
+
+ if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) {
+ this.context.lineTo(xPos, RGraph.GetHeight(this) - this.gutterBottom);
+ }
+
+ // Added August 2010
+ } else if (isStepped && isLast) {
+ this.context.lineTo(xPos,yPos);
+ }
+
+
+ penUp = false;
+ } else {
+ penUp = true;
+ }
+ }
+ }
+
+ if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
+
+ // Is this needed ??
+ var fillStyle = this.Get('chart.fillstyle');
+
+ /**
+ * Draw the bottom edge of the filled bit using either the X axis or the prevlinedata,
+ * depending on the index of the line. The first line uses the X axis, and subsequent
+ * lines use the prevLineCoords array
+ */
+ if (index > 0 && this.Get('chart.filled.accumulative')) {
+ this.context.lineTo(xPos, prevLineCoords ? prevLineCoords[i - 1][1] : (this.canvas.height - this.gutterBottom - 1 + (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - this.gutterTop - this.gutterBottom) / 2 : 0)));
+
+ for (var k=(i - 1); k>=0; --k) {
+ this.context.lineTo(prevLineCoords[k][0], prevLineCoords[k][1]);
+ }
+ } else {
+ // Draw a line down to the X axis
+ if (this.Get('chart.xaxispos') == 'top') {
+ this.context.lineTo(xPos, this.Get('chart.gutter.top') + 1);
+ this.context.lineTo(lineCoords[0][0],this.Get('chart.gutter.top') + 1);
+ } else {
+ this.context.lineTo(xPos,this.canvas.height - this.gutterBottom - 1 - + (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - this.gutterTop - this.gutterBottom) / 2 : 0));
+ this.context.lineTo(lineCoords[0][0],this.canvas.height - this.Get('chart.gutter.bottom') - (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - this.gutterTop - this.gutterBottom) / 2 : 0));
+ }
+ }
+
+ this.context.fillStyle = fill;
+
+ this.context.fill();
+ this.context.beginPath();
+ }
+
+ /**
+ * FIXME this may need removing when Chrome is fixed
+ * SEARCH TAGS: CHROME SHADOW BUG
+ */
+ if (navigator.userAgent.match(/Chrome/) && this.Get('chart.shadow') && this.Get('chart.chromefix') && this.Get('chart.shadow.blur') > 0) {
+
+ for (var i=lineCoords.length - 1; i>=0; --i) {
+ if (
+ typeof(lineCoords[i][1]) != 'number'
+ || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
+ ) {
+ this.context.moveTo(lineCoords[i][0],lineCoords[i][1]);
+ } else {
+ this.context.lineTo(lineCoords[i][0],lineCoords[i][1]);
+ }
+ }
+ }
+
+ this.context.stroke();
+
+
+ if (this.Get('chart.backdrop')) {
+ this.DrawBackdrop(lineCoords, color);
+ }
+
+ // Now redraw the lines with the correct line width
+ this.RedrawLine(lineCoords, color, linewidth);
+
+ this.context.stroke();
+
+ // Draw the tickmarks
+ for (var i=0; i<lineCoords.length; ++i) {
+
+ i = Number(i);
+
+ if (isStepped && i == (lineCoords.length - 1)) {
+ this.context.beginPath();
+ //continue;
+ }
+
+ if (
+ (
+ tickmarks != 'endcircle'
+ && tickmarks != 'endsquare'
+ && tickmarks != 'filledendsquare'
+ && tickmarks != 'endtick'
+ && tickmarks != 'endtriangle'
+ && tickmarks != 'arrow'
+ && tickmarks != 'filledarrow'
+ )
+ || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
+ || i == (lineCoords.length - 1)
+ ) {
+
+ var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
+ var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
+
+ this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);
+
+ // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
+ //
+ //if (this.Get('chart.stepped') && lineCoords[i + 1] && this.Get('chart.tickmarks') != 'endsquare' && this.Get('chart.tickmarks') != 'endcircle' && this.Get('chart.tickmarks') != 'endtick') {
+ // this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
+ //}
+ }
+ }
+
+ // Draw something off canvas to skirt an annoying bug
+ this.context.beginPath();
+ this.context.arc(RGraph.GetWidth(this) + 50000, RGraph.GetHeight(this) + 50000, 2, 0, 6.38, 1);
+ }
+
+
+ /**
+ * This functions draws a tick mark on the line
+ *
+ * @param xPos int The x position of the tickmark
+ * @param yPos int The y position of the tickmark
+ * @param color str The color of the tickmark
+ * @param bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
+ */
+ RGraph.Line.prototype.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index)
+ {
+ // If the yPos is null - no tick
+ if ((yPos == null || yPos > (this.canvas.height - this.gutterBottom) || yPos < this.gutterTop) && !this.Get('chart.outofbounds') || !this.Get('chart.line.visible')) {
+ return;
+ }
+
+ this.context.beginPath();
+
+ var offset = 0;
+
+ // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
+ // UPDATE 28th July 2011 - the line width is now set to 1
+ this.context.lineWidth = this.Get('chart.tickmarks.linewidth') ? this.Get('chart.tickmarks.linewidth') : this.Get('chart.linewidth');
+ this.context.strokeStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
+
+ // Cicular tick marks
+ if ( tickmarks == 'circle'
+ || tickmarks == 'filledcircle'
+ || tickmarks == 'endcircle') {
+
+ if (tickmarks == 'circle'|| tickmarks == 'filledcircle' || (tickmarks == 'endcircle') ) {
+ this.context.beginPath();
+ this.context.arc(xPos + offset, yPos + offset, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
+
+ if (tickmarks == 'filledcircle') {
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
+ } else {
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
+ }
+
+ this.context.stroke();
+ this.context.fill();
+ }
+
+ // Halfheight "Line" style tick marks
+ } else if (tickmarks == 'halftick') {
+ this.context.beginPath();
+ this.context.moveTo(xPos, yPos);
+ this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
+
+ this.context.stroke();
+
+ // Tick style tickmarks
+ } else if (tickmarks == 'tick') {
+ this.context.beginPath();
+ this.context.moveTo(xPos, yPos - this.Get('chart.ticksize'));
+ this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
+
+ this.context.stroke();
+
+ // Endtick style tickmarks
+ } else if (tickmarks == 'endtick') {
+ this.context.beginPath();
+ this.context.moveTo(xPos, yPos - this.Get('chart.ticksize'));
+ this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
+
+ this.context.stroke();
+
+ // "Cross" style tick marks
+ } else if (tickmarks == 'cross') {
+ this.context.beginPath();
+ this.context.moveTo(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
+ this.context.lineTo(xPos + this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
+ this.context.moveTo(xPos + this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
+ this.context.lineTo(xPos - this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
+
+ this.context.stroke();
+
+
+ // Triangle style tick marks
+ } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || tickmarks == 'endtriangle') {
+ this.context.beginPath();
+
+ if (tickmarks == 'filledtriangle') {
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
+ } else {
+ this.context.fillStyle = 'white';
+ }
+
+ this.context.moveTo(xPos - this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
+ this.context.lineTo(xPos, yPos - this.Get('chart.ticksize'));
+ this.context.lineTo(xPos + this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
+ this.context.closePath();
+
+ this.context.stroke();
+ this.context.fill();
+
+
+ // A white bordered circle
+ } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
+ this.context.lineWidth = 1;
+ this.context.strokeStyle = this.Get('chart.tickmarks.dot.color');
+ this.context.fillStyle = this.Get('chart.tickmarks.dot.color');
+
+ // The outer white circle
+ this.context.beginPath();
+ this.context.arc(xPos, yPos, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
+ this.context.closePath();
+
+
+ this.context.fill();
+ this.context.stroke();
+
+ // Now do the inners
+ this.context.beginPath();
+ this.context.fillStyle = color;
+ this.context.strokeStyle = color;
+ this.context.arc(xPos, yPos, this.Get('chart.ticksize') - 2, 0, 360 / (180 / Math.PI), false);
+
+ this.context.closePath();
+
+ this.context.fill();
+ this.context.stroke();
+
+ } else if ( tickmarks == 'square'
+ || tickmarks == 'filledsquare'
+ || (tickmarks == 'endsquare')
+ || (tickmarks == 'filledendsquare') ) {
+
+ this.context.fillStyle = 'white';
+ this.context.strokeStyle = this.context.strokeStyle; // FIXME Is this correct?
+
+ this.context.beginPath();
+ this.context.strokeRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
+
+ // Fillrect
+ if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
+ this.context.fillRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
+
+ } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
+ this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
+ 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);
+ }
+
+ this.context.stroke();
+ this.context.fill();
+
+ /**
+ * FILLED arrowhead
+ */
+ } else if (tickmarks == 'filledarrow') {
+
+ var x = Math.abs(xPos - prevX);
+ var y = Math.abs(yPos - prevY);
+
+ if (yPos < prevY) {
+ var a = Math.atan(x / y) + 1.57;
+ } else {
+ var a = Math.atan(y / x) + 3.14;
+ }
+
+ this.context.beginPath();
+ this.context.moveTo(xPos, yPos);
+ this.context.arc(xPos, yPos, 7, a - 0.5, a + 0.5, false);
+ this.context.closePath();
+
+ this.context.stroke();
+ this.context.fill();
+
+ /**
+ * Arrow head, NOT filled
+ */
+ } else if (tickmarks == 'arrow') {
+
+ var x = Math.abs(xPos - prevX);
+ var y = Math.abs(yPos - prevY);
+
+ if (yPos < prevY) {
+ var a = Math.atan(x / y) + 1.57;
+ } else {
+ var a = Math.atan(y / x) + 3.14;
+ }
+
+ this.context.beginPath();
+ this.context.moveTo(xPos, yPos);
+ this.context.arc(xPos, yPos, 7, a - 0.5 - (document.all ? 0.1 : 0.01), a - 0.4, false);
+
+ this.context.moveTo(xPos, yPos);
+ this.context.arc(xPos, yPos, 7, a + 0.5 + (document.all ? 0.1 : 0.01), a + 0.5, true);
+
+
+ this.context.stroke();
+
+ /**
+ * Custom tick drawing function
+ */
+ } else if (typeof(tickmarks) == 'function') {
+ tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
+ }
+ }
+
+
+ /**
+ * Draws a filled range if necessary
+ */
+ RGraph.Line.prototype.DrawRange = function ()
+ {
+ /**
+ * Fill the range if necessary
+ */
+ if (this.Get('chart.filled.range') && this.Get('chart.filled')) {
+ this.context.beginPath();
+ this.context.fillStyle = this.Get('chart.fillstyle');
+ this.context.strokeStyle = this.Get('chart.fillstyle');
+ this.context.lineWidth = 1;
+ var len = (this.coords.length / 2);
+
+ for (var i=0; i<len; ++i) {
+ if (i == 0) {
+ this.context.moveTo(this.coords[i][0], this.coords[i][1])
+ } else {
+ this.context.lineTo(this.coords[i][0], this.coords[i][1])
+ }
+ }
+
+ for (var i=this.coords.length - 1; i>=len; --i) {
+ this.context.lineTo(this.coords[i][0], this.coords[i][1])
+ }
+ this.context.stroke();
+ this.context.fill();
+ }
+ }
+
+
+ /**
+ * Redraws the line with the correct line width etc
+ *
+ * @param array coords The coordinates of the line
+ */
+ RGraph.Line.prototype.RedrawLine = function (coords, color, linewidth)
+ {
+ if (this.Get('chart.noredraw')) {
+ return;
+ }
+
+ this.context.strokeStyle = (typeof(color) == 'object' && color ? color[0] : color);
+ this.context.lineWidth = linewidth;
+
+ if (!this.Get('chart.line.visible')) {
+ this.context.strokeStyle = 'rgba(0,0,0,0)';
+ }
+
+ if (this.Get('chart.curvy')) {
+ this.DrawCurvyLine(coords, !this.Get('chart.line.visible') ? 'rgba(0,0,0,0)' : color, linewidth);
+ return
+ }
+
+ this.context.beginPath();
+
+ var len = coords.length;
+ var width = this.canvas.width
+ var height = this.canvas.height;
+ var penUp = false;
+
+ for (var i=0; i<len; ++i) {
+
+ var xPos = coords[i][0];
+ var yPos = coords[i][1];
+
+ if (i > 0) {
+ var prevX = coords[i - 1][0];
+ var prevY = coords[i - 1][1];
+ }
+
+
+ if ((
+ (i == 0 && coords[i])
+ || (yPos < this.gutterTop)
+ || (prevY < this.gutterTop)
+ || (yPos > (height - this.gutterBottom))
+ || (i > 0 && prevX > (width - this.gutterRight))
+ || (i > 0 && prevY > (height - this.gutterBottom))
+ || prevY == null
+ || penUp == true
+ ) && (!this.Get('chart.outofbounds') || yPos == null || prevY == null) ) {
+
+ if (RGraph.isIE8() && yPos == null) {
+ // ...?
+ } else {
+ this.context.moveTo(coords[i][0], coords[i][1]);
+ }
+
+ penUp = false;
+
+ } else {
+
+ if (this.Get('chart.stepped') && i > 0) {
+ this.context.lineTo(coords[i][0], coords[i - 1][1]);
+ }
+
+ // Don't draw the last bit of a stepped chart. Now DO
+ //if (!this.Get('chart.stepped') || i < (coords.length - 1)) {
+ this.context.lineTo(coords[i][0], coords[i][1]);
+ //}
+ penUp = false;
+ }
+ }
+
+ /**
+ * If two colors are specified instead of one, go over the up bits
+ */
+ if (this.Get('chart.colors.alternate') && typeof(color) == 'object' && color[0] && color[1]) {
+ for (var i=1; i<len; ++i) {
+
+ var prevX = coords[i - 1][0];
+ var prevY = coords[i - 1][1];
+
+ this.context.beginPath();
+ this.context.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
+ this.context.lineWidth = this.Get('chart.linewidth');
+ this.context.moveTo(prevX, prevY);
+ this.context.lineTo(coords[i][0], coords[i][1]);
+ this.context.stroke();
+ }
+ }
+ }
+
+
+ /**
+ * This function is used by MSIE only to manually draw the shadow
+ *
+ * @param array coords The coords for the line
+ */
+ RGraph.Line.prototype.DrawIEShadow = function (coords, color)
+ {
+ var offsetx = this.Get('chart.shadow.offsetx');
+ var offsety = this.Get('chart.shadow.offsety');
+
+ this.context.lineWidth = this.Get('chart.linewidth');
+ this.context.strokeStyle = color;
+ this.context.beginPath();
+
+ for (var i=0; i<coords.length; ++i) {
+ if (i == 0) {
+ this.context.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
+ } else {
+ this.context.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
+ }
+ }
+
+ this.context.stroke();
+ }
+
+
+ /**
+ * Draw the backdrop
+ */
+ RGraph.Line.prototype.DrawBackdrop = function (coords, color)
+ {
+ var size = this.Get('chart.backdrop.size');
+ this.context.lineWidth = size;
+ this.context.globalAlpha = this.Get('chart.backdrop.alpha');
+ this.context.strokeStyle = color;
+ this.context.lineJoin = 'miter';
+
+ this.context.beginPath();
+ this.context.moveTo(coords[0][0], coords[0][1]);
+ for (var j=1; j<coords.length; ++j) {
+ this.context.lineTo(coords[j][0], coords[j][1]);
+ }
+
+ this.context.stroke();
+
+ // Reset the alpha value
+ this.context.globalAlpha = 1;
+ this.context.lineJoin = 'round';
+ RGraph.NoShadow(this);
+ }
+
+
+ /**
+ * Returns the linewidth
+ */
+ RGraph.Line.prototype.GetLineWidth = function (i)
+ {
+ var linewidth = this.Get('chart.linewidth');
+
+ if (typeof(linewidth) == 'number') {
+ return linewidth;
+
+ } else if (typeof(linewidth) == 'object') {
+ if (linewidth[i]) {
+ return linewidth[i];
+ } else {
+ return linewidth[0];
+ }
+
+ alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
+ }
+ }
+
+
+ /**
+ * The getPoint() method - used to get the point the mouse is currently over, if any
+ *
+ * @param object e The event object
+ * @param object OPTIONAL You can pass in the bar object instead of the
+ * function getting it from the canvas
+ */
+ RGraph.Line.prototype.getPoint = function (e)
+ {
+ var canvas = e.target;
+ var obj = canvas.__object__;
+ var context = obj.context;
+ var mouseXY = RGraph.getMouseXY(e);
+ var mouseX = mouseXY[0];
+ var mouseY = mouseXY[1];
+
+ // This facilitates you being able to pass in the bar object as a parameter instead of
+ // the function getting it from the object
+ if (arguments[1]) {
+ obj = arguments[1];
+ }
+
+ for (var i=0; i<obj.coords.length; ++i) {
+
+ var xCoord = obj.coords[i][0];
+ var yCoord = obj.coords[i][1];
+
+ // Do this if the hotspot is triggered by the X coord AND the Y coord
+ if ( obj.Get('chart.tooltips.hotspot.xonly') == false
+ && mouseX <= (xCoord + 5)
+ && mouseX >= (xCoord - 5)
+ && mouseY <= (yCoord + 5)
+ && mouseY >= (yCoord - 5)
+ ) {
+
+ return [obj, xCoord, yCoord, i];
+
+ } else if ( obj.Get('chart.tooltips.hotspot.xonly') == true
+ && mouseX <= (xCoord + 5)
+ && mouseX >= (xCoord - 5)) {
+
+ return [obj, xCoord, yCoord, i];
+ }
+ }
+ }
+
+
+ /**
+ * Draws the above line labels
+ */
+ RGraph.Line.prototype.DrawAboveLabels = function ()
+ {
+ var context = this.context;
+ var size = this.Get('chart.labels.above.size');
+ var font = this.Get('chart.text.font');
+ var units_pre = this.Get('chart.units.pre');
+ var units_post = this.Get('chart.units.post');
+
+ context.beginPath();
+
+ // Don't need to check that chart.labels.above is enabled here, it's been done already
+ for (var i=0; i<this.coords.length; ++i) {
+ var coords = this.coords[i];
+
+ 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)');
+ }
+
+ context.fill();
+ }
+
+
+ /**
+ * Draw a curvy line. This isn't 100% accurate but may be more to your tastes
+ */
+ RGraph.Line.prototype.DrawCurvyLine = function (coords, color, linewidth)
+ {
+ var co = this.context;
+
+ // Now calculate the halfway point
+ co.beginPath();
+
+ co.strokeStyle = color;
+ co.lineWidth = linewidth;
+
+ for (var i=0; i<coords.length; ++i) {
+ if (coords[i + 1]) {
+
+ var factor = this.Get('chart.curvy.factor');
+ var coordX = coords[i][0];
+ var coordY = coords[i][1];
+ var nextX = coords[i + 1][0];
+ var nextY = coords[i + 1][1];
+ var offsetX = (coords[i + 1][0] - coords[i][0]) * factor;
+ var offsetY = (coords[i + 1][1] - coords[i][1]) * factor;
+
+ if (i == 0) {
+ co.moveTo(coordX, coordY);
+ co.lineTo(nextX - offsetX, nextY - offsetY);
+
+ } else if (nextY == null) {
+ co.lineTo(coordX, coordY);
+
+ } else if (coordY == null) {
+ co.moveTo(nextX, nextY);
+
+
+ } else {
+
+ co.quadraticCurveTo(coordX, coordY, coordX + offsetX, coordY + offsetY);
+
+ if (nextY) {
+ co.lineTo(nextX - offsetX, nextY - offsetY);
+ } else {
+ co.lineTo(coordX, coordY);
+ }
+ }
+
+ // Draw the last bit
+ } else {
+ co.lineTo(coords[i][0], coords[i][1]);
+ }
+ }
+
+ co.stroke();
+ } \ No newline at end of file