Skip to content

Commit

Permalink
Fix min/max for labels source and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrunel committed Jul 16, 2017
1 parent 2956600 commit b8a5d86
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 25 deletions.
48 changes: 23 additions & 25 deletions src/scales/scale.time.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,35 +46,31 @@ module.exports = function(Chart) {
}
};

function sorter(a, b) {
return a - b;
}

function buildLookupTable(ticks, min, max, linear) {
var ilen = ticks.length;
var table = [];
var i, prev, curr, next, decimal;
var i, prev, curr, next, pos;

if (ilen === 0) {
return table;
}

if (ticks[0] !== min) {
table.push({time: min, decimal: 0});
}

for (i = 0; i<ilen; ++i) {
next = ticks[i + 1] || 0;
prev = ticks[i - 1] || 0;
curr = ticks[i];

// only add points that breaks the scale linearity
if (Math.round((next + prev) / 2) !== curr) {
decimal = linear ? (curr - min) / (max - min) : ilen > 1 ? i / (ilen - 1) : 0;
table.push({time: curr, decimal: decimal});
pos = linear ? (curr - min) / (max - min) : ilen > 1 ? i / (ilen - 1) : 0;
table.push({time: curr, pos: pos});
}
}

if (ticks[ilen - 1] !== max) {
table.push({time: max, decimal: 1});
}

return table;
}

Expand Down Expand Up @@ -124,10 +120,6 @@ module.exports = function(Chart) {
return time.valueOf();
}

function sorter(a, b) {
return a - b;
}

var TimeScale = Chart.Scale.extend({
initialize: function() {
if (!moment) {
Expand Down Expand Up @@ -212,14 +204,20 @@ module.exports = function(Chart) {
var capacity = me.getLabelCapacity(min);
var unit = timeOpts.unit || timeHelpers.determineUnit(timeOpts.minUnit, min, max, capacity);
var majorUnit = timeHelpers.determineMajorUnit(unit);
var ticks, stepSize;
var ticks = [];
var i, ilen, timestamp, stepSize;

if (ticksOpts.source === 'labels') {
ticks = model.labels.slice();
if (min !== ticks[0]) {
for (i = 0, ilen = model.labels.length; i < ilen; ++i) {
timestamp = model.labels[i];
if (timestamp >= min && timestamp <= max) {
ticks.push(timestamp);
}
}
if (ticks[0] > min) {
ticks.unshift(min);
}
if (max !== ticks[ticks.length - 1]) {
if (ticks[ticks.length - 1] < max) {
ticks.push(max);
}
} else {
Expand Down Expand Up @@ -321,11 +319,11 @@ module.exports = function(Chart) {

var span = next.time - prev.time;
var ratio = span ? (time - prev.time) / span : 0;
var offset = (next.decimal - prev.decimal) * ratio;
var offset = (next.pos - prev.pos) * ratio;
var size = model.horizontal ? me.width : me.height;
var start = model.horizontal ? me.left : me.top;

return start + size * (prev.decimal + offset);
return start + size * (prev.pos + offset);
},

getPixelForValue: function(value, index, datasetIndex) {
Expand Down Expand Up @@ -357,16 +355,16 @@ module.exports = function(Chart) {
var table = model.table;
var size = model.horizontal ? me.width : me.height;
var start = model.horizontal ? me.left : me.top;
var decimal = size ? (pixel - start) / size : 0;
var range = lookup(table, 'decimal', decimal);
var pos = size ? (pixel - start) / size : 0;
var range = lookup(table, 'pos', pos);

// if pixel is out of bounds, use ticks [0, 1] or [n-1, n] for interpolation,
// note that the lookup table always contains at least 2 items (min and max)
var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;

var span = next.decimal - prev.decimal;
var ratio = span? (decimal - prev.decimal) / span : 0;
var span = next.pos - prev.pos;
var ratio = span? (pos - prev.pos) / span : 0;
var offset = (next.time - prev.time) * ratio;

return moment(prev.time + offset);
Expand Down
149 changes: 149 additions & 0 deletions test/specs/scale.time.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ describe('Time scale tests', function() {
});
}

function fetchTickPositions(scale) {
return scale.ticks.map(function(tick, index) {
return scale.getPixelForTick(index);
});
}

beforeEach(function() {
// Need a time matcher for getValueFromPixel
jasmine.addMatchers({
Expand Down Expand Up @@ -537,4 +543,147 @@ describe('Time scale tests', function() {
expect(chart.scales['y-axis-0'].maxWidth).toEqual(0);
expect(chart.width).toEqual(0);
});

describe('when ticks.source', function() {
describe('is "labels"', 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: {},
ticks: {
source: 'labels'
}
}]
}
}
});
});

it ('should generate ticks from "data.labels"', function() {
expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([
'2017', '2019', '2020', '2025', '2042']);
});
it ('should extend ticks with min and max if outside the time range', function() {
var chart = this.chart;
var options = chart.options.scales.xAxes[0];

options.time.min = '2012';
options.time.max = '2051';
chart.update();

expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([
'2012', '2017', '2019', '2020', '2025', '2042', '2051']);
});
it ('should shrink ticks with min and max if inside the time range', function() {
var chart = this.chart;
var options = chart.options.scales.xAxes[0];

options.time.min = '2022';
options.time.max = '2032';
chart.update();

expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([
'2022', '2025', '2032']);
});
it ('should not duplicate ticks if min and max are the labels limits', function() {
var chart = this.chart;
var options = chart.options.scales.xAxes[0];

options.time.min = '2017';
options.time.max = '2042';
chart.update();

expect(getTicksValues(this.chart.scales.x.ticks)).toEqual([
'2017', '2019', '2020', '2025', '2042']);
});
});
});

describe('when ticks.mode', function() {
describe('is "series"', function() {
it ('should space ticks out with the same gap, whatever their time values', function() {
var 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: {},
ticks: {
mode: 'series',
source: 'labels'
}
}],
yAxes: [{
display: false
}]
}
}
});

var scale = chart.scales.x;
var start = scale.left;
var slice = scale.width / 4;
var pixels = fetchTickPositions(scale);

expect(pixels[0]).toBeCloseToPixel(start);
expect(pixels[1]).toBeCloseToPixel(start + slice);
expect(pixels[2]).toBeCloseToPixel(start + slice * 2);
expect(pixels[3]).toBeCloseToPixel(start + slice * 3);
expect(pixels[4]).toBeCloseToPixel(start + slice * 4);
});
});
describe('is "linear"', function() {
it ('should space ticks out with a gap relative to their time values', function() {
var 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: {},
ticks: {
mode: 'linear',
source: 'labels'
}
}],
yAxes: [{
display: false
}]
}
}
});

var scale = chart.scales.x;
var start = scale.left;
var slice = scale.width / (2042 - 2017);
var pixels = fetchTickPositions(scale);

expect(pixels[0]).toBeCloseToPixel(start);
expect(pixels[1]).toBeCloseToPixel(start + slice * (2019 - 2017));
expect(pixels[2]).toBeCloseToPixel(start + slice * (2020 - 2017));
expect(pixels[3]).toBeCloseToPixel(start + slice * (2025 - 2017));
expect(pixels[4]).toBeCloseToPixel(start + slice * (2042 - 2017));
});
});
});
});

0 comments on commit b8a5d86

Please sign in to comment.