diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index dbdb4ef6772..a2892bfd6cd 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 {left: left, right: right}; + return options.ticks.reverse ? {start: end, end: start} : {start: start, end: end}; } function ticksFromTimestamps(values, majorUnit) { @@ -645,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); }, @@ -706,11 +710,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.start + pos) / (me._offsets.start + 1 + me._offsets.end); - 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) { @@ -741,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); 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)); + }); + }); + }); });