/** * jqPlot * Pure JavaScript plotting plugin using jQuery * * Version: @VERSION * Revision: @REVISION * * Copyright (c) 2009-2013 Chris Leonello * jqPlot is currently available for use in all personal or commercial projects * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can * choose the license that best suits your project and use it accordingly. * * Although not required, the author would appreciate an email letting him * know of any substantial use of jqPlot. You can reach the author at: * chris at jqplot dot com or see http://www.jqplot.com/info.php . * * If you are feeling kind and generous, consider supporting the project by * making a donation at: http://www.jqplot.com/donate.php . * * sprintf functions contained in jqplot.sprintf.js by Ash Searle: * * version 2007.04.27 * author Ash Searle * http://hexmen.com/blog/2007/03/printf-sprintf/ * http://hexmen.com/js/sprintf.js * The author (Ash Searle) has placed this code in the public domain: * "This code is unrestricted: you are free to use it however you like." * */ (function($) { // class: $.jqplot.LinearAxisRenderer // The default jqPlot axis renderer, creating a numeric axis. $.jqplot.LinearAxisRenderer = function() { }; // called with scope of axis object. $.jqplot.LinearAxisRenderer.prototype.init = function(options){ // prop: breakPoints // EXPERIMENTAL!! Use at your own risk! // Works only with linear axes and the default tick renderer. // Array of [start, stop] points to create a broken axis. // Broken axes have a "jump" in them, which is an immediate // transition from a smaller value to a larger value. // Currently, axis ticks MUST be manually assigned if using breakPoints // by using the axis ticks array option. this.breakPoints = null; // prop: breakTickLabel // Label to use at the axis break if breakPoints are specified. this.breakTickLabel = "≈"; // prop: drawBaseline // True to draw the axis baseline. this.drawBaseline = true; // prop: baselineWidth // width of the baseline in pixels. this.baselineWidth = null; // prop: baselineColor // CSS color spec for the baseline. this.baselineColor = null; // prop: forceTickAt0 // This will ensure that there is always a tick mark at 0. // If data range is strictly positive or negative, // this will force 0 to be inside the axis bounds unless // the appropriate axis pad (pad, padMin or padMax) is set // to 0, then this will force an axis min or max value at 0. // This has know effect when any of the following options // are set: autoscale, min, max, numberTicks or tickInterval. this.forceTickAt0 = false; // prop: forceTickAt100 // This will ensure that there is always a tick mark at 100. // If data range is strictly above or below 100, // this will force 100 to be inside the axis bounds unless // the appropriate axis pad (pad, padMin or padMax) is set // to 0, then this will force an axis min or max value at 100. // This has know effect when any of the following options // are set: autoscale, min, max, numberTicks or tickInterval. this.forceTickAt100 = false; // prop: tickInset // Controls the amount to inset the first and last ticks from // the edges of the grid, in multiples of the tick interval. // 0 is no inset, 0.5 is one half a tick interval, 1 is a full // tick interval, etc. this.tickInset = 0; // prop: minorTicks // Number of ticks to add between "major" ticks. // Major ticks are ticks supplied by user or auto computed. // Minor ticks cannot be created by user. this.minorTicks = 0; // prop: alignTicks // true to align tick marks across opposed axes // such as from the y2axis to yaxis. this.alignTicks = false; this._autoFormatString = ''; this._overrideFormatString = false; this._scalefact = 1.0; $.extend(true, this, options); if (this.breakPoints) { if (!$.isArray(this.breakPoints)) { this.breakPoints = null; } else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) { this.breakPoints = null; } } if (this.numberTicks != null && this.numberTicks < 2) { this.numberTicks = 2; } this.resetDataBounds(); }; // called with scope of axis $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) { if (this.show) { // populate the axis label and value properties. // createTicks is a method on the renderer, but // call it within the scope of the axis. this.renderer.createTicks.call(this, plot); // fill a div with axes labels in the right direction. // Need to pregenerate each axis to get its bounds and // position it and the labels correctly on the plot. var dim=0; var temp; // Added for theming. if (this._elem) { // Memory Leaks patch //this._elem.empty(); this._elem.emptyForce(); this._elem = null; } this._elem = $(document.createElement('div')); this._elem.addClass('jqplot-axis jqplot-'+this.name); this._elem.css('position', 'absolute'); if (this.name == 'xaxis' || this.name == 'x2axis') { this._elem.width(this._plotDimensions.width); } else { this._elem.height(this._plotDimensions.height); } // create a _label object. this.labelOptions.axis = this.name; this._label = new this.labelRenderer(this.labelOptions); if (this._label.show) { var elem = this._label.draw(ctx, plot); elem.appendTo(this._elem); elem = null; } var t = this._ticks; var tick; for (var i=0; i dim) { dim = temp; } } } tick = null; t = null; if (lshow) { w = this._label._elem.outerWidth(true); h = this._label._elem.outerHeight(true); } if (this.name == 'xaxis') { dim = dim + h; this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'}); } else if (this.name == 'x2axis') { dim = dim + h; this._elem.css({'height':dim+'px', left:'0px', top:'0px'}); } else if (this.name == 'yaxis') { dim = dim + w; this._elem.css({'width':dim+'px', left:'0px', top:'0px'}); if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { this._label._elem.css('width', w+'px'); } } else { dim = dim + w; this._elem.css({'width':dim+'px', right:'0px', top:'0px'}); if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { this._label._elem.css('width', w+'px'); } } } }; // called with scope of axis $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) { // we're are operating on an axis here var ticks = this._ticks; var userTicks = this.ticks; var name = this.name; // databounds were set on axis initialization. var db = this._dataBounds; var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height; var interval; var min, max; var pos1, pos2; var tt, i; // get a copy of user's settings for min/max. var userMin = this.min; var userMax = this.max; var userNT = this.numberTicks; var userTI = this.tickInterval; var threshold = 30; this._scalefact = (Math.max(dim, threshold+1) - threshold)/300.0; // if we already have ticks, use them. // ticks must be in order of increasing value. if (userTicks.length) { // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed for (i=0; i this.breakPoints[0] && ut[0] <= this.breakPoints[1]) { t.show = false; t.showGridline = false; t.label = ut[1]; } else { t.label = ut[1]; } } else { t.label = ut[1]; } t.setTick(ut[0], this.name); this._ticks.push(t); } else if ($.isPlainObject(ut)) { $.extend(true, t, ut); t.axis = this.name; this._ticks.push(t); } else { t.value = ut; if (this.breakPoints) { if (ut == this.breakPoints[0]) { t.label = this.breakTickLabel; t._breakTick = true; t.showGridline = false; t.showMark = false; } else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) { t.show = false; t.showGridline = false; } } t.setTick(ut, this.name); this._ticks.push(t); } } this.numberTicks = userTicks.length; this.min = this._ticks[0].value; this.max = this._ticks[this.numberTicks-1].value; this.tickInterval = (this.max - this.min) / (this.numberTicks - 1); } // we don't have any ticks yet, let's make some! else { if (name == 'xaxis' || name == 'x2axis') { dim = this._plotDimensions.width; } else { dim = this._plotDimensions.height; } var _numberTicks = this.numberTicks; // if aligning this axis, use number of ticks from previous axis. // Do I need to reset somehow if alignTicks is changed and then graph is replotted?? if (this.alignTicks) { if (this.name === 'x2axis' && plot.axes.xaxis.show) { _numberTicks = plot.axes.xaxis.numberTicks; } else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) { _numberTicks = plot.axes.yaxis.numberTicks; } } min = ((this.min != null) ? this.min : db.min); max = ((this.max != null) ? this.max : db.max); var range = max - min; var rmin, rmax; var temp; if (this.tickOptions == null || !this.tickOptions.formatString) { this._overrideFormatString = true; } // Doing complete autoscaling if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) { // Check if user must have tick at 0 or 100 and ensure they are in range. // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range. if (this.forceTickAt0) { if (min > 0) { min = 0; } if (max < 0) { max = 0; } } if (this.forceTickAt100) { if (min > 100) { min = 100; } if (max < 100) { max = 100; } } var keepMin = false, keepMax = false; if (this.min != null) { keepMin = true; } else if (this.max != null) { keepMax = true; } // var threshold = 30; // var tdim = Math.max(dim, threshold+1); // this._scalefact = (tdim-threshold)/300.0; var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax); // calculate a padded max and min, points should be less than these // so that they aren't too close to the edges of the plot. // User can adjust how much padding is allowed with pad, padMin and PadMax options. // If min or max is set, don't pad that end of axis. var tumin = (this.min != null) ? min : min + range*(this.padMin - 1); var tumax = (this.max != null) ? max : max - range*(this.padMax - 1); // if they're equal, we shouldn't have to do anything, right? // if (min <=tumin || max >= tumax) { if (min tumax) { tumin = (this.min != null) ? min : min - range*(this.padMin - 1); tumax = (this.max != null) ? max : max + range*(this.padMax - 1); ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax); } this.min = ret[0]; this.max = ret[1]; // if numberTicks specified, it should return the same. this.numberTicks = ret[2]; this._autoFormatString = ret[3]; this.tickInterval = ret[4]; } // User has specified some axis scale related option, can use auto algorithm else { // if min and max are same, space them out a bit if (min == max) { var adj = 0.05; if (min > 0) { adj = Math.max(Math.log(min)/Math.LN10, 0.05); } min -= adj; max += adj; } // autoscale. Can't autoscale if min or max is supplied. // Will use numberTicks and tickInterval if supplied. Ticks // across multiple axes may not line up depending on how // bars are to be plotted. if (this.autoscale && this.min == null && this.max == null) { var rrange, ti, margin; var forceMinZero = false; var forceZeroLine = false; var intervals = {min:null, max:null, average:null, stddev:null}; // if any series are bars, or if any are fill to zero, and if this // is the axis to fill toward, check to see if we can start axis at zero. for (var i=0; i vmax) { vmax = vals[j]; } } var dp = (vmax - vmin) / vmax; // is this sries a bar? if (s.renderer.constructor == $.jqplot.BarRenderer) { // if no negative values and could also check range. if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { forceMinZero = true; } else { forceMinZero = false; if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) { forceZeroLine = true; } else { forceZeroLine = false; } } } // if not a bar and filling, use appropriate method. else if (s.fill) { if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { forceMinZero = true; } else if (vmin < 0 && vmax > 0 && s.fillToZero) { forceMinZero = false; forceZeroLine = true; } else { forceMinZero = false; forceZeroLine = false; } } // if not a bar and not filling, only change existing state // if it doesn't make sense else if (vmin < 0) { forceMinZero = false; } } } // check if we need make axis min at 0. if (forceMinZero) { // compute number of ticks this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); this.min = 0; userMin = 0; // what order is this range? // what tick interval does that give us? ti = max/(this.numberTicks-1); temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); if (ti/temp == parseInt(ti/temp, 10)) { ti += temp; } this.tickInterval = Math.ceil(ti/temp) * temp; this.max = this.tickInterval * (this.numberTicks - 1); } // check if we need to make sure there is a tick at 0. else if (forceZeroLine) { // compute number of ticks this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1)); var ntmax = this.numberTicks - 1 - ntmin; ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax)); temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); this.tickInterval = Math.ceil(ti/temp) * temp; this.max = this.tickInterval * ntmax; this.min = -this.tickInterval * ntmin; } // if nothing else, do autoscaling which will try to line up ticks across axes. else { if (this.numberTicks == null){ if (this.tickInterval) { this.numberTicks = 3 + Math.ceil(range / this.tickInterval); } else { this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); } } if (this.tickInterval == null) { // get a tick interval ti = range/(this.numberTicks - 1); if (ti < 1) { temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); } else { temp = 1; } this.tickInterval = Math.ceil(ti*temp*this.pad)/temp; } else { temp = 1 / this.tickInterval; } // try to compute a nicer, more even tick interval // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10)); // this.tickInterval = Math.ceil(ti/temp) * temp; rrange = this.tickInterval * (this.numberTicks - 1); margin = (rrange - range)/2; if (this.min == null) { this.min = Math.floor(temp*(min-margin))/temp; } if (this.max == null) { this.max = this.min + rrange; } } // Compute a somewhat decent format string if it is needed. // get precision of interval and determine a format string. var sf = $.jqplot.getSignificantFigures(this.tickInterval); var fstr; // if we have only a whole number, use integer formatting if (sf.digitsLeft >= sf.significantDigits) { fstr = '%d'; } else { var temp = Math.max(0, 5 - sf.digitsLeft); temp = Math.min(temp, sf.digitsRight); fstr = '%.'+ temp + 'f'; } this._autoFormatString = fstr; } // Use the default algorithm which pads each axis to make the chart // centered nicely on the grid. else { rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1); rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1); range = rmax - rmin; if (this.numberTicks == null){ // if tickInterval is specified by user, we will ignore computed maximum. // max will be equal or greater to fit even # of ticks. if (this.tickInterval != null) { this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1; } else if (dim > 100) { this.numberTicks = parseInt(3+(dim-100)/75, 10); } else { this.numberTicks = 2; } } if (this.tickInterval == null) { this.tickInterval = range / (this.numberTicks-1); } if (this.max == null) { rmax = rmin + this.tickInterval*(this.numberTicks - 1); } if (this.min == null) { rmin = rmax - this.tickInterval*(this.numberTicks - 1); } // get precision of interval and determine a format string. var sf = $.jqplot.getSignificantFigures(this.tickInterval); var fstr; // if we have only a whole number, use integer formatting if (sf.digitsLeft >= sf.significantDigits) { fstr = '%d'; } else { var temp = Math.max(0, 5 - sf.digitsLeft); temp = Math.min(temp, sf.digitsRight); fstr = '%.'+ temp + 'f'; } this._autoFormatString = fstr; this.min = rmin; this.max = rmax; } if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') { // fix for misleading tick display with small range and low precision. range = this.max - this.min; // figure out precision var temptick = new this.tickRenderer(this.tickOptions); // use the tick formatString or, the default. var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; var fs = fs.match($.jqplot.sprintf.regex)[0]; var precision = 0; if (fs) { if (fs.search(/[fFeEgGpP]/) > -1) { var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/); if (m) { precision = parseInt(m[1], 10); } else { precision = 6; } } else if (fs.search(/[di]/) > -1) { precision = 0; } // fact will be <= 1; var fact = Math.pow(10, -precision); if (this.tickInterval < fact) { // need to correct underrange if (userNT == null && userTI == null) { this.tickInterval = fact; if (userMax == null && userMin == null) { // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact; this.min = Math.floor(this._dataBounds.min/fact) * fact; if (this.min == this._dataBounds.min) { this.min = this._dataBounds.min - this.tickInterval; } // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact; this.max = Math.ceil(this._dataBounds.max/fact) * fact; if (this.max == this._dataBounds.max) { this.max = this._dataBounds.max + this.tickInterval; } var n = (this.max - this.min)/this.tickInterval; n = n.toFixed(11); n = Math.ceil(n); this.numberTicks = n + 1; } else if (userMax == null) { // add one tick for top of range. var n = (this._dataBounds.max - this.min) / this.tickInterval; n = n.toFixed(11); this.numberTicks = Math.ceil(n) + 2; this.max = this.min + this.tickInterval * (this.numberTicks-1); } else if (userMin == null) { // add one tick for bottom of range. var n = (this.max - this._dataBounds.min) / this.tickInterval; n = n.toFixed(11); this.numberTicks = Math.ceil(n) + 2; this.min = this.max - this.tickInterval * (this.numberTicks-1); } else { // calculate a number of ticks so max is within axis scale this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1; // if user's min and max don't fit evenly in ticks, adjust. // This takes care of cases such as user min set to 0, max set to 3.5 but tick // format string set to %d (integer ticks) this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision); this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision); // this.max = this.min + this.tickInterval*(this.numberTicks-1); this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1; } } } } } } if (this._overrideFormatString && this._autoFormatString != '') { this.tickOptions = this.tickOptions || {}; this.tickOptions.formatString = this._autoFormatString; } var t, to; for (var i=0; i plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr); // $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) { if ($.isArray(opts) && opts.length == this._ticks.length) { var t; for (var i=0; i this.breakPoints[0] && u < this.breakPoints[1]){ u = this.breakPoints[0]; } if (u <= this.breakPoints[0]) { return (u - min) * pixellength / unitlength + offmin; } else { return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin; } }; if (this.name.charAt(0) == 'x'){ this.series_u2p = function(u){ if (u > this.breakPoints[0] && u < this.breakPoints[1]){ u = this.breakPoints[0]; } if (u <= this.breakPoints[0]) { return (u - min) * pixellength / unitlength; } else { return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength; } }; this.series_p2u = function(p){ return p * unitlength / pixellength + min; }; } else { this.series_u2p = function(u){ if (u > this.breakPoints[0] && u < this.breakPoints[1]){ u = this.breakPoints[0]; } if (u >= this.breakPoints[1]) { return (u - max) * pixellength / unitlength; } else { return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength; } }; this.series_p2u = function(p){ return p * unitlength / pixellength + max; }; } } else { this.p2u = function(p){ return (p - offmin) * unitlength / pixellength + min; }; this.u2p = function(u){ return (u - min) * pixellength / unitlength + offmin; }; if (this.name == 'xaxis' || this.name == 'x2axis'){ this.series_u2p = function(u){ return (u - min) * pixellength / unitlength; }; this.series_p2u = function(p){ return p * unitlength / pixellength + min; }; } else { this.series_u2p = function(u){ return (u - max) * pixellength / unitlength; }; this.series_p2u = function(p){ return p * unitlength / pixellength + max; }; } } if (this.show) { if (this.name == 'xaxis' || this.name == 'x2axis') { for (var i=0; i 0) { shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; } else { shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; } break; case 'middle': // if (t.angle > 0) { // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; // } // else { // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; // } shim = -t.getHeight()/2; break; default: shim = -t.getHeight()/2; break; } } else { shim = -t.getHeight()/2; } var val = this.u2p(t.value) + shim + 'px'; t._elem.css('top', val); t.pack(); } } if (lshow) { var h = this._label._elem.outerHeight(true); this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px'); if (this.name == 'yaxis') { this._label._elem.css('left', '0px'); } else { this._label._elem.css('right', '0px'); } this._label.pack(); } } } ticks = null; }; })(jQuery);