diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 00b19b059e4..d204da910ee 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,7 +1,14 @@ # Installation -Chart.js can be installed via npm or bower. It is recommended to get Chart.js this way. -## npm +Chart.js can be installed as a dependency of your application via [npm](https://www.npmjs.com/package/chart.js) or [bower](https://libraries.io/bower/chartjs). It is recommended to get Chart.js this way. + +## Including as a dependency in your build + +### Optional Chart.js dependencies + +If you are using the time scale you will need to include either [Moment.js](https://momentjs.com) or [Luxon](https://moment.github.io/luxon/docs/manual/install.html#node) in your dependencies. If you do not include Moment.js you will receive a warning that it is missing from your build. You may ignore this warning if you are not using the time scale or have included Luxon. + +### npm [![npm](https://img.shields.io/npm/v/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js) [![npm](https://img.shields.io/npm/dm/chart.js.svg?style=flat-square&maxAge=600)](https://npmjs.com/package/chart.js) @@ -9,14 +16,33 @@ Chart.js can be installed via npm or bower. It is recommended to get Chart.js th npm install chart.js --save ``` -## Bower +### Bower [![bower](https://img.shields.io/bower/v/chartjs.svg?style=flat-square&maxAge=600)](https://libraries.io/bower/chartjs) ```bash bower install chart.js --save ``` -## CDN +## Pre-built scripts + +Chart.js provides two different builds for you to choose: `Stand-Alone Build`, `Bundled Build`. + +### Stand-Alone Build +Files: +* `dist/Chart.js` +* `dist/Chart.min.js` + +The stand-alone build includes Chart.js as well as the color parsing library. If this version is used, you are required to include [Moment.js](http://momentjs.com/) before Chart.js for the functionality of the time axis. + +### Bundled Build +Files: +* `dist/Chart.bundle.js` +* `dist/Chart.bundle.min.js` + +The bundled build includes Moment.js in a single file. You should use this version if you require time axes and want to include a single file. You should not use this build if your application already included Moment.js. Otherwise, Moment.js will be included twice which results in increasing page load time and possible version compatibility issues. The Moment.js version in the bundled build is private to Chart.js so if you want to use Moment.js yourself, it's better to use Chart.js (non bundled) and import Moment.js manually. + +You can get these builds from the CDNs below. + ### CDNJS [![cdnjs](https://img.shields.io/cdnjs/v/Chart.js.svg?style=flat-square&maxAge=600)](https://cdnjs.com/libraries/Chart.js) @@ -31,27 +57,9 @@ Chart.js built files are also available through [jsDelivr](http://www.jsdelivr.c https://www.jsdelivr.com/package/npm/chart.js?path=dist -## Github +### Github [![github](https://img.shields.io/github/release/chartjs/Chart.js.svg?style=flat-square&maxAge=600)](https://github.com/chartjs/Chart.js/releases/latest) You can download the latest version of [Chart.js on GitHub](https://github.com/chartjs/Chart.js/releases/latest). If you download or clone the repository, you must [build](../developers/contributing.md#building-and-testing) Chart.js to generate the dist files. Chart.js no longer comes with prebuilt release versions, so an alternative option to downloading the repo is **strongly** advised. - -# Selecting the Correct Build - -Chart.js provides two different builds for you to choose: `Stand-Alone Build`, `Bundled Build`. - -## Stand-Alone Build -Files: -* `dist/Chart.js` -* `dist/Chart.min.js` - -The stand-alone build includes Chart.js as well as the color parsing library. If this version is used, you are required to include [Moment.js](http://momentjs.com/) before Chart.js for the functionality of the time axis. - -## Bundled Build -Files: -* `dist/Chart.bundle.js` -* `dist/Chart.bundle.min.js` - -The bundled build includes Moment.js in a single file. You should use this version if you require time axes and want to include a single file. You should not use this build if your application already included Moment.js. Otherwise, Moment.js will be included twice which results in increasing page load time and possible version compatability issues. The Moment.js version in the bundled build is private to Chart.js so if you want to use Moment.js yourself, it's better to use Chart.js (non bundled) and import Moment.js manually. diff --git a/gulpfile.js b/gulpfile.js index d8872df0e31..696664e25f7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -107,6 +107,7 @@ function buildTask() { } var bundled = browserify('./src/chart.js', { standalone: 'Chart' }) + .ignore('luxon') .plugin(collapse) .bundle() .on('error', errorHandler) @@ -121,6 +122,7 @@ function buildTask() { .pipe(gulp.dest(outDir)); var nonBundled = browserify('./src/chart.js', { standalone: 'Chart' }) + .ignore('luxon') .ignore('moment') .plugin(collapse) .bundle() diff --git a/package.json b/package.json index 30e365f7467..5fdf7a3214d 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "karma-firefox-launcher": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-jasmine-html-reporter": "^0.2.2", + "moment": "^2.10.2", + "luxon": "^1.2.1", "merge-stream": "^1.0.1", "pixelmatch": "^4.0.2", "vinyl-source-stream": "^1.1.0", @@ -51,7 +53,9 @@ "main": "Chart.js" }, "dependencies": { - "chartjs-color": "^2.1.0", + "chartjs-color": "^2.1.0" + }, + "peerDependencies": { "moment": "^2.10.2" } } diff --git a/samples/samples.js b/samples/samples.js index 6edc6282423..60514a59981 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -113,8 +113,11 @@ title: 'Line (point data)', path: 'scales/time/line-point-data.html' }, { - title: 'Time Series', + title: 'Time Series - Moment', path: 'scales/time/financial.html' + }, { + title: 'Time Series - Luxon', + path: 'scales/time/financial-luxon.html' }, { title: 'Combo', path: 'scales/time/combo.html' diff --git a/samples/scales/time/financial-luxon.html b/samples/scales/time/financial-luxon.html new file mode 100644 index 00000000000..1cae2012d3b --- /dev/null +++ b/samples/scales/time/financial-luxon.html @@ -0,0 +1,101 @@ + + + + + Line Chart + + + + + + + +
+ +
+
+
+ Chart Type: + + + + + + diff --git a/src/scales/scale.time.js b/src/scales/scale.time.js index 405d8ff8c80..61e4aae1b91 100644 --- a/src/scales/scale.time.js +++ b/src/scales/scale.time.js @@ -1,8 +1,19 @@ /* global window: false */ 'use strict'; -var moment = require('moment'); -moment = typeof moment === 'function' ? moment : window.moment; +var moment, luxon, DateTime; +try { + moment = require('moment'); // eslint-disable-line global-require + moment = typeof moment === 'function' ? moment : window.moment; +} catch (mte) { + try { + luxon = require('luxon'); // eslint-disable-line global-require + luxon = (luxon && luxon.DateTime) ? luxon : window.luxon; + DateTime = luxon && luxon.DateTime; + } catch (lxe) { + throw new Error('Chart.js - Neither Moment.js no Luxon could be found! You must include one of these date libraries to use the time scale. For more info, see https://www.chartjs.org/docs/latest/getting-started/installation.html'); + } +} var defaults = require('../core/core.defaults'); var helpers = require('../helpers/index'); @@ -179,11 +190,24 @@ function interpolate(table, skey, sval, tkey) { return prev[tkey] + offset; } +function millisToDate(value) { + return luxon ? DateTime.fromMillis(value) : moment(value); +} + /** * Convert the given value to a moment object using the given time options. * @see http://momentjs.com/docs/#/parsing/ */ -function momentify(value, options) { +function createDate(value, options) { + if (luxon) { + if (typeof value === 'number') { + return DateTime.fromMillis(value); + } + if (value instanceof Date) { + return DateTime.fromJSDate(value); + } + throw 'Input must be either a Date or milliseconds since epoch, but got ' + value; + } var parser = options.parser; var format = options.parser || options.format; @@ -212,14 +236,18 @@ function momentify(value, options) { return value; } +function isValid(date) { + return luxon ? date.isValid : date.isValid(); +} + 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()) { + var value = createDate(scale.getRightValue(input), options); + if (!isValid(value)) { return null; } @@ -278,7 +306,8 @@ function determineUnitForAutoTicks(minUnit, min, max, capacity) { * Figures out what unit to format a set of ticks with */ function determineUnitForFormatting(ticks, minUnit, min, max) { - var duration = moment.duration(moment(max).diff(moment(min))); + var duration = luxon ? createDate(max).diff(createDate(min)) + : moment.duration(moment(max).diff(moment(min))); var ilen = UNITS.length; var i, unit; @@ -394,7 +423,7 @@ function ticksFromTimestamps(values, majorUnit) { for (i = 0, ilen = values.length; i < ilen; ++i) { value = values[i]; - major = majorUnit ? value === +moment(value).startOf(majorUnit) : false; + major = majorUnit ? value === millisToDate(value).startOf(majorUnit).valueOf() : false; ticks.push({ value: value, @@ -406,18 +435,27 @@ function ticksFromTimestamps(values, majorUnit) { } function determineLabelFormat(data, timeOpts) { - var i, momentDate, hasTime; + var i, date, hasTime; var ilen = data.length; // find the label with the most parts (milliseconds, minutes, etc.) // format all labels with the same level of detail as the most specific label for (i = 0; i < ilen; i++) { - momentDate = momentify(data[i], timeOpts); - if (momentDate.millisecond() !== 0) { - return 'MMM D, YYYY h:mm:ss.SSS a'; - } - if (momentDate.second() !== 0 || momentDate.minute() !== 0 || momentDate.hour() !== 0) { - hasTime = true; + date = createDate(data[i], timeOpts); + if (luxon) { + if (date.millisecond !== 0) { + return 'MMM D, YYYY h:mm:ss.SSS a'; + } + if (date.second !== 0 || date.minute !== 0 || date.hour !== 0) { + hasTime = true; + } + } else { + if (date.millisecond() !== 0) { + return 'MMM D, YYYY h:mm:ss.SSS a'; + } + if (date.second() !== 0 || date.minute() !== 0 || date.hour() !== 0) { + hasTime = true; + } } } if (hasTime) { @@ -463,12 +501,12 @@ module.exports = function() { 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 + hour: luxon ? 'ha' : 'hA', // 5PM + day: luxon ? 'MMM d' : 'MMM D', // Sep 4 + week: luxon ? 'WW' : 'll', // Week 46, or maybe "[W]WW - YYYY" ? + month: luxon ? 'MMM yyyy' : 'MMM YYYY', // Sept 2015 + quarter: luxon ? '\'Q\'q - yyyy' : '[Q]Q - YYYY', // Q3 - 2015 + year: luxon ? 'yyyy' : 'YYYY' // 2015 }, }, ticks: { @@ -492,10 +530,6 @@ module.exports = function() { var TimeScale = 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(); Scale.prototype.initialize.call(this); @@ -661,13 +695,13 @@ module.exports = function() { label = me.getRightValue(value); } if (timeOpts.tooltipFormat) { - return momentify(label, timeOpts).format(timeOpts.tooltipFormat); + return createDate(label, timeOpts).format(timeOpts.tooltipFormat); } if (typeof label === 'string') { return label; } - return momentify(label, timeOpts).format(me._labelFormat); + return createDate(label, timeOpts).format(me._labelFormat); }, /** @@ -682,10 +716,11 @@ module.exports = function() { var minorFormat = formats[me._unit]; var majorUnit = me._majorUnit; var majorFormat = formats[majorUnit]; - var majorTime = tick.clone().startOf(majorUnit).valueOf(); + var majorTime = (luxon ? tick : tick.clone()).startOf(majorUnit).valueOf(); var majorTickOpts = options.ticks.major; var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime; - var label = tick.format(formatOverride ? formatOverride : major ? majorFormat : minorFormat); + var format = formatOverride ? formatOverride : major ? majorFormat : minorFormat; + var label = luxon ? tick.toFormat(format) : tick.format(format); var tickOpts = major ? majorTickOpts : options.ticks.minor; var formatter = helpers.valueOrDefault(tickOpts.callback, tickOpts.userCallback); @@ -697,7 +732,7 @@ module.exports = function() { var i, ilen; for (i = 0, ilen = ticks.length; i < ilen; ++i) { - labels.push(this.tickFormatFunction(moment(ticks[i].value), i, ticks)); + labels.push(this.tickFormatFunction(millisToDate(ticks[i].value), i, ticks)); } return labels;