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.
299 lines
9.6 KiB
299 lines
9.6 KiB
2 years ago
|
/* Pretty handling of log axes.
|
||
|
|
||
|
Copyright (c) 2007-2014 IOLA and Ole Laursen.
|
||
|
Copyright (c) 2015 Ciprian Ceteras cipix2000@gmail.com.
|
||
|
Copyright (c) 2017 Raluca Portase
|
||
|
Licensed under the MIT license.
|
||
|
|
||
|
Set axis.mode to "log" to enable.
|
||
|
*/
|
||
|
|
||
|
/* global jQuery*/
|
||
|
|
||
|
/**
|
||
|
## jquery.flot.logaxis
|
||
|
This plugin is used to create logarithmic axis. This includes tick generation,
|
||
|
formatters and transformers to and from logarithmic representation.
|
||
|
|
||
|
### Methods and hooks
|
||
|
*/
|
||
|
|
||
|
(function ($) {
|
||
|
'use strict';
|
||
|
|
||
|
var options = {
|
||
|
xaxis: {}
|
||
|
};
|
||
|
|
||
|
/*tick generators and formatters*/
|
||
|
var PREFERRED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 10),
|
||
|
EXTENDED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 4);
|
||
|
|
||
|
function computePreferedLogTickValues(endLimit, rangeStep) {
|
||
|
var log10End = Math.floor(Math.log(endLimit) * Math.LOG10E) - 1,
|
||
|
log10Start = -log10End,
|
||
|
val, range, vals = [];
|
||
|
|
||
|
for (var power = log10Start; power <= log10End; power++) {
|
||
|
range = parseFloat('1e' + power);
|
||
|
for (var mult = 1; mult < 9; mult += rangeStep) {
|
||
|
val = range * mult;
|
||
|
vals.push(val);
|
||
|
}
|
||
|
}
|
||
|
return vals;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
- logTickGenerator(plot, axis, noTicks)
|
||
|
|
||
|
Generates logarithmic ticks, depending on axis range.
|
||
|
In case the number of ticks that can be generated is less than the expected noTicks/4,
|
||
|
a linear tick generation is used.
|
||
|
*/
|
||
|
var logTickGenerator = function (plot, axis, noTicks) {
|
||
|
var ticks = [],
|
||
|
minIdx = -1,
|
||
|
maxIdx = -1,
|
||
|
surface = plot.getCanvas(),
|
||
|
logTickValues = PREFERRED_LOG_TICK_VALUES,
|
||
|
min = clampAxis(axis, plot),
|
||
|
max = axis.max;
|
||
|
|
||
|
if (!noTicks) {
|
||
|
noTicks = 0.3 * Math.sqrt(axis.direction === "x" ? surface.width : surface.height);
|
||
|
}
|
||
|
|
||
|
PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
|
||
|
if (val >= min) {
|
||
|
minIdx = i;
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
|
||
|
if (val >= max) {
|
||
|
maxIdx = i;
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (maxIdx === -1) {
|
||
|
maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;
|
||
|
}
|
||
|
|
||
|
if (maxIdx - minIdx <= noTicks / 4 && logTickValues.length !== EXTENDED_LOG_TICK_VALUES.length) {
|
||
|
//try with multiple of 5 for tick values
|
||
|
logTickValues = EXTENDED_LOG_TICK_VALUES;
|
||
|
minIdx *= 2;
|
||
|
maxIdx *= 2;
|
||
|
}
|
||
|
|
||
|
var lastDisplayed = null,
|
||
|
inverseNoTicks = 1 / noTicks,
|
||
|
tickValue, pixelCoord, tick;
|
||
|
|
||
|
// Count the number of tick values would appear, if we can get at least
|
||
|
// nTicks / 4 accept them.
|
||
|
if (maxIdx - minIdx >= noTicks / 4) {
|
||
|
for (var idx = maxIdx; idx >= minIdx; idx--) {
|
||
|
tickValue = logTickValues[idx];
|
||
|
pixelCoord = (Math.log(tickValue) - Math.log(min)) / (Math.log(max) - Math.log(min));
|
||
|
tick = tickValue;
|
||
|
|
||
|
if (lastDisplayed === null) {
|
||
|
lastDisplayed = {
|
||
|
pixelCoord: pixelCoord,
|
||
|
idealPixelCoord: pixelCoord
|
||
|
};
|
||
|
} else {
|
||
|
if (Math.abs(pixelCoord - lastDisplayed.pixelCoord) >= inverseNoTicks) {
|
||
|
lastDisplayed = {
|
||
|
pixelCoord: pixelCoord,
|
||
|
idealPixelCoord: lastDisplayed.idealPixelCoord - inverseNoTicks
|
||
|
};
|
||
|
} else {
|
||
|
tick = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (tick) {
|
||
|
ticks.push(tick);
|
||
|
}
|
||
|
}
|
||
|
// Since we went in backwards order.
|
||
|
ticks.reverse();
|
||
|
} else {
|
||
|
var tickSize = plot.computeTickSize(min, max, noTicks),
|
||
|
customAxis = {min: min, max: max, tickSize: tickSize};
|
||
|
ticks = $.plot.linearTickGenerator(customAxis);
|
||
|
}
|
||
|
|
||
|
return ticks;
|
||
|
};
|
||
|
|
||
|
var clampAxis = function (axis, plot) {
|
||
|
var min = axis.min,
|
||
|
max = axis.max;
|
||
|
|
||
|
if (min <= 0) {
|
||
|
//for empty graph if axis.min is not strictly positive make it 0.1
|
||
|
if (axis.datamin === null) {
|
||
|
min = axis.min = 0.1;
|
||
|
} else {
|
||
|
min = processAxisOffset(plot, axis);
|
||
|
}
|
||
|
|
||
|
if (max < min) {
|
||
|
axis.max = axis.datamax !== null ? axis.datamax : axis.options.max;
|
||
|
axis.options.offset.below = 0;
|
||
|
axis.options.offset.above = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return min;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
- logTickFormatter(value, axis, precision)
|
||
|
|
||
|
This is the corresponding tickFormatter of the logaxis.
|
||
|
For a number greater that 10^6 or smaller than 10^(-3), this will be drawn
|
||
|
with e representation
|
||
|
*/
|
||
|
var logTickFormatter = function (value, axis, precision) {
|
||
|
var tenExponent = value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0;
|
||
|
|
||
|
if (precision) {
|
||
|
if ((tenExponent >= -4) && (tenExponent <= 7)) {
|
||
|
return $.plot.defaultTickFormatter(value, axis, precision);
|
||
|
} else {
|
||
|
return $.plot.expRepTickFormatter(value, axis, precision);
|
||
|
}
|
||
|
}
|
||
|
if ((tenExponent >= -4) && (tenExponent <= 7)) {
|
||
|
//if we have float numbers, return a limited length string(ex: 0.0009 is represented as 0.000900001)
|
||
|
var formattedValue = tenExponent < 0 ? value.toFixed(-tenExponent) : value.toFixed(tenExponent + 2);
|
||
|
if (formattedValue.indexOf('.') !== -1) {
|
||
|
var lastZero = formattedValue.lastIndexOf('0');
|
||
|
|
||
|
while (lastZero === formattedValue.length - 1) {
|
||
|
formattedValue = formattedValue.slice(0, -1);
|
||
|
lastZero = formattedValue.lastIndexOf('0');
|
||
|
}
|
||
|
|
||
|
//delete the dot if is last
|
||
|
if (formattedValue.indexOf('.') === formattedValue.length - 1) {
|
||
|
formattedValue = formattedValue.slice(0, -1);
|
||
|
}
|
||
|
}
|
||
|
return formattedValue;
|
||
|
} else {
|
||
|
return $.plot.expRepTickFormatter(value, axis);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/*logaxis caracteristic functions*/
|
||
|
var logTransform = function (v) {
|
||
|
if (v < PREFERRED_LOG_TICK_VALUES[0]) {
|
||
|
v = PREFERRED_LOG_TICK_VALUES[0];
|
||
|
}
|
||
|
|
||
|
return Math.log(v);
|
||
|
};
|
||
|
|
||
|
var logInverseTransform = function (v) {
|
||
|
return Math.exp(v);
|
||
|
};
|
||
|
|
||
|
var invertedTransform = function (v) {
|
||
|
return -v;
|
||
|
}
|
||
|
|
||
|
var invertedLogTransform = function (v) {
|
||
|
return -logTransform(v);
|
||
|
}
|
||
|
|
||
|
var invertedLogInverseTransform = function (v) {
|
||
|
return logInverseTransform(-v);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
- setDataminRange(plot, axis)
|
||
|
|
||
|
It is used for clamping the starting point of a logarithmic axis.
|
||
|
This will set the axis datamin range to 0.1 or to the first datapoint greater then 0.
|
||
|
The function is usefull since the logarithmic representation can not show
|
||
|
values less than or equal to 0.
|
||
|
*/
|
||
|
function setDataminRange(plot, axis) {
|
||
|
if (axis.options.mode === 'log' && axis.datamin <= 0) {
|
||
|
if (axis.datamin === null) {
|
||
|
axis.datamin = 0.1;
|
||
|
} else {
|
||
|
axis.datamin = processAxisOffset(plot, axis);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function processAxisOffset(plot, axis) {
|
||
|
var series = plot.getData(),
|
||
|
range = series
|
||
|
.filter(function(series) {
|
||
|
return series.xaxis === axis || series.yaxis === axis;
|
||
|
})
|
||
|
.map(function(series) {
|
||
|
return plot.computeRangeForDataSeries(series, null, isValid);
|
||
|
}),
|
||
|
min = axis.direction === 'x'
|
||
|
? Math.min(0.1, range && range[0] ? range[0].xmin : 0.1)
|
||
|
: Math.min(0.1, range && range[0] ? range[0].ymin : 0.1);
|
||
|
|
||
|
axis.min = min;
|
||
|
|
||
|
return min;
|
||
|
}
|
||
|
|
||
|
function isValid(a) {
|
||
|
return a > 0;
|
||
|
}
|
||
|
|
||
|
function init(plot) {
|
||
|
plot.hooks.processOptions.push(function (plot) {
|
||
|
$.each(plot.getAxes(), function (axisName, axis) {
|
||
|
var opts = axis.options;
|
||
|
if (opts.mode === 'log') {
|
||
|
axis.tickGenerator = function (axis) {
|
||
|
var noTicks = 11;
|
||
|
return logTickGenerator(plot, axis, noTicks);
|
||
|
};
|
||
|
if (typeof axis.options.tickFormatter !== 'function') {
|
||
|
axis.options.tickFormatter = logTickFormatter;
|
||
|
}
|
||
|
axis.options.transform = opts.inverted ? invertedLogTransform : logTransform;
|
||
|
axis.options.inverseTransform = opts.inverted ? invertedLogInverseTransform : logInverseTransform;
|
||
|
axis.options.autoScaleMargin = 0;
|
||
|
plot.hooks.setRange.push(setDataminRange);
|
||
|
} else if (opts.inverted) {
|
||
|
axis.options.transform = invertedTransform;
|
||
|
axis.options.inverseTransform = invertedTransform;
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
$.plot.plugins.push({
|
||
|
init: init,
|
||
|
options: options,
|
||
|
name: 'log',
|
||
|
version: '0.1'
|
||
|
});
|
||
|
|
||
|
$.plot.logTicksGenerator = logTickGenerator;
|
||
|
$.plot.logTickFormatter = logTickFormatter;
|
||
|
})(jQuery);
|