]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
New time scale `ticks.source: 'data'` option (#4568)
authorSimon Brunel <simonbrunel@users.noreply.github.com>
Thu, 27 Jul 2017 04:41:09 +0000 (06:41 +0200)
committerGitHub <noreply@github.com>
Thu, 27 Jul 2017 04:41:09 +0000 (06:41 +0200)
This new option value generates ticks from data (including labels from {t|x|y} data objects).

src/scales/scale.time.js
test/specs/scale.time.tests.js

index 9b88feecc53dd9197321e12b01059362d552f200..4d9cc6c4cf86c267969dcdcde9ab6cb6d89f7cd0 100644 (file)
@@ -260,12 +260,13 @@ function determineMajorUnit(unit) {
 }
 
 /**
- * Generates timestamps between min and max, rounded to the `minor` unit, aligned on
- * the `major` unit, spaced with `stepSize` and using the given scale time `options`.
+ * Generates a maximum of `capacity` timestamps between min and max, rounded to the
+ * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
  * Important: this method can return ticks outside the min and max range, it's the
  * responsibility of the calling code to clamp values if needed.
  */
-function generate(min, max, minor, major, stepSize, options) {
+function generate(min, max, minor, major, capacity, options) {
+       var stepSize = helpers.valueOrDefault(options.stepSize, options.unitStepSize);
        var weekday = minor === 'week' ? options.isoWeekday : false;
        var interval = INTERVALS[minor];
        var first = moment(min);
@@ -273,6 +274,10 @@ function generate(min, max, minor, major, stepSize, options) {
        var ticks = [];
        var time;
 
+       if (!stepSize) {
+               stepSize = determineStepSize(min, max, minor, capacity);
+       }
+
        // For 'week' unit, handle the first day of week option
        if (weekday) {
                first = first.isoWeekday(weekday);
@@ -348,8 +353,9 @@ module.exports = function(Chart) {
 
                        /**
                         * Ticks generation input values:
-                        * - 'labels': generates ticks from user given `data.labels` values ONLY.
                         * - 'auto': generates "optimal" ticks based on scale size and time options.
+                        * - 'data': generates ticks from data (including labels from data {t|x|y} objects).
+                        * - 'labels': generates ticks from user given `data.labels` values ONLY.
                         * @see https://github.com/chartjs/Chart.js/pull/4507
                         * @since 2.7.0
                         */
@@ -472,15 +478,22 @@ module.exports = function(Chart) {
                        var majorUnit = determineMajorUnit(unit);
                        var timestamps = [];
                        var ticks = [];
-                       var i, ilen, timestamp, stepSize;
-
-                       if (ticksOpts.source === 'auto') {
-                               stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize)
-                                       || determineStepSize(min, max, unit, capacity);
+                       var hash = {};
+                       var i, ilen, timestamp;
 
-                               timestamps = generate(min, max, unit, majorUnit, stepSize, timeOpts);
-                       } else {
+                       switch (ticksOpts.source) {
+                       case 'data':
+                               for (i = 0, ilen = me._datasets.length; i < ilen; ++i) {
+                                       timestamps.push.apply(timestamps, me._datasets[i]);
+                               }
+                               timestamps.sort(sorter);
+                               break;
+                       case 'labels':
                                timestamps = me._labels;
+                               break;
+                       case 'auto':
+                       default:
+                               timestamps = generate(min, max, unit, majorUnit, capacity, timeOpts);
                        }
 
                        if (ticksOpts.bounds === 'labels' && timestamps.length) {
@@ -492,10 +505,12 @@ module.exports = function(Chart) {
                        min = parse(timeOpts.min, me) || min;
                        max = parse(timeOpts.max, me) || max;
 
-                       // Remove ticks outside the min/max range
+                       // Remove ticks outside the min/max range and duplicated entries
                        for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
                                timestamp = timestamps[i];
-                               if (timestamp >= min && timestamp <= max) {
+                               if (timestamp >= min && timestamp <= max && !hash[timestamp]) {
+                                       // hash is used to efficiently detect timestamp duplicates
+                                       hash[timestamp] = true;
                                        ticks.push(timestamp);
                                }
                        }
index fd5b03491e5a2efa7825727f3966f07b40b157ff..e46db676a031640501d149074f3958749a3e8c6b 100755 (executable)
@@ -281,8 +281,8 @@ describe('Time scale tests', function() {
                                                        round: true,
                                                        parser: function(label) {
                                                                return label === 'foo' ?
-                                                                       moment(946771200000) :  // 02/01/2000 @ 12:00am (UTC)
-                                                                       moment(1462665600000);  // 05/08/2016 @ 12:00am (UTC)
+                                                                       moment('2000/01/02', 'YYYY/MM/DD') :
+                                                                       moment('2016/05/08', 'YYYY/MM/DD');
                                                        }
                                                },
                                                ticks: {
@@ -694,19 +694,86 @@ describe('Time scale tests', function() {
                                expect(getTicksValues(scale.ticks)).toEqual([
                                        '2017', '2019', '2020', '2025', '2042']);
                        });
-                       it ('should remove ticks that are not inside the min and max time range', function() {
+                       it ('should not duplicate ticks if min and max are the labels limits', function() {
                                var chart = this.chart;
                                var scale = chart.scales.x;
                                var options = chart.options.scales.xAxes[0];
 
-                               options.time.min = '2022';
-                               options.time.max = '2032';
+                               options.time.min = '2017';
+                               options.time.max = '2042';
                                chart.update();
 
-                               expect(scale.min).toEqual(+moment('2022', 'YYYY'));
-                               expect(scale.max).toEqual(+moment('2032', 'YYYY'));
+                               expect(scale.min).toEqual(+moment('2017', 'YYYY'));
+                               expect(scale.max).toEqual(+moment('2042', 'YYYY'));
                                expect(getTicksValues(scale.ticks)).toEqual([
-                                       '2025']);
+                                       '2017', '2019', '2020', '2025', '2042']);
+                       });
+                       it ('should correctly handle empty `data.labels`', function() {
+                               var chart = this.chart;
+                               var scale = chart.scales.x;
+
+                               chart.data.labels = [];
+                               chart.update();
+
+                               expect(scale.min).toEqual(+moment().startOf('day'));
+                               expect(scale.max).toEqual(+moment().endOf('day') + 1);
+                               expect(getTicksValues(scale.ticks)).toEqual([]);
+                       });
+               });
+
+               describe('is "data"', function() {
+                       beforeEach(function() {
+                               this.chart = window.acquireChart({
+                                       type: 'line',
+                                       data: {
+                                               labels: ['2017', '2019', '2020', '2025', '2042'],
+                                               datasets: [
+                                                       {data: [0, 1, 2, 3, 4, 5]},
+                                                       {data: [
+                                                               {t: '2018', y: 6},
+                                                               {t: '2020', y: 7},
+                                                               {t: '2043', y: 8}
+                                                       ]}
+                                               ]
+                                       },
+                                       options: {
+                                               scales: {
+                                                       xAxes: [{
+                                                               id: 'x',
+                                                               type: 'time',
+                                                               time: {
+                                                                       parser: 'YYYY'
+                                                               },
+                                                               ticks: {
+                                                                       source: 'data'
+                                                               }
+                                                       }]
+                                               }
+                                       }
+                               });
+                       });
+
+                       it ('should generate ticks from "datasets.data"', function() {
+                               var scale = this.chart.scales.x;
+
+                               expect(scale.min).toEqual(+moment('2017', 'YYYY'));
+                               expect(scale.max).toEqual(+moment('2043', 'YYYY'));
+                               expect(getTicksValues(scale.ticks)).toEqual([
+                                       '2017', '2018', '2019', '2020', '2025', '2042', '2043']);
+                       });
+                       it ('should not add ticks for min and max if they extend the labels range', function() {
+                               var chart = this.chart;
+                               var scale = chart.scales.x;
+                               var options = chart.options.scales.xAxes[0];
+
+                               options.time.min = '2012';
+                               options.time.max = '2051';
+                               chart.update();
+
+                               expect(scale.min).toEqual(+moment('2012', 'YYYY'));
+                               expect(scale.max).toEqual(+moment('2051', 'YYYY'));
+                               expect(getTicksValues(scale.ticks)).toEqual([
+                                       '2017', '2018', '2019', '2020', '2025', '2042', '2043']);
                        });
                        it ('should not duplicate ticks if min and max are the labels limits', function() {
                                var chart = this.chart;
@@ -714,13 +781,13 @@ describe('Time scale tests', function() {
                                var options = chart.options.scales.xAxes[0];
 
                                options.time.min = '2017';
-                               options.time.max = '2042';
+                               options.time.max = '2043';
                                chart.update();
 
                                expect(scale.min).toEqual(+moment('2017', 'YYYY'));
-                               expect(scale.max).toEqual(+moment('2042', 'YYYY'));
+                               expect(scale.max).toEqual(+moment('2043', 'YYYY'));
                                expect(getTicksValues(scale.ticks)).toEqual([
-                                       '2017', '2019', '2020', '2025', '2042']);
+                                       '2017', '2018', '2019', '2020', '2025', '2042', '2043']);
                        });
                        it ('should correctly handle empty `data.labels`', function() {
                                var chart = this.chart;
@@ -729,9 +796,10 @@ describe('Time scale tests', function() {
                                chart.data.labels = [];
                                chart.update();
 
-                               expect(scale.min).toEqual(+moment().startOf('day'));
-                               expect(scale.max).toEqual(+moment().endOf('day') + 1);
-                               expect(getTicksValues(scale.ticks)).toEqual([]);
+                               expect(scale.min).toEqual(+moment('2018', 'YYYY'));
+                               expect(scale.max).toEqual(+moment('2043', 'YYYY'));
+                               expect(getTicksValues(scale.ticks)).toEqual([
+                                       '2018', '2020', '2043']);
                        });
                });
        });
@@ -970,7 +1038,7 @@ describe('Time scale tests', function() {
        });
 
        describe('when time.min and/or time.max are defined', function() {
-               ['auto', 'labels'].forEach(function(source) {
+               ['auto', 'data', 'labels'].forEach(function(source) {
                        ['data', 'labels'].forEach(function(bounds) {
                                describe('and source is "' + source + '" and bounds "' + bounds + '"', function() {
                                        beforeEach(function() {
@@ -1017,6 +1085,10 @@ describe('Time scale tests', function() {
                                                expect(scale.max).toEqual(+moment(max, 'MM/DD HH:mm'));
                                                expect(scale.getPixelForValue(min)).toBeCloseToPixel(scale.left);
                                                expect(scale.getPixelForValue(max)).toBeCloseToPixel(scale.left + scale.width);
+                                               scale.ticks.forEach(function(tick) {
+                                                       expect(tick.time >= +moment(min, 'MM/DD HH:mm')).toBeTruthy();
+                                                       expect(tick.time <= +moment(max, 'MM/DD HH:mm')).toBeTruthy();
+                                               });
                                        });
                                        it ('should shrink scale to the min/max range', function() {
                                                var chart = this.chart;
@@ -1033,6 +1105,10 @@ describe('Time scale tests', function() {
                                                expect(scale.max).toEqual(+moment(max, 'MM/DD HH:mm'));
                                                expect(scale.getPixelForValue(min)).toBeCloseToPixel(scale.left);
                                                expect(scale.getPixelForValue(max)).toBeCloseToPixel(scale.left + scale.width);
+                                               scale.ticks.forEach(function(tick) {
+                                                       expect(tick.time >= +moment(min, 'MM/DD HH:mm')).toBeTruthy();
+                                                       expect(tick.time <= +moment(max, 'MM/DD HH:mm')).toBeTruthy();
+                                               });
                                        });
                                });
                        });