You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
393 lines
14 KiB
393 lines
14 KiB
/**
|
|
* 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($) {
|
|
/**
|
|
* The following code was generaously given to me a while back by Scott Prahl.
|
|
* He did a good job at computing axes min, max and number of ticks for the
|
|
* case where the user has not set any scale related parameters (tickInterval,
|
|
* numberTicks, min or max). I had ignored this use case for a long time,
|
|
* focusing on the more difficult case where user has set some option controlling
|
|
* tick generation. Anyway, about time I got this into jqPlot.
|
|
* Thanks Scott!!
|
|
*/
|
|
|
|
/**
|
|
* Copyright (c) 2010 Scott Prahl
|
|
* The next three routines are currently available for use in all personal
|
|
* or commercial projects under both the MIT and GPL version 2.0 licenses.
|
|
* This means that you can choose the license that best suits your project
|
|
* and use it accordingly.
|
|
*/
|
|
|
|
// A good format string depends on the interval. If the interval is greater
|
|
// than 1 then there is no need to show any decimal digits. If it is < 1.0, then
|
|
// use the magnitude of the interval to determine the number of digits to show.
|
|
function bestFormatString (interval)
|
|
{
|
|
var fstr;
|
|
interval = Math.abs(interval);
|
|
if (interval >= 10) {
|
|
fstr = '%d';
|
|
}
|
|
|
|
else if (interval > 1) {
|
|
if (interval === parseInt(interval, 10)) {
|
|
fstr = '%d';
|
|
}
|
|
else {
|
|
fstr = '%.1f';
|
|
}
|
|
}
|
|
|
|
else {
|
|
var expv = -Math.floor(Math.log(interval)/Math.LN10);
|
|
fstr = '%.' + expv + 'f';
|
|
}
|
|
|
|
return fstr;
|
|
}
|
|
|
|
var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
|
|
|
|
var _getLowerFactor = function(f) {
|
|
var i = _factors.indexOf(f);
|
|
if (i > 0) {
|
|
return _factors[i-1];
|
|
}
|
|
else {
|
|
return _factors[_factors.length - 1] / 100;
|
|
}
|
|
};
|
|
|
|
var _getHigherFactor = function(f) {
|
|
var i = _factors.indexOf(f);
|
|
if (i < _factors.length-1) {
|
|
return _factors[i+1];
|
|
}
|
|
else {
|
|
return _factors[0] * 100;
|
|
}
|
|
};
|
|
|
|
// Given a fixed minimum and maximum and a target number ot ticks
|
|
// figure out the best interval and
|
|
// return min, max, number ticks, format string and tick interval
|
|
function bestConstrainedInterval(min, max, nttarget) {
|
|
// run through possible number to ticks and see which interval is best
|
|
var low = Math.floor(nttarget/2);
|
|
var hi = Math.ceil(nttarget*1.5);
|
|
var badness = Number.MAX_VALUE;
|
|
var r = (max - min);
|
|
var temp;
|
|
var sd;
|
|
var bestNT;
|
|
var gsf = $.jqplot.getSignificantFigures;
|
|
var fsd;
|
|
var fs;
|
|
var currentNT;
|
|
var bestPrec;
|
|
|
|
for (var i=0, l=hi-low+1; i<l; i++) {
|
|
currentNT = low + i;
|
|
temp = r/(currentNT-1);
|
|
sd = gsf(temp);
|
|
|
|
temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
|
|
if (temp < badness) {
|
|
badness = temp;
|
|
bestNT = currentNT;
|
|
bestPrec = sd.digitsRight;
|
|
}
|
|
else if (temp === badness) {
|
|
// let nicer ticks trump number ot ticks
|
|
if (sd.digitsRight < bestPrec) {
|
|
bestNT = currentNT;
|
|
bestPrec = sd.digitsRight;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
|
|
if (fsd === 0) {
|
|
fs = '%d';
|
|
}
|
|
else {
|
|
fs = '%.' + fsd + 'f';
|
|
}
|
|
temp = r / (bestNT - 1);
|
|
// min, max, number ticks, format string, tick interval
|
|
return [min, max, bestNT, fs, temp];
|
|
}
|
|
|
|
// This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
|
|
// it is based soley on the range and number of ticks. So if user specifies
|
|
// number of ticks, use this.
|
|
function bestInterval(range, numberTicks) {
|
|
numberTicks = numberTicks || 7;
|
|
var minimum = range / (numberTicks - 1);
|
|
var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
|
|
var residual = minimum / magnitude;
|
|
var interval;
|
|
// "nicest" ranges are 1, 2, 5 or powers of these.
|
|
// for magnitudes below 1, only allow these.
|
|
if (magnitude < 1) {
|
|
if (residual > 5) {
|
|
interval = 10 * magnitude;
|
|
}
|
|
else if (residual > 2) {
|
|
interval = 5 * magnitude;
|
|
}
|
|
else if (residual > 1) {
|
|
interval = 2 * magnitude;
|
|
}
|
|
else {
|
|
interval = magnitude;
|
|
}
|
|
}
|
|
// for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
|
|
// this helps a lot with poor choices for number of ticks.
|
|
else {
|
|
if (residual > 5) {
|
|
interval = 10 * magnitude;
|
|
}
|
|
else if (residual > 4) {
|
|
interval = 5 * magnitude;
|
|
}
|
|
else if (residual > 3) {
|
|
interval = 4 * magnitude;
|
|
}
|
|
else if (residual > 2) {
|
|
interval = 3 * magnitude;
|
|
}
|
|
else if (residual > 1) {
|
|
interval = 2 * magnitude;
|
|
}
|
|
else {
|
|
interval = magnitude;
|
|
}
|
|
}
|
|
|
|
return interval;
|
|
}
|
|
|
|
// This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
|
|
// it is based soley on the range of data, number of ticks must be computed later.
|
|
function bestLinearInterval(range, scalefact) {
|
|
scalefact = scalefact || 1;
|
|
var expv = Math.floor(Math.log(range)/Math.LN10);
|
|
var magnitude = Math.pow(10, expv);
|
|
// 0 < f < 10
|
|
var f = range / magnitude;
|
|
var fact;
|
|
// for large plots, scalefact will decrease f and increase number of ticks.
|
|
// for small plots, scalefact will increase f and decrease number of ticks.
|
|
f = f/scalefact;
|
|
|
|
// for large plots, smaller interval, more ticks.
|
|
if (f<=0.38) {
|
|
fact = 0.1;
|
|
}
|
|
else if (f<=1.6) {
|
|
fact = 0.2;
|
|
}
|
|
else if (f<=4.0) {
|
|
fact = 0.5;
|
|
}
|
|
else if (f<=8.0) {
|
|
fact = 1.0;
|
|
}
|
|
// for very small plots, larger interval, less ticks in number ticks
|
|
else if (f<=16.0) {
|
|
fact = 2;
|
|
}
|
|
else {
|
|
fact = 5;
|
|
}
|
|
|
|
return fact*magnitude;
|
|
}
|
|
|
|
function bestLinearComponents(range, scalefact) {
|
|
var expv = Math.floor(Math.log(range)/Math.LN10);
|
|
var magnitude = Math.pow(10, expv);
|
|
// 0 < f < 10
|
|
var f = range / magnitude;
|
|
var interval;
|
|
var fact;
|
|
// for large plots, scalefact will decrease f and increase number of ticks.
|
|
// for small plots, scalefact will increase f and decrease number of ticks.
|
|
f = f/scalefact;
|
|
|
|
// for large plots, smaller interval, more ticks.
|
|
if (f<=0.38) {
|
|
fact = 0.1;
|
|
}
|
|
else if (f<=1.6) {
|
|
fact = 0.2;
|
|
}
|
|
else if (f<=4.0) {
|
|
fact = 0.5;
|
|
}
|
|
else if (f<=8.0) {
|
|
fact = 1.0;
|
|
}
|
|
// for very small plots, larger interval, less ticks in number ticks
|
|
else if (f<=16.0) {
|
|
fact = 2;
|
|
}
|
|
// else if (f<=20.0) {
|
|
// fact = 3;
|
|
// }
|
|
// else if (f<=24.0) {
|
|
// fact = 4;
|
|
// }
|
|
else {
|
|
fact = 5;
|
|
}
|
|
|
|
interval = fact * magnitude;
|
|
|
|
return [interval, fact, magnitude];
|
|
}
|
|
|
|
// Given the min and max for a dataset, return suitable endpoints
|
|
// for the graphing, a good number for the number of ticks, and a
|
|
// format string so that extraneous digits are not displayed.
|
|
// returned is an array containing [min, max, nTicks, format]
|
|
$.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
|
|
// Set to preserve EITHER min OR max.
|
|
// If min is preserved, max must be free.
|
|
keepMin = (keepMin === null) ? false : keepMin;
|
|
keepMax = (keepMax === null || keepMin) ? false : keepMax;
|
|
// if endpoints are equal try to include zero otherwise include one
|
|
if (axis_min === axis_max) {
|
|
axis_max = (axis_max) ? 0 : 1;
|
|
}
|
|
|
|
scalefact = scalefact || 1.0;
|
|
|
|
// make sure range is positive
|
|
if (axis_max < axis_min) {
|
|
var a = axis_max;
|
|
axis_max = axis_min;
|
|
axis_min = a;
|
|
}
|
|
|
|
var r = [];
|
|
var ss = bestLinearInterval(axis_max - axis_min, scalefact);
|
|
|
|
var gsf = $.jqplot.getSignificantFigures;
|
|
|
|
if (numberTicks == null) {
|
|
|
|
// Figure out the axis min, max and number of ticks
|
|
// the min and max will be some multiple of the tick interval,
|
|
// 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
|
|
// axis min is negative, 0 will be a tick.
|
|
if (!keepMin && !keepMax) {
|
|
r[0] = Math.floor(axis_min / ss) * ss; // min
|
|
r[1] = Math.ceil(axis_max / ss) * ss; // max
|
|
r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks
|
|
r[3] = bestFormatString(ss); // format string
|
|
r[4] = ss; // tick Interval
|
|
}
|
|
|
|
else if (keepMin) {
|
|
r[0] = axis_min; // min
|
|
r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
|
|
r[1] = axis_min + (r[2] - 1) * ss; // max
|
|
var digitsMin = gsf(axis_min).digitsRight;
|
|
var digitsSS = gsf(ss).digitsRight;
|
|
if (digitsMin < digitsSS) {
|
|
r[3] = bestFormatString(ss); // format string
|
|
}
|
|
else {
|
|
r[3] = '%.' + digitsMin + 'f';
|
|
}
|
|
r[4] = ss; // tick Interval
|
|
}
|
|
|
|
else if (keepMax) {
|
|
r[1] = axis_max; // max
|
|
r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
|
|
r[0] = axis_max - (r[2] - 1) * ss; // min
|
|
var digitsMax = gsf(axis_max).digitsRight;
|
|
var digitsSS = gsf(ss).digitsRight;
|
|
if (digitsMax < digitsSS) {
|
|
r[3] = bestFormatString(ss); // format string
|
|
}
|
|
else {
|
|
r[3] = '%.' + digitsMax + 'f';
|
|
}
|
|
r[4] = ss; // tick Interval
|
|
}
|
|
}
|
|
|
|
else {
|
|
var tempr = [];
|
|
|
|
// Figure out the axis min, max and number of ticks
|
|
// the min and max will be some multiple of the tick interval,
|
|
// 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
|
|
// axis min is negative, 0 will be a tick.
|
|
tempr[0] = Math.floor(axis_min / ss) * ss; // min
|
|
tempr[1] = Math.ceil(axis_max / ss) * ss; // max
|
|
tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks
|
|
tempr[3] = bestFormatString(ss); // format string
|
|
tempr[4] = ss; // tick Interval
|
|
|
|
// first, see if we happen to get the right number of ticks
|
|
if (tempr[2] === numberTicks) {
|
|
r = tempr;
|
|
}
|
|
|
|
else {
|
|
|
|
var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
|
|
|
|
r[0] = tempr[0]; // min
|
|
r[2] = numberTicks; // number of ticks
|
|
r[4] = newti; // tick interval
|
|
r[3] = bestFormatString(newti); // format string
|
|
r[1] = r[0] + (r[2] - 1) * r[4]; // max
|
|
}
|
|
}
|
|
|
|
return r;
|
|
};
|
|
|
|
$.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
|
|
$.jqplot.LinearTickGenerator.bestInterval = bestInterval;
|
|
$.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
|
|
$.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
|
|
|
|
})(jQuery); |