diff --git a/src/scales/scale.logarithmic.js b/src/scales/scale.logarithmic.js index 78aaf8171c4..09296c19568 100644 --- a/src/scales/scale.logarithmic.js +++ b/src/scales/scale.logarithmic.js @@ -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; } diff --git a/test/specs/scale.logarithmic.tests.js b/test/specs/scale.logarithmic.tests.js index 9640b1d3d62..3fd92197763 100644 --- a/test/specs/scale.logarithmic.tests.js +++ b/test/specs/scale.logarithmic.tests.js @@ -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); }); + });