Skip to content

Commit

Permalink
Change ticks.mode to scale.distribution (chartjs#4582)
Browse files Browse the repository at this point in the history
Fix `ticks.mode` behavior when `ticks.source` is `auto`: the lookup table is now built from the data and not from the ticks, so data (and ticks) are correctly distributed along the scale. Rename the option to `distribution` (more explicit than `mode`) and since this option applies from now on the data, it seems better to have it under `scale` instead `scale.ticks`.
  • Loading branch information
simonbrunel authored and yofreke committed Dec 30, 2017
1 parent b2c6cbb commit f1eed0d
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 93 deletions.
128 changes: 77 additions & 51 deletions src/scales/scale.time.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,22 @@ function sorter(a, b) {
return a - b;
}

function arrayUnique(items) {
var hash = {};
var out = [];
var i, ilen, item;

for (i = 0, ilen = items.length; i < ilen; ++i) {
item = items[i];
if (!hash[item]) {
hash[item] = true;
out.push(item);
}
}

return out;
}

/**
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position
* (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
Expand All @@ -73,31 +89,33 @@ function sorter(a, b) {
* to create the lookup table. The table ALWAYS contains at least two items: min and max.
*
* @param {Number[]} timestamps - timestamps sorted from lowest to highest.
* @param {Boolean} linear - If true, timestamps will be spread linearly along the min/max
* range, so basically, the table will contains only two items: {min, 0} and {max, 1}. If
* false, timestamps will be positioned at the same distance from each other. In this case,
* only timestamps that break the time linearity are registered, meaning that in the best
* case, all timestamps are linear, the table contains only min and max.
* @param {String} distribution - If 'linear', timestamps will be spread linearly along the min
* and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
* If 'series', timestamps will be positioned at the same distance from each other. In this
* case, only timestamps that break the time linearity are registered, meaning that in the
* best case, all timestamps are linear, the table contains only min and max.
*/
function buildLookupTable(timestamps, min, max, linear) {
if (linear || !timestamps.length) {
function buildLookupTable(timestamps, min, max, distribution) {
if (distribution === 'linear' || !timestamps.length) {
return [
{time: min, pos: 0},
{time: max, pos: 1}
];
}

var table = [];
var items = timestamps.slice(0);
var items = [min];
var i, ilen, prev, curr, next;

if (min < timestamps[0]) {
items.unshift(min);
}
if (max > timestamps[timestamps.length - 1]) {
items.push(max);
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
curr = timestamps[i];
if (curr > min && curr < max) {
items.push(curr);
}
}

items.push(max);

for (i = 0, ilen = items.length; i < ilen; ++i) {
next = items[i + 1];
prev = items[i - 1];
Expand Down Expand Up @@ -334,6 +352,15 @@ module.exports = function(Chart) {
var defaultConfig = {
position: 'bottom',

/**
* Data distribution along the scale:
* - 'linear': data are spread according to their time (distances can vary),
* - 'series': data are spread at the same distance from each other.
* @see https://github.com/chartjs/Chart.js/pull/4507
* @since 2.7.0
*/
distribution: 'linear',

time: {
parser: false, // false == a pattern string from http://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from http://momentjs.com/docs/#/parsing/string-format/
Expand All @@ -359,15 +386,6 @@ module.exports = function(Chart) {
ticks: {
autoSkip: false,

/**
* Ticks distribution along the scale:
* - 'linear': ticks and data are spread according to their time (distances can vary),
* - 'series': ticks and data are spread at the same distance from each other.
* @see https://github.com/chartjs/Chart.js/pull/4507
* @since 2.7.0
*/
mode: 'linear',

/**
* Ticks generation input values:
* - 'auto': generates "optimal" ticks based on scale size and time options.
Expand Down Expand Up @@ -430,44 +448,54 @@ module.exports = function(Chart) {
var me = this;
var chart = me.chart;
var options = me.options;
var datasets = chart.data.datasets || [];
var min = parse(options.time.min, me) || MAX_INTEGER;
var max = parse(options.time.max, me) || MIN_INTEGER;
var timestamps = [];
var datasets = [];
var labels = [];
var i, j, ilen, jlen, data, timestamp;

// Convert labels to timestamps
for (i = 0, ilen = chart.data.labels.length; i < ilen; ++i) {
timestamp = parse(chart.data.labels[i], me);
min = Math.min(min, timestamp);
max = Math.max(max, timestamp);
labels.push(timestamp);
labels.push(parse(chart.data.labels[i], me));
}

// Convert data to timestamps
for (i = 0, ilen = datasets.length; i < ilen; ++i) {
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
if (chart.isDatasetVisible(i)) {
data = datasets[i].data;
data = chart.data.datasets[i].data;

// Let's consider that all data have the same format.
if (helpers.isObject(data[0])) {
timestamps[i] = [];
datasets[i] = [];

for (j = 0, jlen = data.length; j < jlen; ++j) {
timestamp = parse(data[j], me);
min = Math.min(min, timestamp);
max = Math.max(max, timestamp);
timestamps[i][j] = timestamp;
timestamps.push(timestamp);
datasets[i][j] = timestamp;
}
} else {
timestamps[i] = labels.slice(0);
timestamps.push.apply(timestamps, labels);
datasets[i] = labels.slice(0);
}
} else {
timestamps[i] = [];
datasets[i] = [];
}
}

if (labels.length) {
// Sort labels **after** data have been converted
labels = arrayUnique(labels).sort(sorter);
min = Math.min(min, labels[0]);
max = Math.max(max, labels[labels.length - 1]);
}

if (timestamps.length) {
timestamps = arrayUnique(timestamps).sort(sorter);
min = Math.min(min, timestamps[0]);
max = Math.max(max, timestamps[timestamps.length - 1]);
}

// In case there is no valid min/max, let's use today limits
min = min === MAX_INTEGER ? +moment().startOf('day') : min;
max = max === MIN_INTEGER ? +moment().endOf('day') + 1 : max;
Expand All @@ -477,36 +505,36 @@ module.exports = function(Chart) {
me.max = Math.max(min + 1, max);

// PRIVATE
me._datasets = timestamps;
me._horizontal = me.isHorizontal();
me._labels = labels.sort(sorter); // Sort labels **after** data have been converted
me._table = [];
me._timestamps = {
data: timestamps,
datasets: datasets,
labels: labels
};
},

buildTicks: function() {
var me = this;
var min = me.min;
var max = me.max;
var timeOpts = me.options.time;
var ticksOpts = me.options.ticks;
var options = me.options;
var timeOpts = options.time;
var ticksOpts = options.ticks;
var formats = timeOpts.displayFormats;
var capacity = me.getLabelCapacity(min);
var unit = timeOpts.unit || determineUnit(timeOpts.minUnit, min, max, capacity);
var majorUnit = determineMajorUnit(unit);
var timestamps = [];
var ticks = [];
var hash = {};
var i, ilen, timestamp;

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);
timestamps = me._timestamps.data;
break;
case 'labels':
timestamps = me._labels;
timestamps = me._timestamps.labels;
break;
case 'auto':
default:
Expand All @@ -522,12 +550,10 @@ module.exports = function(Chart) {
min = parse(timeOpts.min, me) || min;
max = parse(timeOpts.max, me) || max;

// Remove ticks outside the min/max range and duplicated entries
// Remove ticks outside the min/max range
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
timestamp = timestamps[i];
if (timestamp >= min && timestamp <= max && !hash[timestamp]) {
// hash is used to efficiently detect timestamp duplicates
hash[timestamp] = true;
if (timestamp >= min && timestamp <= max) {
ticks.push(timestamp);
}
}
Expand All @@ -540,7 +566,7 @@ module.exports = function(Chart) {
me._majorUnit = majorUnit;
me._minorFormat = formats[unit];
me._majorFormat = formats[majorUnit];
me._table = buildLookupTable(ticks, min, max, ticksOpts.mode === 'linear');
me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);

return ticksFromTimestamps(ticks, majorUnit);
},
Expand Down Expand Up @@ -609,7 +635,7 @@ module.exports = function(Chart) {
var time = null;

if (index !== undefined && datasetIndex !== undefined) {
time = me._datasets[datasetIndex][index];
time = me._timestamps.datasets[datasetIndex][index];
}

if (time === null) {
Expand Down

0 comments on commit f1eed0d

Please sign in to comment.