mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
735 lines
20 KiB
JavaScript
735 lines
20 KiB
JavaScript
/* global window: false */
|
|
'use strict';
|
|
|
|
var moment = require('moment');
|
|
moment = typeof moment === 'function' ? moment : window.moment;
|
|
|
|
var defaults = require('../core/core.defaults');
|
|
var helpers = require('../helpers/index');
|
|
|
|
// Integer constants are from the ES6 spec.
|
|
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
|
|
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
|
|
|
|
var INTERVALS = {
|
|
millisecond: {
|
|
common: true,
|
|
size: 1,
|
|
steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
|
|
},
|
|
second: {
|
|
common: true,
|
|
size: 1000,
|
|
steps: [1, 2, 5, 10, 30]
|
|
},
|
|
minute: {
|
|
common: true,
|
|
size: 60000,
|
|
steps: [1, 2, 5, 10, 30]
|
|
},
|
|
hour: {
|
|
common: true,
|
|
size: 3600000,
|
|
steps: [1, 2, 3, 6, 12]
|
|
},
|
|
day: {
|
|
common: true,
|
|
size: 86400000,
|
|
steps: [1, 2, 5]
|
|
},
|
|
week: {
|
|
common: false,
|
|
size: 604800000,
|
|
steps: [1, 2, 3, 4]
|
|
},
|
|
month: {
|
|
common: true,
|
|
size: 2.628e9,
|
|
steps: [1, 2, 3]
|
|
},
|
|
quarter: {
|
|
common: false,
|
|
size: 7.884e9,
|
|
steps: [1, 2, 3, 4]
|
|
},
|
|
year: {
|
|
common: true,
|
|
size: 3.154e10
|
|
}
|
|
};
|
|
|
|
var UNITS = Object.keys(INTERVALS);
|
|
|
|
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
|
|
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
|
|
* extremity (left + width or top + height). Note that it would be more optimized to directly
|
|
* store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
|
|
* 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 {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, distribution) {
|
|
if (distribution === 'linear' || !timestamps.length) {
|
|
return [
|
|
{time: min, pos: 0},
|
|
{time: max, pos: 1}
|
|
];
|
|
}
|
|
|
|
var table = [];
|
|
var items = [min];
|
|
var i, ilen, prev, curr, next;
|
|
|
|
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];
|
|
curr = items[i];
|
|
|
|
// only add points that breaks the scale linearity
|
|
if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
|
|
table.push({time: curr, pos: i / (ilen - 1)});
|
|
}
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
|
|
function lookup(table, key, value) {
|
|
var lo = 0;
|
|
var hi = table.length - 1;
|
|
var mid, i0, i1;
|
|
|
|
while (lo >= 0 && lo <= hi) {
|
|
mid = (lo + hi) >> 1;
|
|
i0 = table[mid - 1] || null;
|
|
i1 = table[mid];
|
|
|
|
if (!i0) {
|
|
// given value is outside table (before first item)
|
|
return {lo: null, hi: i1};
|
|
} else if (i1[key] < value) {
|
|
lo = mid + 1;
|
|
} else if (i0[key] > value) {
|
|
hi = mid - 1;
|
|
} else {
|
|
return {lo: i0, hi: i1};
|
|
}
|
|
}
|
|
|
|
// given value is outside table (after last item)
|
|
return {lo: i1, hi: null};
|
|
}
|
|
|
|
/**
|
|
* Linearly interpolates the given source `value` using the table items `skey` values and
|
|
* returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
|
|
* returns the position for a timestamp equal to 42. If value is out of bounds, values at
|
|
* index [0, 1] or [n - 1, n] are used for the interpolation.
|
|
*/
|
|
function interpolate(table, skey, sval, tkey) {
|
|
var range = lookup(table, skey, sval);
|
|
|
|
// Note: 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[skey] - prev[skey];
|
|
var ratio = span ? (sval - prev[skey]) / span : 0;
|
|
var offset = (next[tkey] - prev[tkey]) * ratio;
|
|
|
|
return prev[tkey] + offset;
|
|
}
|
|
|
|
/**
|
|
* Convert the given value to a moment object using the given time options.
|
|
* @see http://momentjs.com/docs/#/parsing/
|
|
*/
|
|
function momentify(value, options) {
|
|
var parser = options.parser;
|
|
var format = options.parser || options.format;
|
|
|
|
if (typeof parser === 'function') {
|
|
return parser(value);
|
|
}
|
|
|
|
if (typeof value === 'string' && typeof format === 'string') {
|
|
return moment(value, format);
|
|
}
|
|
|
|
if (!(value instanceof moment)) {
|
|
value = moment(value);
|
|
}
|
|
|
|
if (value.isValid()) {
|
|
return value;
|
|
}
|
|
|
|
// Labels are in an incompatible moment format and no `parser` has been provided.
|
|
// The user might still use the deprecated `format` option to convert his inputs.
|
|
if (typeof format === 'function') {
|
|
return format(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function parse(input, scale) {
|
|
if (helpers.isNullOrUndef(input)) {
|
|
return null;
|
|
}
|
|
|
|
var options = scale.options.time;
|
|
var value = momentify(scale.getRightValue(input), options);
|
|
if (!value.isValid()) {
|
|
return null;
|
|
}
|
|
|
|
if (options.round) {
|
|
value.startOf(options.round);
|
|
}
|
|
|
|
return value.valueOf();
|
|
}
|
|
|
|
/**
|
|
* Returns the number of unit to skip to be able to display up to `capacity` number of ticks
|
|
* in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
|
|
*/
|
|
function determineStepSize(min, max, unit, capacity) {
|
|
var range = max - min;
|
|
var interval = INTERVALS[unit];
|
|
var milliseconds = interval.size;
|
|
var steps = interval.steps;
|
|
var i, ilen, factor;
|
|
|
|
if (!steps) {
|
|
return Math.ceil(range / ((capacity || 1) * milliseconds));
|
|
}
|
|
|
|
for (i = 0, ilen = steps.length; i < ilen; ++i) {
|
|
factor = steps[i];
|
|
if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return factor;
|
|
}
|
|
|
|
function determineUnit(minUnit, min, max, capacity) {
|
|
var ilen = UNITS.length;
|
|
var i, interval, factor;
|
|
|
|
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
|
|
interval = INTERVALS[UNITS[i]];
|
|
factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
|
|
|
|
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
|
|
return UNITS[i];
|
|
}
|
|
}
|
|
|
|
return UNITS[ilen - 1];
|
|
}
|
|
|
|
function determineMajorUnit(unit) {
|
|
for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
|
|
if (INTERVALS[UNITS[i]].common) {
|
|
return UNITS[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a maximum of `capacity` timestamps between min and max, rounded to the
|
|
* `minor` unit, aligned on the `major` unit and using the given scale time `options`.
|
|
* Important: this method can return ticks outside the min and max range, it's the
|
|
* responsibility of the calling code to clamp values if needed.
|
|
*/
|
|
function generate(min, max, minor, major, capacity, options) {
|
|
var timeOpts = options.time;
|
|
var stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize);
|
|
var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
|
|
var majorTicksEnabled = options.ticks.major.enabled;
|
|
var interval = INTERVALS[minor];
|
|
var first = moment(min);
|
|
var last = moment(max);
|
|
var ticks = [];
|
|
var time;
|
|
|
|
if (!stepSize) {
|
|
stepSize = determineStepSize(min, max, minor, capacity);
|
|
}
|
|
|
|
// For 'week' unit, handle the first day of week option
|
|
if (weekday) {
|
|
first = first.isoWeekday(weekday);
|
|
last = last.isoWeekday(weekday);
|
|
}
|
|
|
|
// Align first/last ticks on unit
|
|
first = first.startOf(weekday ? 'day' : minor);
|
|
last = last.startOf(weekday ? 'day' : minor);
|
|
|
|
// Make sure that the last tick include max
|
|
if (last < max) {
|
|
last.add(1, minor);
|
|
}
|
|
|
|
time = moment(first);
|
|
|
|
if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
|
|
// Align the first tick on the previous `minor` unit aligned on the `major` unit:
|
|
// we first aligned time on the previous `major` unit then add the number of full
|
|
// stepSize there is between first and the previous major time.
|
|
time.startOf(major);
|
|
time.add(~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
|
|
}
|
|
|
|
for (; time < last; time.add(stepSize, minor)) {
|
|
ticks.push(+time);
|
|
}
|
|
|
|
ticks.push(+time);
|
|
|
|
return ticks;
|
|
}
|
|
|
|
/**
|
|
* Returns the right and left offsets from edges in the form of {left, right}.
|
|
* Offsets are added when the `offset` option is true.
|
|
*/
|
|
function computeOffsets(table, ticks, min, max, options) {
|
|
var left = 0;
|
|
var right = 0;
|
|
var upper, lower;
|
|
|
|
if (options.offset && ticks.length) {
|
|
if (!options.time.min) {
|
|
upper = ticks.length > 1 ? ticks[1] : max;
|
|
lower = ticks[0];
|
|
left = (
|
|
interpolate(table, 'time', upper, 'pos') -
|
|
interpolate(table, 'time', lower, 'pos')
|
|
) / 2;
|
|
}
|
|
if (!options.time.max) {
|
|
upper = ticks[ticks.length - 1];
|
|
lower = ticks.length > 1 ? ticks[ticks.length - 2] : min;
|
|
right = (
|
|
interpolate(table, 'time', upper, 'pos') -
|
|
interpolate(table, 'time', lower, 'pos')
|
|
) / 2;
|
|
}
|
|
}
|
|
|
|
return {left: left, right: right};
|
|
}
|
|
|
|
function ticksFromTimestamps(values, majorUnit) {
|
|
var ticks = [];
|
|
var i, ilen, value, major;
|
|
|
|
for (i = 0, ilen = values.length; i < ilen; ++i) {
|
|
value = values[i];
|
|
major = majorUnit ? value === +moment(value).startOf(majorUnit) : false;
|
|
|
|
ticks.push({
|
|
value: value,
|
|
major: major
|
|
});
|
|
}
|
|
|
|
return ticks;
|
|
}
|
|
|
|
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',
|
|
|
|
/**
|
|
* Scale boundary strategy (bypassed by min/max time options)
|
|
* - `data`: make sure data are fully visible, ticks outside are removed
|
|
* - `ticks`: make sure ticks are fully visible, data outside are truncated
|
|
* @see https://github.com/chartjs/Chart.js/pull/4556
|
|
* @since 2.7.0
|
|
*/
|
|
bounds: 'data',
|
|
|
|
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/
|
|
unit: false, // false == automatic or override with week, month, year, etc.
|
|
round: false, // none, or override with week, month, year, etc.
|
|
displayFormat: false, // DEPRECATED
|
|
isoWeekday: false, // override week start day - see http://momentjs.com/docs/#/get-set/iso-weekday/
|
|
minUnit: 'millisecond',
|
|
|
|
// defaults to unit's corresponding unitFormat below or override using pattern string from http://momentjs.com/docs/#/displaying/format/
|
|
displayFormats: {
|
|
millisecond: 'h:mm:ss.SSS a', // 11:20:01.123 AM,
|
|
second: 'h:mm:ss a', // 11:20:01 AM
|
|
minute: 'h:mm a', // 11:20 AM
|
|
hour: 'hA', // 5PM
|
|
day: 'MMM D', // Sep 4
|
|
week: 'll', // Week 46, or maybe "[W]WW - YYYY" ?
|
|
month: 'MMM YYYY', // Sept 2015
|
|
quarter: '[Q]Q - YYYY', // Q3
|
|
year: 'YYYY' // 2015
|
|
},
|
|
},
|
|
ticks: {
|
|
autoSkip: false,
|
|
|
|
/**
|
|
* Ticks generation input values:
|
|
* - 'auto': generates "optimal" ticks based on scale size and time options.
|
|
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
|
|
* - 'labels': generates ticks from user given `data.labels` values ONLY.
|
|
* @see https://github.com/chartjs/Chart.js/pull/4507
|
|
* @since 2.7.0
|
|
*/
|
|
source: 'auto',
|
|
|
|
major: {
|
|
enabled: false
|
|
}
|
|
}
|
|
};
|
|
|
|
var TimeScale = Chart.Scale.extend({
|
|
initialize: function() {
|
|
if (!moment) {
|
|
throw new Error('Chart.js - Moment.js could not be found! You must include it before Chart.js to use the time scale. Download at https://momentjs.com');
|
|
}
|
|
|
|
this.mergeTicksOptions();
|
|
|
|
Chart.Scale.prototype.initialize.call(this);
|
|
},
|
|
|
|
update: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
|
|
// DEPRECATIONS: output a message only one time per update
|
|
if (options.time && options.time.format) {
|
|
console.warn('options.time.format is deprecated and replaced by options.time.parser.');
|
|
}
|
|
|
|
return Chart.Scale.prototype.update.apply(me, arguments);
|
|
},
|
|
|
|
/**
|
|
* Allows data to be referenced via 't' attribute
|
|
*/
|
|
getRightValue: function(rawValue) {
|
|
if (rawValue && rawValue.t !== undefined) {
|
|
rawValue = rawValue.t;
|
|
}
|
|
return Chart.Scale.prototype.getRightValue.call(this, rawValue);
|
|
},
|
|
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var timeOpts = me.options.time;
|
|
var min = parse(timeOpts.min, me) || MAX_INTEGER;
|
|
var max = parse(timeOpts.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) {
|
|
labels.push(parse(chart.data.labels[i], me));
|
|
}
|
|
|
|
// Convert data to timestamps
|
|
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
|
|
if (chart.isDatasetVisible(i)) {
|
|
data = chart.data.datasets[i].data;
|
|
|
|
// Let's consider that all data have the same format.
|
|
if (helpers.isObject(data[0])) {
|
|
datasets[i] = [];
|
|
|
|
for (j = 0, jlen = data.length; j < jlen; ++j) {
|
|
timestamp = parse(data[j], me);
|
|
timestamps.push(timestamp);
|
|
datasets[i][j] = timestamp;
|
|
}
|
|
} else {
|
|
timestamps.push.apply(timestamps, labels);
|
|
datasets[i] = labels.slice(0);
|
|
}
|
|
} else {
|
|
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;
|
|
|
|
// Make sure that max is strictly higher than min (required by the lookup table)
|
|
me.min = Math.min(min, max);
|
|
me.max = Math.max(min + 1, max);
|
|
|
|
// PRIVATE
|
|
me._horizontal = me.isHorizontal();
|
|
me._table = [];
|
|
me._timestamps = {
|
|
data: timestamps,
|
|
datasets: datasets,
|
|
labels: labels
|
|
};
|
|
},
|
|
|
|
buildTicks: function() {
|
|
var me = this;
|
|
var min = me.min;
|
|
var max = me.max;
|
|
var options = me.options;
|
|
var timeOpts = options.time;
|
|
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 i, ilen, timestamp;
|
|
|
|
switch (options.ticks.source) {
|
|
case 'data':
|
|
timestamps = me._timestamps.data;
|
|
break;
|
|
case 'labels':
|
|
timestamps = me._timestamps.labels;
|
|
break;
|
|
case 'auto':
|
|
default:
|
|
timestamps = generate(min, max, unit, majorUnit, capacity, options);
|
|
}
|
|
|
|
if (options.bounds === 'ticks' && timestamps.length) {
|
|
min = timestamps[0];
|
|
max = timestamps[timestamps.length - 1];
|
|
}
|
|
|
|
// Enforce limits with user min/max options
|
|
min = parse(timeOpts.min, me) || min;
|
|
max = parse(timeOpts.max, me) || max;
|
|
|
|
// Remove ticks outside the min/max range
|
|
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
|
|
timestamp = timestamps[i];
|
|
if (timestamp >= min && timestamp <= max) {
|
|
ticks.push(timestamp);
|
|
}
|
|
}
|
|
|
|
me.min = min;
|
|
me.max = max;
|
|
|
|
// PRIVATE
|
|
me._unit = unit;
|
|
me._majorUnit = majorUnit;
|
|
me._minorFormat = formats[unit];
|
|
me._majorFormat = formats[majorUnit];
|
|
me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
|
|
me._offsets = computeOffsets(me._table, ticks, min, max, options);
|
|
|
|
return ticksFromTimestamps(ticks, majorUnit);
|
|
},
|
|
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
var me = this;
|
|
var data = me.chart.data;
|
|
var timeOpts = me.options.time;
|
|
var label = data.labels && index < data.labels.length ? data.labels[index] : '';
|
|
var value = data.datasets[datasetIndex].data[index];
|
|
|
|
if (helpers.isObject(value)) {
|
|
label = me.getRightValue(value);
|
|
}
|
|
if (timeOpts.tooltipFormat) {
|
|
label = momentify(label, timeOpts).format(timeOpts.tooltipFormat);
|
|
}
|
|
|
|
return label;
|
|
},
|
|
|
|
/**
|
|
* Function to format an individual tick mark
|
|
* @private
|
|
*/
|
|
tickFormatFunction: function(tick, index, ticks) {
|
|
var me = this;
|
|
var options = me.options;
|
|
var time = tick.valueOf();
|
|
var majorUnit = me._majorUnit;
|
|
var majorFormat = me._majorFormat;
|
|
var majorTime = tick.clone().startOf(me._majorUnit).valueOf();
|
|
var majorTickOpts = options.ticks.major;
|
|
var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
|
|
var label = tick.format(major ? majorFormat : me._minorFormat);
|
|
var tickOpts = major ? majorTickOpts : options.ticks.minor;
|
|
var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback);
|
|
|
|
return formatter ? formatter(label, index, ticks) : label;
|
|
},
|
|
|
|
convertTicksToLabels: function(ticks) {
|
|
var labels = [];
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
|
labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks));
|
|
}
|
|
|
|
return labels;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getPixelForOffset: function(time) {
|
|
var me = this;
|
|
var size = me._horizontal ? me.width : me.height;
|
|
var start = me._horizontal ? me.left : me.top;
|
|
var pos = interpolate(me._table, 'time', time, 'pos');
|
|
|
|
return start + size * (me._offsets.left + pos) / (me._offsets.left + 1 + me._offsets.right);
|
|
},
|
|
|
|
getPixelForValue: function(value, index, datasetIndex) {
|
|
var me = this;
|
|
var time = null;
|
|
|
|
if (index !== undefined && datasetIndex !== undefined) {
|
|
time = me._timestamps.datasets[datasetIndex][index];
|
|
}
|
|
|
|
if (time === null) {
|
|
time = parse(value, me);
|
|
}
|
|
|
|
if (time !== null) {
|
|
return me.getPixelForOffset(time);
|
|
}
|
|
},
|
|
|
|
getPixelForTick: function(index) {
|
|
var ticks = this.getTicks();
|
|
return index >= 0 && index < ticks.length ?
|
|
this.getPixelForOffset(ticks[index].value) :
|
|
null;
|
|
},
|
|
|
|
getValueForPixel: function(pixel) {
|
|
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 time = interpolate(me._table, 'pos', pos, 'time');
|
|
|
|
return moment(time);
|
|
},
|
|
|
|
/**
|
|
* Crude approximation of what the label width might be
|
|
* @private
|
|
*/
|
|
getLabelWidth: function(label) {
|
|
var me = this;
|
|
var ticksOpts = me.options.ticks;
|
|
var tickLabelWidth = me.ctx.measureText(label).width;
|
|
var angle = helpers.toRadians(ticksOpts.maxRotation);
|
|
var cosRotation = Math.cos(angle);
|
|
var sinRotation = Math.sin(angle);
|
|
var tickFontSize = helpers.valueOrDefault(ticksOpts.fontSize, defaults.global.defaultFontSize);
|
|
|
|
return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getLabelCapacity: function(exampleTime) {
|
|
var me = this;
|
|
|
|
me._minorFormat = me.options.time.displayFormats.millisecond; // Pick the longest format for guestimation
|
|
|
|
var exampleLabel = me.tickFormatFunction(moment(exampleTime), 0, []);
|
|
var tickLabelWidth = me.getLabelWidth(exampleLabel);
|
|
var innerWidth = me.isHorizontal() ? me.width : me.height;
|
|
|
|
return Math.floor(innerWidth / tickLabelWidth);
|
|
}
|
|
});
|
|
|
|
Chart.scaleService.registerScaleType('time', TimeScale, defaultConfig);
|
|
};
|