mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 12:19:08 +02:00
Fix log scale when value is 0 (#4913)
This commit is contained in:
parent
d415e617d8
commit
939756c260
@ -177,64 +177,94 @@ module.exports = function(Chart) {
|
||||
getPixelForTick: function(index) {
|
||||
return this.getPixelForValue(this.tickValues[index]);
|
||||
},
|
||||
/**
|
||||
* Returns the value of the first tick.
|
||||
* @param {Number} value - The minimum not zero value.
|
||||
* @return {Number} The first tick value.
|
||||
* @private
|
||||
*/
|
||||
_getFirstTickValue: function(value) {
|
||||
var exp = Math.floor(helpers.log10(value));
|
||||
var significand = Math.floor(value / Math.pow(10, exp));
|
||||
|
||||
return significand * Math.pow(10, exp);
|
||||
},
|
||||
getPixelForValue: function(value) {
|
||||
var me = this;
|
||||
var start = me.start;
|
||||
var newVal = +me.getRightValue(value);
|
||||
var opts = me.options;
|
||||
var tickOpts = opts.ticks;
|
||||
var innerDimension, pixel, range;
|
||||
var reverse = me.options.ticks.reverse;
|
||||
var log10 = helpers.log10;
|
||||
var firstTickValue = me._getFirstTickValue(me.minNotZero);
|
||||
var offset = 0;
|
||||
var innerDimension, pixel, start, end, sign;
|
||||
|
||||
value = +me.getRightValue(value);
|
||||
if (reverse) {
|
||||
start = me.end;
|
||||
end = me.start;
|
||||
sign = -1;
|
||||
} else {
|
||||
start = me.start;
|
||||
end = me.end;
|
||||
sign = 1;
|
||||
}
|
||||
if (me.isHorizontal()) {
|
||||
range = helpers.log10(me.end) - helpers.log10(start); // todo: if start === 0
|
||||
if (newVal === 0) {
|
||||
pixel = me.left;
|
||||
} else {
|
||||
innerDimension = me.width;
|
||||
pixel = me.left + (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
|
||||
}
|
||||
pixel = reverse ? me.right : me.left;
|
||||
} else {
|
||||
// Bottom - top since pixels increase downward on a screen
|
||||
innerDimension = me.height;
|
||||
if (start === 0 && !tickOpts.reverse) {
|
||||
range = helpers.log10(me.end) - helpers.log10(me.minNotZero);
|
||||
if (newVal === start) {
|
||||
pixel = me.bottom;
|
||||
} else if (newVal === me.minNotZero) {
|
||||
pixel = me.bottom - innerDimension * 0.02;
|
||||
} else {
|
||||
pixel = me.bottom - innerDimension * 0.02 - (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
|
||||
sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
|
||||
pixel = reverse ? me.top : me.bottom;
|
||||
}
|
||||
} else if (me.end === 0 && tickOpts.reverse) {
|
||||
range = helpers.log10(me.start) - helpers.log10(me.minNotZero);
|
||||
if (newVal === me.end) {
|
||||
pixel = me.top;
|
||||
} else if (newVal === me.minNotZero) {
|
||||
pixel = me.top + innerDimension * 0.02;
|
||||
} else {
|
||||
pixel = me.top + innerDimension * 0.02 + (innerDimension * 0.98 / range * (helpers.log10(newVal) - helpers.log10(me.minNotZero)));
|
||||
if (value !== start) {
|
||||
if (start === 0) { // include zero tick
|
||||
offset = helpers.getValueOrDefault(
|
||||
me.options.ticks.fontSize,
|
||||
Chart.defaults.global.defaultFontSize
|
||||
);
|
||||
innerDimension -= offset;
|
||||
start = firstTickValue;
|
||||
}
|
||||
} else if (newVal === 0) {
|
||||
pixel = tickOpts.reverse ? me.top : me.bottom;
|
||||
} else {
|
||||
range = helpers.log10(me.end) - helpers.log10(start);
|
||||
innerDimension = me.height;
|
||||
pixel = me.bottom - (innerDimension / range * (helpers.log10(newVal) - helpers.log10(start)));
|
||||
if (value !== 0) {
|
||||
offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
|
||||
}
|
||||
pixel += sign * offset;
|
||||
}
|
||||
return pixel;
|
||||
},
|
||||
getValueForPixel: function(pixel) {
|
||||
var me = this;
|
||||
var range = helpers.log10(me.end) - helpers.log10(me.start);
|
||||
var value, innerDimension;
|
||||
var reverse = me.options.ticks.reverse;
|
||||
var log10 = helpers.log10;
|
||||
var firstTickValue = me._getFirstTickValue(me.minNotZero);
|
||||
var innerDimension, start, end, value;
|
||||
|
||||
if (reverse) {
|
||||
start = me.end;
|
||||
end = me.start;
|
||||
} else {
|
||||
start = me.start;
|
||||
end = me.end;
|
||||
}
|
||||
if (me.isHorizontal()) {
|
||||
innerDimension = me.width;
|
||||
value = me.start * Math.pow(10, (pixel - me.left) * range / innerDimension);
|
||||
} else { // todo: if start === 0
|
||||
value = reverse ? me.right - pixel : pixel - me.left;
|
||||
} else {
|
||||
innerDimension = me.height;
|
||||
value = Math.pow(10, (me.bottom - pixel) * range / innerDimension) / me.start;
|
||||
value = reverse ? pixel - me.top : me.bottom - pixel;
|
||||
}
|
||||
if (value !== start) {
|
||||
if (start === 0) { // include zero tick
|
||||
var offset = helpers.getValueOrDefault(
|
||||
me.options.ticks.fontSize,
|
||||
Chart.defaults.global.defaultFontSize
|
||||
);
|
||||
value -= offset;
|
||||
innerDimension -= offset;
|
||||
start = firstTickValue;
|
||||
}
|
||||
value *= log10(end) - log10(start);
|
||||
value /= innerDimension;
|
||||
value = Math.pow(10, log10(start) + value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
@ -735,47 +735,99 @@ describe('Logarithmic Scale tests', function() {
|
||||
expect(yScale.getValueForPixel(246)).toBeCloseTo(10, 1e-4);
|
||||
});
|
||||
|
||||
it('should get the correct pixel value for a point when 0 values are present', function() {
|
||||
it('should get the correct pixel value for a point when 0 values are present or min: 0', function() {
|
||||
var config = [
|
||||
{
|
||||
dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],
|
||||
firstTick: 1, // value of the first tick
|
||||
lastTick: 80
|
||||
},
|
||||
{
|
||||
dataset: [{x: 0, y: 0}, {x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],
|
||||
firstTick: 6,
|
||||
lastTick: 80
|
||||
},
|
||||
{
|
||||
dataset: [{x: 10, y: 10}, {x: 1.2, y: 1.2}, {x: 25, y: 25}, {x: 78, y: 78}],
|
||||
scale: {ticks: {min: 0}},
|
||||
firstTick: 1,
|
||||
lastTick: 80
|
||||
},
|
||||
{
|
||||
dataset: [{x: 10, y: 10}, {x: 6.3, y: 6.3}, {x: 25, y: 25}, {x: 78, y: 78}],
|
||||
scale: {ticks: {min: 0}},
|
||||
firstTick: 6,
|
||||
lastTick: 80
|
||||
},
|
||||
];
|
||||
Chart.helpers.each(config, function(setup) {
|
||||
var xScaleConfig = {
|
||||
type: 'logarithmic'
|
||||
};
|
||||
var yScaleConfig = {
|
||||
type: 'logarithmic'
|
||||
};
|
||||
Chart.helpers.extend(xScaleConfig, setup.scale);
|
||||
Chart.helpers.extend(yScaleConfig, setup.scale);
|
||||
var chart = window.acquireChart({
|
||||
type: 'bar',
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
yAxisID: 'yScale',
|
||||
data: [0.063, 4, 0, 63, 10, 0.5]
|
||||
data: setup.dataset
|
||||
}],
|
||||
labels: []
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'yScale',
|
||||
type: 'logarithmic',
|
||||
ticks: {
|
||||
reverse: false
|
||||
}
|
||||
}]
|
||||
xAxes: [xScaleConfig],
|
||||
yAxes: [yScaleConfig]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var yScale = chart.scales.yScale;
|
||||
expect(yScale.getPixelForValue(70, 0, 0)).toBeCloseToPixel(32); // top + paddingTop
|
||||
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(484); // bottom - paddingBottom
|
||||
expect(yScale.getPixelForValue(0.063, 0, 0)).toBeCloseToPixel(475); // minNotZero 2% from range
|
||||
expect(yScale.getPixelForValue(0.5, 0, 0)).toBeCloseToPixel(344);
|
||||
expect(yScale.getPixelForValue(4, 0, 0)).toBeCloseToPixel(213);
|
||||
expect(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(155);
|
||||
expect(yScale.getPixelForValue(63, 0, 0)).toBeCloseToPixel(38.5);
|
||||
var chartArea = chart.chartArea;
|
||||
var expectations = [
|
||||
{
|
||||
id: 'x-axis-0', // horizontal scale
|
||||
axis: 'xAxes',
|
||||
start: chartArea.left,
|
||||
end: chartArea.right
|
||||
},
|
||||
{
|
||||
id: 'y-axis-0', // vertical scale
|
||||
axis: 'yAxes',
|
||||
start: chartArea.bottom,
|
||||
end: chartArea.top
|
||||
}
|
||||
];
|
||||
Chart.helpers.each(expectations, function(expectation) {
|
||||
var scale = chart.scales[expectation.id];
|
||||
var firstTick = setup.firstTick;
|
||||
var lastTick = setup.lastTick;
|
||||
var fontSize = chart.options.defaultFontSize;
|
||||
var start = expectation.start;
|
||||
var end = expectation.end;
|
||||
var sign = scale.isHorizontal() ? 1 : -1;
|
||||
|
||||
chart.options.scales.yAxes[0].ticks.reverse = true; // Reverse mode
|
||||
expect(scale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(end);
|
||||
expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(start + sign * fontSize);
|
||||
|
||||
expect(scale.getValueForPixel(start)).toBeCloseTo(0);
|
||||
expect(scale.getValueForPixel(end)).toBeCloseTo(lastTick);
|
||||
expect(scale.getValueForPixel(start + sign * fontSize)).toBeCloseTo(firstTick);
|
||||
|
||||
chart.options.scales[expectation.axis][0].ticks.reverse = true; // Reverse mode
|
||||
chart.update();
|
||||
|
||||
expect(yScale.getPixelForValue(70, 0, 0)).toBeCloseToPixel(484); // bottom - paddingBottom
|
||||
expect(yScale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(32); // top + paddingTop
|
||||
expect(yScale.getPixelForValue(0.063, 0, 0)).toBeCloseToPixel(41); // minNotZero 2% from range
|
||||
expect(yScale.getPixelForValue(0.5, 0, 0)).toBeCloseToPixel(172);
|
||||
expect(yScale.getPixelForValue(4, 0, 0)).toBeCloseToPixel(303);
|
||||
expect(yScale.getPixelForValue(10, 0, 0)).toBeCloseToPixel(361);
|
||||
expect(yScale.getPixelForValue(63, 0, 0)).toBeCloseToPixel(477);
|
||||
expect(scale.getPixelForValue(0, 0, 0)).toBeCloseToPixel(end);
|
||||
expect(scale.getPixelForValue(lastTick, 0, 0)).toBeCloseToPixel(start);
|
||||
expect(scale.getPixelForValue(firstTick, 0, 0)).toBeCloseToPixel(end - sign * fontSize);
|
||||
|
||||
expect(scale.getValueForPixel(end)).toBeCloseTo(0);
|
||||
expect(scale.getValueForPixel(start)).toBeCloseTo(lastTick);
|
||||
expect(scale.getValueForPixel(end - sign * fontSize)).toBeCloseTo(firstTick);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user