Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issues #3917, #4380, #2966 - log scale with min: 0 #4913

Merged
merged 5 commits into from Nov 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
114 changes: 72 additions & 42 deletions src/scales/scale.logarithmic.js
Expand Up @@ -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)));
}
innerDimension = me.width;
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)));
}
} 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)));
}
} 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)));
sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
pixel = reverse ? me.top : me.bottom;
}
if (value !== start) {
if (start === 0) { // include zero tick
offset = helpers.getValueOrDefault(
me.options.ticks.fontSize,
Chart.defaults.global.defaultFontSize
);
innerDimension -= offset;
start = firstTickValue;
}
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;
}
Expand Down
130 changes: 91 additions & 39 deletions test/specs/scale.logarithmic.tests.js
Expand Up @@ -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() {
var chart = window.acquireChart({
type: 'bar',
data: {
datasets: [{
yAxisID: 'yScale',
data: [0.063, 4, 0, 63, 10, 0.5]
}],
labels: []
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
},
options: {
scales: {
yAxes: [{
id: 'yScale',
type: 'logarithmic',
ticks: {
reverse: false
}
}]
{
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: 'line',
data: {
datasets: [{
data: setup.dataset
}],
},
options: {
scales: {
xAxes: [xScaleConfig],
yAxes: [yScaleConfig]
}
}
}
});

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;

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(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);
});
});

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);

chart.options.scales.yAxes[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);
});

});