]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Fix log scale when value is 0 (#4913)
authorjcopperfield <33193571+jcopperfield@users.noreply.github.com>
Fri, 10 Nov 2017 08:16:48 +0000 (09:16 +0100)
committerSimon Brunel <simonbrunel@users.noreply.github.com>
Fri, 10 Nov 2017 08:16:48 +0000 (09:16 +0100)
src/scales/scale.logarithmic.js
test/specs/scale.logarithmic.tests.js

index 78aaf8171c4a9ae460abd2ec4b6f4e5586b9b52f..09296c195682074284bdcdd47f7981332723ea33 100644 (file)
@@ -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;
                }
index 9640b1d3d6281782fde04ec51d552d49acd1bb95..3fd92197763666075f35ef8db5d8a979b155c479 100644 (file)
@@ -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);
        });
+
 });