From f20b51bd35952084251cbe59c113675eac3e9f80 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 19 Dec 2018 19:11:49 +0200 Subject: [PATCH 1/5] Add `reverse` support to time scale --- src/scales/scale.time.js | 9 +- test/specs/scale.time.tests.js | 187 +++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 3 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index dbdb4ef6772..c42d0328459 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -363,6 +363,7 @@ function computeOffsets(table, ticks, min, max, options) { var left = 0; var right = 0; var upper, lower; + var isReverse = options.ticks.reverse; if (options.offset && ticks.length) { if (!options.time.min) { @@ -383,7 +384,7 @@ function computeOffsets(table, ticks, min, max, options) { } } - return {left: left, right: right}; + return isReverse ? {left: right, right: left} : {left: left, right: right}; } function ticksFromTimestamps(values, majorUnit) { @@ -706,11 +707,13 @@ module.exports = function() { */ getPixelForOffset: function(time) { var me = this; + var isReverse = me.options.ticks.reverse; var size = me._horizontal ? me.width : me.height; - var start = me._horizontal ? me.left : me.top; + var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top; var pos = interpolate(me._table, 'time', time, 'pos'); + var offset = size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); - return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); + return isReverse ? start - offset : start + offset; }, getPixelForValue: function(value, index, datasetIndex) { diff --git a/test/specs/scale.time.tests.js b/test/specs/scale.time.tests.js index 964e25e080a..fc575f3b58e 100755 --- a/test/specs/scale.time.tests.js +++ b/test/specs/scale.time.tests.js @@ -1317,4 +1317,191 @@ describe('Time scale tests', function() { }); }); }); + + describe('when ticks.reverse', function() { + describe('is "true"', function() { + it ('should reverse the labels', function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY', + }, + ticks: { + source: 'labels', + reverse: true + } + }], + yAxes: [{ + display: false + }] + } + } + }); + var scale = this.chart.scales.x; + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(scale.left + scale.width); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(scale.left); + }); + }); + }); + + describe('when ticks.reverse is "true" and distribution', function() { + describe('is "series"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY' + }, + distribution: 'series', + ticks: { + source: 'labels', + reverse: true + } + }], + yAxes: [{ + display: false + }] + } + } + }); + }); + + it ('should reverse the labels and space data out with the same gap, whatever their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / 4; + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 4); + expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * 3); + expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * 2); + expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start); + }); + + it ('should reverse the labels and should add a step before if scale.min is before the first data', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.min = '2012'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 4); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start); + }); + + it ('should reverse the labels and should add a step after if scale.max is after the last data', function() { + var chart = this.chart; + var scale = chart.scales.x; + var options = chart.options.scales.xAxes[0]; + + options.time.max = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 5; + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 5); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice); + }); + + it ('should reverse the labels and should add steps before and after if scale.min/max are outside the data 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 = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / 6; + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 5); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice); + }); + }); + describe('is "linear"', function() { + beforeEach(function() { + this.chart = window.acquireChart({ + type: 'line', + data: { + labels: ['2017', '2019', '2020', '2025', '2042'], + datasets: [{data: [0, 1, 2, 3, 4, 5]}] + }, + options: { + scales: { + xAxes: [{ + id: 'x', + type: 'time', + time: { + parser: 'YYYY' + }, + distribution: 'linear', + ticks: { + source: 'labels', + reverse: true + } + }], + yAxes: [{ + display: false + }] + } + } + }); + }); + + it ('should reverse the labels and should space data out with a gap relative to their time values', function() { + var scale = this.chart.scales.x; + var start = scale.left; + var slice = scale.width / (2042 - 2017); + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * (2042 - 2017)); + expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * (2042 - 2019)); + expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * (2042 - 2020)); + expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice * (2042 - 2025)); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start); + }); + + it ('should reverse the labels and should take in account scale min and max if outside the ticks 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 = '2050'; + chart.update(); + + var start = scale.left; + var slice = scale.width / (2050 - 2012); + + expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * (2050 - 2017)); + expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * (2050 - 2019)); + expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * (2050 - 2020)); + expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice * (2050 - 2025)); + expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice * (2050 - 2042)); + }); + }); + }); }); From fa0e36314944387c102454dc6719812e4e469c4c Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 19 Dec 2018 22:27:59 +0200 Subject: [PATCH 2/5] Fix wrong rotation for reverse ticks --- src/core/core.scale.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 5a777e65856..4c22c1dc75c 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -352,7 +352,7 @@ module.exports = Element.extend({ var cosRotation, sinRotation; // Allow 3 pixels x2 padding either side for label readability - var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; + var tickWidth = Math.abs(me.getPixelForTick(1) - me.getPixelForTick(0)) - 6; // Max label rotation can be set or default to 90 - also act as a loop counter while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { From 0fad2b62a6b7e4bb706d8a02c863de451ff8aa9a Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 19 Dec 2018 23:20:13 +0200 Subject: [PATCH 3/5] remove useless cache variable --- src/scales/scale.time.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index c42d0328459..b8a6f35128d 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -363,7 +363,6 @@ function computeOffsets(table, ticks, min, max, options) { var left = 0; var right = 0; var upper, lower; - var isReverse = options.ticks.reverse; if (options.offset && ticks.length) { if (!options.time.min) { @@ -384,7 +383,7 @@ function computeOffsets(table, ticks, min, max, options) { } } - return isReverse ? {left: right, right: left} : {left: left, right: right}; + return options.ticks.reverse ? {left: right, right: left} : {left: left, right: right}; } function ticksFromTimestamps(values, majorUnit) { From ef52acb6f9049fdb92b41d222187032062fd10d0 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Thu, 20 Dec 2018 11:24:25 +0200 Subject: [PATCH 4/5] {start, end} instead of {left, right} for internal offsets. actually reverse the ticks, to be combatible with other implementations revert change to core.scale.js (not needed anymore) --- src/core/core.scale.js | 2 +- src/scales/scale.time.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/core/core.scale.js b/src/core/core.scale.js index 4c22c1dc75c..5a777e65856 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -352,7 +352,7 @@ module.exports = Element.extend({ var cosRotation, sinRotation; // Allow 3 pixels x2 padding either side for label readability - var tickWidth = Math.abs(me.getPixelForTick(1) - me.getPixelForTick(0)) - 6; + var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6; // Max label rotation can be set or default to 90 - also act as a loop counter while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) { diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index b8a6f35128d..4516ce054e6 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -356,19 +356,19 @@ function generate(min, max, capacity, options) { } /** - * Returns the right and left offsets from edges in the form of {left, right}. + * Returns the end and start offsets from edges in the form of {start, end}. * Offsets are added when the `offset` option is true. */ function computeOffsets(table, ticks, min, max, options) { - var left = 0; - var right = 0; + var start = 0; + var end = 0; var upper, lower; if (options.offset && ticks.length) { if (!options.time.min) { upper = ticks.length > 1 ? ticks[1] : max; lower = ticks[0]; - left = ( + start = ( interpolate(table, 'time', upper, 'pos') - interpolate(table, 'time', lower, 'pos') ) / 2; @@ -376,14 +376,14 @@ function computeOffsets(table, ticks, min, max, options) { if (!options.time.max) { upper = ticks[ticks.length - 1]; lower = ticks.length > 1 ? ticks[ticks.length - 2] : min; - right = ( + end = ( interpolate(table, 'time', upper, 'pos') - interpolate(table, 'time', lower, 'pos') ) / 2; } } - return options.ticks.reverse ? {left: right, right: left} : {left: left, right: right}; + return options.ticks.reverse ? {start: -end, end: -start} : {start: start, end: end}; } function ticksFromTimestamps(values, majorUnit) { @@ -638,6 +638,10 @@ module.exports = function() { me.min = min; me.max = max; + if (options.ticks.reverse) { + ticks.reverse(); + } + // PRIVATE me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); me._majorUnit = determineMajorUnit(me._unit); @@ -710,7 +714,7 @@ module.exports = function() { var size = me._horizontal ? me.width : me.height; var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top; var pos = interpolate(me._table, 'time', time, 'pos'); - var offset = size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right); + var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end); return isReverse ? start - offset : start + offset; }, @@ -743,7 +747,7 @@ module.exports = function() { var me = this; var size = me._horizontal ? me.width : me.height; var start = me._horizontal ? me.left : me.top; - var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right; + var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end; var time = interpolate(me._table, 'pos', pos, 'time'); return moment(time); From aa536e328b9ed5159a5ec0cac881c8e13863aa50 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Thu, 20 Dec 2018 13:52:52 +0200 Subject: [PATCH 5/5] move ticks.reverse after // PRIVATE --- src/scales/scale.time.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 4516ce054e6..a2892bfd6cd 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -383,7 +383,7 @@ function computeOffsets(table, ticks, min, max, options) { } } - return options.ticks.reverse ? {start: -end, end: -start} : {start: start, end: end}; + return options.ticks.reverse ? {start: end, end: start} : {start: start, end: end}; } function ticksFromTimestamps(values, majorUnit) { @@ -638,10 +638,6 @@ module.exports = function() { me.min = min; me.max = max; - if (options.ticks.reverse) { - ticks.reverse(); - } - // PRIVATE me._unit = timeOpts.unit || determineUnitForFormatting(ticks, timeOpts.minUnit, me.min, me.max); me._majorUnit = determineMajorUnit(me._unit); @@ -649,6 +645,10 @@ module.exports = function() { me._offsets = computeOffsets(me._table, ticks, min, max, options); me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts); + if (options.ticks.reverse) { + ticks.reverse(); + } + return ticksFromTimestamps(ticks, me._majorUnit); },