odoo/addons/web_graph/static/lib/flotr2/js/Axis.js

304 lines
8.7 KiB
JavaScript

/**
* Flotr Axis Library
*/
(function () {
var
_ = Flotr._,
LOGARITHMIC = 'logarithmic';
function Axis (o) {
this.orientation = 1;
this.offset = 0;
this.datamin = Number.MAX_VALUE;
this.datamax = -Number.MAX_VALUE;
_.extend(this, o);
this._setTranslations();
}
// Prototype
Axis.prototype = {
setScale : function () {
var length = this.length;
if (this.options.scaling == LOGARITHMIC) {
this.scale = length / (log(this.max, this.options.base) - log(this.min, this.options.base));
} else {
this.scale = length / (this.max - this.min);
}
},
calculateTicks : function () {
var options = this.options;
this.ticks = [];
this.minorTicks = [];
// User Ticks
if(options.ticks){
this._cleanUserTicks(options.ticks, this.ticks);
this._cleanUserTicks(options.minorTicks || [], this.minorTicks);
}
else {
if (options.mode == 'time') {
this._calculateTimeTicks();
} else if (options.scaling === 'logarithmic') {
this._calculateLogTicks();
} else {
this._calculateTicks();
}
}
},
/**
* Calculates the range of an axis to apply autoscaling.
*/
calculateRange: function () {
if (!this.used) return;
var axis = this,
o = axis.options,
min = o.min !== null ? o.min : axis.datamin,
max = o.max !== null ? o.max : axis.datamax,
margin = o.autoscaleMargin;
if (o.scaling == 'logarithmic') {
if (min <= 0) min = axis.datamin;
// Let it widen later on
if (max <= 0) max = min;
}
if (max == min) {
var widen = max ? 0.01 : 1.00;
if (o.min === null) min -= widen;
if (o.max === null) max += widen;
}
if (o.scaling === 'logarithmic') {
if (min < 0) min = max / o.base; // Could be the result of widening
var maxexp = Math.log(max);
if (o.base != Math.E) maxexp /= Math.log(o.base);
maxexp = Math.ceil(maxexp);
var minexp = Math.log(min);
if (o.base != Math.E) minexp /= Math.log(o.base);
minexp = Math.ceil(minexp);
axis.tickSize = Flotr.getTickSize(o.noTicks, minexp, maxexp, o.tickDecimals === null ? 0 : o.tickDecimals);
// Try to determine a suitable amount of miniticks based on the length of a decade
if (o.minorTickFreq === null) {
if (maxexp - minexp > 10)
o.minorTickFreq = 0;
else if (maxexp - minexp > 5)
o.minorTickFreq = 2;
else
o.minorTickFreq = 5;
}
} else {
axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
}
axis.min = min;
axis.max = max; //extendRange may use axis.min or axis.max, so it should be set before it is caled
// Autoscaling. @todo This probably fails with log scale. Find a testcase and fix it
if(o.min === null && o.autoscale){
axis.min -= axis.tickSize * margin;
// Make sure we don't go below zero if all values are positive.
if(axis.min < 0 && axis.datamin >= 0) axis.min = 0;
axis.min = axis.tickSize * Math.floor(axis.min / axis.tickSize);
}
if(o.max === null && o.autoscale){
axis.max += axis.tickSize * margin;
if(axis.max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) axis.max = 0;
axis.max = axis.tickSize * Math.ceil(axis.max / axis.tickSize);
}
if (axis.min == axis.max) axis.max = axis.min + 1;
},
calculateTextDimensions : function (T, options) {
var maxLabel = '',
length,
i;
if (this.options.showLabels) {
for (i = 0; i < this.ticks.length; ++i) {
length = this.ticks[i].label.length;
if (length > maxLabel.length){
maxLabel = this.ticks[i].label;
}
}
}
this.maxLabel = T.dimensions(
maxLabel,
{size:options.fontSize, angle: Flotr.toRad(this.options.labelsAngle)},
'font-size:smaller;',
'flotr-grid-label'
);
this.titleSize = T.dimensions(
this.options.title,
{size:options.fontSize*1.2, angle: Flotr.toRad(this.options.titleAngle)},
'font-weight:bold;',
'flotr-axis-title'
);
},
_cleanUserTicks : function (ticks, axisTicks) {
var axis = this, options = this.options,
v, i, label, tick;
if(_.isFunction(ticks)) ticks = ticks({min : axis.min, max : axis.max});
for(i = 0; i < ticks.length; ++i){
tick = ticks[i];
if(typeof(tick) === 'object'){
v = tick[0];
label = (tick.length > 1) ? tick[1] : options.tickFormatter(v, {min : axis.min, max : axis.max});
} else {
v = tick;
label = options.tickFormatter(v, {min : this.min, max : this.max});
}
axisTicks[i] = { v: v, label: label };
}
},
_calculateTimeTicks : function () {
this.ticks = Flotr.Date.generator(this);
},
_calculateLogTicks : function () {
var axis = this,
o = axis.options,
v,
decadeStart;
var max = Math.log(axis.max);
if (o.base != Math.E) max /= Math.log(o.base);
max = Math.ceil(max);
var min = Math.log(axis.min);
if (o.base != Math.E) min /= Math.log(o.base);
min = Math.ceil(min);
for (i = min; i < max; i += axis.tickSize) {
decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
// Next decade begins here:
var decadeEnd = decadeStart * ((o.base == Math.E) ? Math.exp(axis.tickSize) : Math.pow(o.base, axis.tickSize));
var stepSize = (decadeEnd - decadeStart) / o.minorTickFreq;
axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
for (v = decadeStart + stepSize; v < decadeEnd; v += stepSize)
axis.minorTicks.push({v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max})});
}
// Always show the value at the would-be start of next decade (end of this decade)
decadeStart = (o.base == Math.E) ? Math.exp(i) : Math.pow(o.base, i);
axis.ticks.push({v: decadeStart, label: o.tickFormatter(decadeStart, {min : axis.min, max : axis.max})});
},
_calculateTicks : function () {
var axis = this,
o = axis.options,
tickSize = axis.tickSize,
min = axis.min,
max = axis.max,
start = tickSize * Math.ceil(min / tickSize), // Round to nearest multiple of tick size.
decimals,
minorTickSize,
v, v2,
i, j;
if (o.minorTickFreq)
minorTickSize = tickSize / o.minorTickFreq;
// Then store all possible ticks.
for (i = 0; (v = v2 = start + i * tickSize) <= max; ++i){
// Round (this is always needed to fix numerical instability).
decimals = o.tickDecimals;
if (decimals === null) decimals = 1 - Math.floor(Math.log(tickSize) / Math.LN10);
if (decimals < 0) decimals = 0;
v = v.toFixed(decimals);
axis.ticks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
if (o.minorTickFreq) {
for (j = 0; j < o.minorTickFreq && (i * tickSize + j * minorTickSize) < max; ++j) {
v = v2 + j * minorTickSize;
axis.minorTicks.push({ v: v, label: o.tickFormatter(v, {min : axis.min, max : axis.max}) });
}
}
}
},
_setTranslations : function (logarithmic) {
this.d2p = (logarithmic ? d2pLog : d2p);
this.p2d = (logarithmic ? p2dLog : p2d);
}
};
// Static Methods
_.extend(Axis, {
getAxes : function (options) {
return {
x: new Axis({options: options.xaxis, n: 1, length: this.plotWidth}),
x2: new Axis({options: options.x2axis, n: 2, length: this.plotWidth}),
y: new Axis({options: options.yaxis, n: 1, length: this.plotHeight, offset: this.plotHeight, orientation: -1}),
y2: new Axis({options: options.y2axis, n: 2, length: this.plotHeight, offset: this.plotHeight, orientation: -1})
};
}
});
// Helper Methods
function d2p (dataValue) {
return this.offset + this.orientation * (dataValue - this.min) * this.scale;
}
function p2d (pointValue) {
return (this.offset + this.orientation * pointValue) / this.scale + this.min;
}
function d2pLog (dataValue) {
return this.offset + this.orientation * (log(dataValue, this.options.base) - log(this.min, this.options.base)) * this.scale;
}
function p2dLog (pointValue) {
return exp((this.offset + this.orientation * pointValue) / this.scale + log(this.min, this.options.base), this.options.base);
}
function log (value, base) {
value = Math.log(Math.max(value, Number.MIN_VALUE));
if (base !== Math.E)
value /= Math.log(base);
return value;
}
function exp (value, base) {
return (base === Math.E) ? Math.exp(value) : Math.pow(base, value);
}
Flotr.Axis = Axis;
})();