Skip to content

Commit 999881f

Browse files
kurklesimonbrunel
authored andcommitted
Add reverse support to time scale (chartjs#5927)
1 parent 784c2dd commit 999881f

File tree

2 files changed

+202
-9
lines changed

2 files changed

+202
-9
lines changed

src/scales/scale.time.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,34 +356,34 @@ function generate(min, max, capacity, options) {
356356
}
357357

358358
/**
359-
* Returns the right and left offsets from edges in the form of {left, right}.
359+
* Returns the end and start offsets from edges in the form of {start, end}.
360360
* Offsets are added when the `offset` option is true.
361361
*/
362362
function computeOffsets(table, ticks, min, max, options) {
363-
var left = 0;
364-
var right = 0;
363+
var start = 0;
364+
var end = 0;
365365
var upper, lower;
366366

367367
if (options.offset && ticks.length) {
368368
if (!options.time.min) {
369369
upper = ticks.length > 1 ? ticks[1] : max;
370370
lower = ticks[0];
371-
left = (
371+
start = (
372372
interpolate(table, 'time', upper, 'pos') -
373373
interpolate(table, 'time', lower, 'pos')
374374
) / 2;
375375
}
376376
if (!options.time.max) {
377377
upper = ticks[ticks.length - 1];
378378
lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
379-
right = (
379+
end = (
380380
interpolate(table, 'time', upper, 'pos') -
381381
interpolate(table, 'time', lower, 'pos')
382382
) / 2;
383383
}
384384
}
385385

386-
return {left: left, right: right};
386+
return options.ticks.reverse ? {start: end, end: start} : {start: start, end: end};
387387
}
388388

389389
function ticksFromTimestamps(values, majorUnit) {
@@ -645,6 +645,10 @@ module.exports = function() {
645645
me._offsets = computeOffsets(me._table, ticks, min, max, options);
646646
me._labelFormat = determineLabelFormat(me._timestamps.data, timeOpts);
647647

648+
if (options.ticks.reverse) {
649+
ticks.reverse();
650+
}
651+
648652
return ticksFromTimestamps(ticks, me._majorUnit);
649653
},
650654

@@ -706,11 +710,13 @@ module.exports = function() {
706710
*/
707711
getPixelForOffset: function(time) {
708712
var me = this;
713+
var isReverse = me.options.ticks.reverse;
709714
var size = me._horizontal ? me.width : me.height;
710-
var start = me._horizontal ? me.left : me.top;
715+
var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top;
711716
var pos = interpolate(me._table, 'time', time, 'pos');
717+
var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end);
712718

713-
return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
719+
return isReverse ? start - offset : start + offset;
714720
},
715721

716722
getPixelForValue: function(value, index, datasetIndex) {
@@ -741,7 +747,7 @@ module.exports = function() {
741747
var me = this;
742748
var size = me._horizontal ? me.width : me.height;
743749
var start = me._horizontal ? me.left : me.top;
744-
var pos = (size ? (pixel - start) / size : 0) * (me._offsets.left + 1 + me._offsets.left) - me._offsets.right;
750+
var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end;
745751
var time = interpolate(me._table, 'pos', pos, 'time');
746752

747753
return moment(time);

test/specs/scale.time.tests.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,4 +1317,191 @@ describe('Time scale tests', function() {
13171317
});
13181318
});
13191319
});
1320+
1321+
describe('when ticks.reverse', function() {
1322+
describe('is "true"', function() {
1323+
it ('should reverse the labels', function() {
1324+
this.chart = window.acquireChart({
1325+
type: 'line',
1326+
data: {
1327+
labels: ['2017', '2019', '2020', '2025', '2042'],
1328+
datasets: [{data: [0, 1, 2, 3, 4, 5]}]
1329+
},
1330+
options: {
1331+
scales: {
1332+
xAxes: [{
1333+
id: 'x',
1334+
type: 'time',
1335+
time: {
1336+
parser: 'YYYY',
1337+
},
1338+
ticks: {
1339+
source: 'labels',
1340+
reverse: true
1341+
}
1342+
}],
1343+
yAxes: [{
1344+
display: false
1345+
}]
1346+
}
1347+
}
1348+
});
1349+
var scale = this.chart.scales.x;
1350+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(scale.left + scale.width);
1351+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(scale.left);
1352+
});
1353+
});
1354+
});
1355+
1356+
describe('when ticks.reverse is "true" and distribution', function() {
1357+
describe('is "series"', function() {
1358+
beforeEach(function() {
1359+
this.chart = window.acquireChart({
1360+
type: 'line',
1361+
data: {
1362+
labels: ['2017', '2019', '2020', '2025', '2042'],
1363+
datasets: [{data: [0, 1, 2, 3, 4, 5]}]
1364+
},
1365+
options: {
1366+
scales: {
1367+
xAxes: [{
1368+
id: 'x',
1369+
type: 'time',
1370+
time: {
1371+
parser: 'YYYY'
1372+
},
1373+
distribution: 'series',
1374+
ticks: {
1375+
source: 'labels',
1376+
reverse: true
1377+
}
1378+
}],
1379+
yAxes: [{
1380+
display: false
1381+
}]
1382+
}
1383+
}
1384+
});
1385+
});
1386+
1387+
it ('should reverse the labels and space data out with the same gap, whatever their time values', function() {
1388+
var scale = this.chart.scales.x;
1389+
var start = scale.left;
1390+
var slice = scale.width / 4;
1391+
1392+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 4);
1393+
expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * 3);
1394+
expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * 2);
1395+
expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice);
1396+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start);
1397+
});
1398+
1399+
it ('should reverse the labels and should add a step before if scale.min is before the first data', function() {
1400+
var chart = this.chart;
1401+
var scale = chart.scales.x;
1402+
var options = chart.options.scales.xAxes[0];
1403+
1404+
options.time.min = '2012';
1405+
chart.update();
1406+
1407+
var start = scale.left;
1408+
var slice = scale.width / 5;
1409+
1410+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 4);
1411+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start);
1412+
});
1413+
1414+
it ('should reverse the labels and should add a step after if scale.max is after the last data', function() {
1415+
var chart = this.chart;
1416+
var scale = chart.scales.x;
1417+
var options = chart.options.scales.xAxes[0];
1418+
1419+
options.time.max = '2050';
1420+
chart.update();
1421+
1422+
var start = scale.left;
1423+
var slice = scale.width / 5;
1424+
1425+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 5);
1426+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice);
1427+
});
1428+
1429+
it ('should reverse the labels and should add steps before and after if scale.min/max are outside the data range', function() {
1430+
var chart = this.chart;
1431+
var scale = chart.scales.x;
1432+
var options = chart.options.scales.xAxes[0];
1433+
1434+
options.time.min = '2012';
1435+
options.time.max = '2050';
1436+
chart.update();
1437+
1438+
var start = scale.left;
1439+
var slice = scale.width / 6;
1440+
1441+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * 5);
1442+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice);
1443+
});
1444+
});
1445+
describe('is "linear"', function() {
1446+
beforeEach(function() {
1447+
this.chart = window.acquireChart({
1448+
type: 'line',
1449+
data: {
1450+
labels: ['2017', '2019', '2020', '2025', '2042'],
1451+
datasets: [{data: [0, 1, 2, 3, 4, 5]}]
1452+
},
1453+
options: {
1454+
scales: {
1455+
xAxes: [{
1456+
id: 'x',
1457+
type: 'time',
1458+
time: {
1459+
parser: 'YYYY'
1460+
},
1461+
distribution: 'linear',
1462+
ticks: {
1463+
source: 'labels',
1464+
reverse: true
1465+
}
1466+
}],
1467+
yAxes: [{
1468+
display: false
1469+
}]
1470+
}
1471+
}
1472+
});
1473+
});
1474+
1475+
it ('should reverse the labels and should space data out with a gap relative to their time values', function() {
1476+
var scale = this.chart.scales.x;
1477+
var start = scale.left;
1478+
var slice = scale.width / (2042 - 2017);
1479+
1480+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * (2042 - 2017));
1481+
expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * (2042 - 2019));
1482+
expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * (2042 - 2020));
1483+
expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice * (2042 - 2025));
1484+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start);
1485+
});
1486+
1487+
it ('should reverse the labels and should take in account scale min and max if outside the ticks range', function() {
1488+
var chart = this.chart;
1489+
var scale = chart.scales.x;
1490+
var options = chart.options.scales.xAxes[0];
1491+
1492+
options.time.min = '2012';
1493+
options.time.max = '2050';
1494+
chart.update();
1495+
1496+
var start = scale.left;
1497+
var slice = scale.width / (2050 - 2012);
1498+
1499+
expect(scale.getPixelForValue('2017')).toBeCloseToPixel(start + slice * (2050 - 2017));
1500+
expect(scale.getPixelForValue('2019')).toBeCloseToPixel(start + slice * (2050 - 2019));
1501+
expect(scale.getPixelForValue('2020')).toBeCloseToPixel(start + slice * (2050 - 2020));
1502+
expect(scale.getPixelForValue('2025')).toBeCloseToPixel(start + slice * (2050 - 2025));
1503+
expect(scale.getPixelForValue('2042')).toBeCloseToPixel(start + slice * (2050 - 2042));
1504+
});
1505+
});
1506+
});
13201507
});

0 commit comments

Comments
 (0)