From c77c48133b1d82b6c835a8eb734879f6913b6ad4 Mon Sep 17 00:00:00 2001 From: Ben Goldsworthy Date: Thu, 20 Feb 2025 23:42:38 +0100 Subject: [PATCH] add Chart.js packages --- layouts/_default/single.html | 2 + layouts/blog/single.html | 2 + package-lock.json | 32 + package.json | 2 + static/js/chartjs-adapter-date-fns/LICENSE.md | 9 + static/js/chartjs-adapter-date-fns/README.md | 79 + .../dist/chartjs-adapter-date-fns.bundle.js | 6322 +++++++++++++++++ .../chartjs-adapter-date-fns.bundle.min.js | 7 + .../dist/chartjs-adapter-date-fns.esm.js | 109 + .../dist/chartjs-adapter-date-fns.js | 114 + .../dist/chartjs-adapter-date-fns.min.js | 7 + .../js/chartjs-adapter-date-fns/package.json | 61 + .../js/chartjs-plugin-annotation/LICENSE.md | 9 + static/js/chartjs-plugin-annotation/README.md | 41 + .../dist/chartjs-plugin-annotation.cjs | 3040 ++++++++ .../dist/chartjs-plugin-annotation.esm.js | 3033 ++++++++ .../dist/chartjs-plugin-annotation.min.js | 7 + .../js/chartjs-plugin-annotation/package.json | 90 + .../types/element.d.ts | 18 + .../types/events.d.ts | 28 + .../types/index.d.ts | 31 + .../types/label.d.ts | 165 + .../types/options.d.ts | 166 + 23 files changed, 13374 insertions(+) create mode 100644 static/js/chartjs-adapter-date-fns/LICENSE.md create mode 100644 static/js/chartjs-adapter-date-fns/README.md create mode 100644 static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js create mode 100644 static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js create mode 100644 static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.esm.js create mode 100644 static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.js create mode 100644 static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.min.js create mode 100644 static/js/chartjs-adapter-date-fns/package.json create mode 100644 static/js/chartjs-plugin-annotation/LICENSE.md create mode 100644 static/js/chartjs-plugin-annotation/README.md create mode 100644 static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs create mode 100644 static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.esm.js create mode 100644 static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js create mode 100644 static/js/chartjs-plugin-annotation/package.json create mode 100644 static/js/chartjs-plugin-annotation/types/element.d.ts create mode 100644 static/js/chartjs-plugin-annotation/types/events.d.ts create mode 100644 static/js/chartjs-plugin-annotation/types/index.d.ts create mode 100644 static/js/chartjs-plugin-annotation/types/label.d.ts create mode 100644 static/js/chartjs-plugin-annotation/types/options.d.ts diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 24da06b..6203f84 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -60,6 +60,8 @@ id="Charts-script" src="/js/chart/chart.js" > + + + + + +``` + +Read more about jsDeliver versioning on their [website](http://www.jsdelivr.com/). + +## Configuration + +### Locale support via scale options + +date-fns requires a date-fns locale object to be tagged on to each `format()` call, which requires the locale to be explicitly set via the `adapters.date` option: [Chart.js documentation on adapters.date](https://www.chartjs.org/docs/next/axes/cartesian/time#date-adapters) + +For example: + +```javascript +// import date-fns locale: +import {de} from 'date-fns/locale'; + + +// scale options: +{ + adapters: { + date: { + locale: de + } + } +} +``` + +Further, read the [Chart.js documentation](https://www.chartjs.org/docs/next) for other possible date/time related options. For example, the time scale [`time.*` options](https://www.chartjs.org/docs/next/axes/cartesian/time#configuration-options) can be overridden using the [date-fns tokens](https://date-fns.org/docs/format). + +## Development + +You first need to install node dependencies (requires [Node.js](https://nodejs.org/)): + +```bash +> npm install +``` + +The following commands will then be available from the repository root: + +```bash +> npm run build // build dist files +> npm run lint // perform code linting +``` + +## License + +`chartjs-adapter-date-fns` is available under the [MIT license](LICENSE.md). diff --git a/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js b/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js new file mode 100644 index 0000000..39c150e --- /dev/null +++ b/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js @@ -0,0 +1,6322 @@ +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('chart.js')) : +typeof define === 'function' && define.amd ? define(['chart.js'], factory) : +(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Chart)); +})(this, (function (chart_js) { 'use strict'; + +function toInteger(dirtyNumber) { + if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) { + return NaN; + } + + var number = Number(dirtyNumber); + + if (isNaN(number)) { + return number; + } + + return number < 0 ? Math.ceil(number) : Math.floor(number); +} + +function requiredArgs(required, args) { + if (args.length < required) { + throw new TypeError(required + ' argument' + (required > 1 ? 's' : '') + ' required, but only ' + args.length + ' present'); + } +} + +/** + * @name toDate + * @category Common Helpers + * @summary Convert the given argument to an instance of Date. + * + * @description + * Convert the given argument to an instance of Date. + * + * If the argument is an instance of Date, the function returns its clone. + * + * If the argument is a number, it is treated as a timestamp. + * + * If the argument is none of the above, the function returns Invalid Date. + * + * **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`. + * + * @param {Date|Number} argument - the value to convert + * @returns {Date} the parsed date in the local time zone + * @throws {TypeError} 1 argument required + * + * @example + * // Clone the date: + * const result = toDate(new Date(2014, 1, 11, 11, 30, 30)) + * //=> Tue Feb 11 2014 11:30:30 + * + * @example + * // Convert the timestamp to date: + * const result = toDate(1392098430000) + * //=> Tue Feb 11 2014 11:30:30 + */ + +function toDate(argument) { + requiredArgs(1, arguments); + var argStr = Object.prototype.toString.call(argument); // Clone the date + + if (argument instanceof Date || typeof argument === 'object' && argStr === '[object Date]') { + // Prevent the date to lose the milliseconds when passed to new Date() in IE10 + return new Date(argument.getTime()); + } else if (typeof argument === 'number' || argStr === '[object Number]') { + return new Date(argument); + } else { + if ((typeof argument === 'string' || argStr === '[object String]') && typeof console !== 'undefined') { + // eslint-disable-next-line no-console + console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://git.io/fjule"); // eslint-disable-next-line no-console + + console.warn(new Error().stack); + } + + return new Date(NaN); + } +} + +/** + * @name addDays + * @category Day Helpers + * @summary Add the specified number of days to the given date. + * + * @description + * Add the specified number of days to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of days to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the days added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 10 days to 1 September 2014: + * const result = addDays(new Date(2014, 8, 1), 10) + * //=> Thu Sep 11 2014 00:00:00 + */ + +function addDays(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var date = toDate(dirtyDate); + var amount = toInteger(dirtyAmount); + + if (isNaN(amount)) { + return new Date(NaN); + } + + if (!amount) { + // If 0 days, no-op to avoid changing times in the hour before end of DST + return date; + } + + date.setDate(date.getDate() + amount); + return date; +} + +/** + * @name addMonths + * @category Month Helpers + * @summary Add the specified number of months to the given date. + * + * @description + * Add the specified number of months to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of months to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the months added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 5 months to 1 September 2014: + * const result = addMonths(new Date(2014, 8, 1), 5) + * //=> Sun Feb 01 2015 00:00:00 + */ + +function addMonths(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var date = toDate(dirtyDate); + var amount = toInteger(dirtyAmount); + + if (isNaN(amount)) { + return new Date(NaN); + } + + if (!amount) { + // If 0 months, no-op to avoid changing times in the hour before end of DST + return date; + } + + var dayOfMonth = date.getDate(); // The JS Date object supports date math by accepting out-of-bounds values for + // month, day, etc. For example, new Date(2020, 1, 0) returns 31 Dec 2019 and + // new Date(2020, 13, 1) returns 1 Feb 2021. This is *almost* the behavior we + // want except that dates will wrap around the end of a month, meaning that + // new Date(2020, 13, 31) will return 3 Mar 2021 not 28 Feb 2021 as desired. So + // we'll default to the end of the desired month by adding 1 to the desired + // month and using a date of 0 to back up one day to the end of the desired + // month. + + var endOfDesiredMonth = new Date(date.getTime()); + endOfDesiredMonth.setMonth(date.getMonth() + amount + 1, 0); + var daysInMonth = endOfDesiredMonth.getDate(); + + if (dayOfMonth >= daysInMonth) { + // If we're already at the end of the month, then this is the correct date + // and we're done. + return endOfDesiredMonth; + } else { + // Otherwise, we now know that setting the original day-of-month value won't + // cause an overflow, so set the desired day-of-month. Note that we can't + // just set the date of `endOfDesiredMonth` because that object may have had + // its time changed in the unusual case where where a DST transition was on + // the last day of the month and its local time was in the hour skipped or + // repeated next to a DST transition. So we use `date` instead which is + // guaranteed to still have the original time. + date.setFullYear(endOfDesiredMonth.getFullYear(), endOfDesiredMonth.getMonth(), dayOfMonth); + return date; + } +} + +/** + * @name addMilliseconds + * @category Millisecond Helpers + * @summary Add the specified number of milliseconds to the given date. + * + * @description + * Add the specified number of milliseconds to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of milliseconds to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the milliseconds added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 750 milliseconds to 10 July 2014 12:45:30.000: + * const result = addMilliseconds(new Date(2014, 6, 10, 12, 45, 30, 0), 750) + * //=> Thu Jul 10 2014 12:45:30.750 + */ + +function addMilliseconds(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var timestamp = toDate(dirtyDate).getTime(); + var amount = toInteger(dirtyAmount); + return new Date(timestamp + amount); +} + +var MILLISECONDS_IN_HOUR$3 = 3600000; +/** + * @name addHours + * @category Hour Helpers + * @summary Add the specified number of hours to the given date. + * + * @description + * Add the specified number of hours to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of hours to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the hours added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 2 hours to 10 July 2014 23:00:00: + * const result = addHours(new Date(2014, 6, 10, 23, 0), 2) + * //=> Fri Jul 11 2014 01:00:00 + */ + +function addHours(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_HOUR$3); +} + +/** + * @name startOfWeek + * @category Week Helpers + * @summary Return the start of a week for the given date. + * + * @description + * Return the start of a week for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @param {Object} [options] - an object with options. + * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale} + * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday) + * @returns {Date} the start of a week + * @throws {TypeError} 1 argument required + * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6 + * + * @example + * // The start of a week for 2 September 2014 11:55:00: + * var result = startOfWeek(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Sun Aug 31 2014 00:00:00 + * + * @example + * // If the week starts on Monday, the start of the week for 2 September 2014 11:55:00: + * var result = startOfWeek(new Date(2014, 8, 2, 11, 55, 0), { weekStartsOn: 1 }) + * //=> Mon Sep 01 2014 00:00:00 + */ + +function startOfWeek(dirtyDate, dirtyOptions) { + requiredArgs(1, arguments); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeWeekStartsOn = locale && locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + var date = toDate(dirtyDate); + var day = date.getDay(); + var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn; + date.setDate(date.getDate() - diff); + date.setHours(0, 0, 0, 0); + return date; +} + +/** + * Google Chrome as of 67.0.3396.87 introduced timezones with offset that includes seconds. + * They usually appear for dates that denote time before the timezones were introduced + * (e.g. for 'Europe/Prague' timezone the offset is GMT+00:57:44 before 1 October 1891 + * and GMT+01:00:00 after that date) + * + * Date#getTimezoneOffset returns the offset in minutes and would return 57 for the example above, + * which would lead to incorrect calculations. + * + * This function returns the timezone offset in milliseconds that takes seconds in account. + */ +function getTimezoneOffsetInMilliseconds(date) { + var utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds())); + utcDate.setUTCFullYear(date.getFullYear()); + return date.getTime() - utcDate.getTime(); +} + +/** + * @name startOfDay + * @category Day Helpers + * @summary Return the start of a day for the given date. + * + * @description + * Return the start of a day for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a day + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a day for 2 September 2014 11:55:00: + * const result = startOfDay(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Tue Sep 02 2014 00:00:00 + */ + +function startOfDay(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setHours(0, 0, 0, 0); + return date; +} + +var MILLISECONDS_IN_DAY$1 = 86400000; +/** + * @name differenceInCalendarDays + * @category Day Helpers + * @summary Get the number of calendar days between the given dates. + * + * @description + * Get the number of calendar days between the given dates. This means that the times are removed + * from the dates and then the difference in days is calculated. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of calendar days + * @throws {TypeError} 2 arguments required + * + * @example + * // How many calendar days are between + * // 2 July 2011 23:00:00 and 2 July 2012 00:00:00? + * var result = differenceInCalendarDays( + * new Date(2012, 6, 2, 0, 0), + * new Date(2011, 6, 2, 23, 0) + * ) + * //=> 366 + * // How many calendar days are between + * // 2 July 2011 23:59:00 and 3 July 2011 00:01:00? + * var result = differenceInCalendarDays( + * new Date(2011, 6, 3, 0, 1), + * new Date(2011, 6, 2, 23, 59) + * ) + * //=> 1 + */ + +function differenceInCalendarDays(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var startOfDayLeft = startOfDay(dirtyDateLeft); + var startOfDayRight = startOfDay(dirtyDateRight); + var timestampLeft = startOfDayLeft.getTime() - getTimezoneOffsetInMilliseconds(startOfDayLeft); + var timestampRight = startOfDayRight.getTime() - getTimezoneOffsetInMilliseconds(startOfDayRight); // Round the number of days to the nearest integer + // because the number of milliseconds in a day is not constant + // (e.g. it's different in the day of the daylight saving time clock shift) + + return Math.round((timestampLeft - timestampRight) / MILLISECONDS_IN_DAY$1); +} + +var MILLISECONDS_IN_MINUTE$3 = 60000; +/** + * @name addMinutes + * @category Minute Helpers + * @summary Add the specified number of minutes to the given date. + * + * @description + * Add the specified number of minutes to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of minutes to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the minutes added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 30 minutes to 10 July 2014 12:00:00: + * const result = addMinutes(new Date(2014, 6, 10, 12, 0), 30) + * //=> Thu Jul 10 2014 12:30:00 + */ + +function addMinutes(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_MINUTE$3); +} + +/** + * @name addQuarters + * @category Quarter Helpers + * @summary Add the specified number of year quarters to the given date. + * + * @description + * Add the specified number of year quarters to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of quarters to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the quarters added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 1 quarter to 1 September 2014: + * const result = addQuarters(new Date(2014, 8, 1), 1) + * //=> Mon Dec 01 2014 00:00:00 + */ + +function addQuarters(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + var months = amount * 3; + return addMonths(dirtyDate, months); +} + +/** + * @name addSeconds + * @category Second Helpers + * @summary Add the specified number of seconds to the given date. + * + * @description + * Add the specified number of seconds to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of seconds to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the seconds added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 30 seconds to 10 July 2014 12:45:00: + * const result = addSeconds(new Date(2014, 6, 10, 12, 45, 0), 30) + * //=> Thu Jul 10 2014 12:45:30 + */ + +function addSeconds(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + return addMilliseconds(dirtyDate, amount * 1000); +} + +/** + * @name addWeeks + * @category Week Helpers + * @summary Add the specified number of weeks to the given date. + * + * @description + * Add the specified number of week to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of weeks to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the weeks added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 4 weeks to 1 September 2014: + * const result = addWeeks(new Date(2014, 8, 1), 4) + * //=> Mon Sep 29 2014 00:00:00 + */ + +function addWeeks(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + var days = amount * 7; + return addDays(dirtyDate, days); +} + +/** + * @name addYears + * @category Year Helpers + * @summary Add the specified number of years to the given date. + * + * @description + * Add the specified number of years to the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of years to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the years added + * @throws {TypeError} 2 arguments required + * + * @example + * // Add 5 years to 1 September 2014: + * const result = addYears(new Date(2014, 8, 1), 5) + * //=> Sun Sep 01 2019 00:00:00 + */ + +function addYears(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + return addMonths(dirtyDate, amount * 12); +} + +/** + * @name compareAsc + * @category Common Helpers + * @summary Compare the two dates and return -1, 0 or 1. + * + * @description + * Compare the two dates and return 1 if the first date is after the second, + * -1 if the first date is before the second or 0 if dates are equal. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the first date to compare + * @param {Date|Number} dateRight - the second date to compare + * @returns {Number} the result of the comparison + * @throws {TypeError} 2 arguments required + * + * @example + * // Compare 11 February 1987 and 10 July 1989: + * const result = compareAsc(new Date(1987, 1, 11), new Date(1989, 6, 10)) + * //=> -1 + * + * @example + * // Sort the array of dates: + * const result = [ + * new Date(1995, 6, 2), + * new Date(1987, 1, 11), + * new Date(1989, 6, 10) + * ].sort(compareAsc) + * //=> [ + * // Wed Feb 11 1987 00:00:00, + * // Mon Jul 10 1989 00:00:00, + * // Sun Jul 02 1995 00:00:00 + * // ] + */ + +function compareAsc(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + var diff = dateLeft.getTime() - dateRight.getTime(); + + if (diff < 0) { + return -1; + } else if (diff > 0) { + return 1; // Return 0 if diff is 0; return NaN if diff is NaN + } else { + return diff; + } +} + +/** + * @name isValid + * @category Common Helpers + * @summary Is the given date valid? + * + * @description + * Returns false if argument is Invalid Date and true otherwise. + * Argument is converted to Date using `toDate`. See [toDate]{@link https://date-fns.org/docs/toDate} + * Invalid Date is a Date, whose time value is NaN. + * + * Time value of Date: http://es5.github.io/#x15.9.1.1 + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * - Now `isValid` doesn't throw an exception + * if the first argument is not an instance of Date. + * Instead, argument is converted beforehand using `toDate`. + * + * Examples: + * + * | `isValid` argument | Before v2.0.0 | v2.0.0 onward | + * |---------------------------|---------------|---------------| + * | `new Date()` | `true` | `true` | + * | `new Date('2016-01-01')` | `true` | `true` | + * | `new Date('')` | `false` | `false` | + * | `new Date(1488370835081)` | `true` | `true` | + * | `new Date(NaN)` | `false` | `false` | + * | `'2016-01-01'` | `TypeError` | `false` | + * | `''` | `TypeError` | `false` | + * | `1488370835081` | `TypeError` | `true` | + * | `NaN` | `TypeError` | `false` | + * + * We introduce this change to make *date-fns* consistent with ECMAScript behavior + * that try to coerce arguments to the expected type + * (which is also the case with other *date-fns* functions). + * + * @param {*} date - the date to check + * @returns {Boolean} the date is valid + * @throws {TypeError} 1 argument required + * + * @example + * // For the valid date: + * var result = isValid(new Date(2014, 1, 31)) + * //=> true + * + * @example + * // For the value, convertable into a date: + * var result = isValid(1393804800000) + * //=> true + * + * @example + * // For the invalid date: + * var result = isValid(new Date('')) + * //=> false + */ + +function isValid(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + return !isNaN(date); +} + +/** + * @name differenceInCalendarMonths + * @category Month Helpers + * @summary Get the number of calendar months between the given dates. + * + * @description + * Get the number of calendar months between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of calendar months + * @throws {TypeError} 2 arguments required + * + * @example + * // How many calendar months are between 31 January 2014 and 1 September 2014? + * var result = differenceInCalendarMonths( + * new Date(2014, 8, 1), + * new Date(2014, 0, 31) + * ) + * //=> 8 + */ + +function differenceInCalendarMonths(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + var yearDiff = dateLeft.getFullYear() - dateRight.getFullYear(); + var monthDiff = dateLeft.getMonth() - dateRight.getMonth(); + return yearDiff * 12 + monthDiff; +} + +/** + * @name differenceInCalendarYears + * @category Year Helpers + * @summary Get the number of calendar years between the given dates. + * + * @description + * Get the number of calendar years between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of calendar years + * @throws {TypeError} 2 arguments required + * + * @example + * // How many calendar years are between 31 December 2013 and 11 February 2015? + * var result = differenceInCalendarYears( + * new Date(2015, 1, 11), + * new Date(2013, 11, 31) + * ) + * //=> 2 + */ + +function differenceInCalendarYears(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + return dateLeft.getFullYear() - dateRight.getFullYear(); +} + +// for accurate equality comparisons of UTC timestamps that end up +// having the same representation in local time, e.g. one hour before +// DST ends vs. the instant that DST ends. + +function compareLocalAsc(dateLeft, dateRight) { + var diff = dateLeft.getFullYear() - dateRight.getFullYear() || dateLeft.getMonth() - dateRight.getMonth() || dateLeft.getDate() - dateRight.getDate() || dateLeft.getHours() - dateRight.getHours() || dateLeft.getMinutes() - dateRight.getMinutes() || dateLeft.getSeconds() - dateRight.getSeconds() || dateLeft.getMilliseconds() - dateRight.getMilliseconds(); + + if (diff < 0) { + return -1; + } else if (diff > 0) { + return 1; // Return 0 if diff is 0; return NaN if diff is NaN + } else { + return diff; + } +} +/** + * @name differenceInDays + * @category Day Helpers + * @summary Get the number of full days between the given dates. + * + * @description + * Get the number of full day periods between two dates. Fractional days are + * truncated towards zero. + * + * One "full day" is the distance between a local time in one day to the same + * local time on the next or previous day. A full day can sometimes be less than + * or more than 24 hours if a daylight savings change happens between two dates. + * + * To ignore DST and only measure exact 24-hour periods, use this instead: + * `Math.floor(differenceInHours(dateLeft, dateRight)/24)|0`. + * + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of full days according to the local timezone + * @throws {TypeError} 2 arguments required + * + * @example + * // How many full days are between + * // 2 July 2011 23:00:00 and 2 July 2012 00:00:00? + * var result = differenceInDays( + * new Date(2012, 6, 2, 0, 0), + * new Date(2011, 6, 2, 23, 0) + * ) + * //=> 365 + * // How many full days are between + * // 2 July 2011 23:59:00 and 3 July 2011 00:01:00? + * var result = differenceInDays( + * new Date(2011, 6, 3, 0, 1), + * new Date(2011, 6, 2, 23, 59) + * ) + * //=> 0 + * // How many full days are between + * // 1 March 2020 0:00 and 1 June 2020 0:00 ? + * // Note: because local time is used, the + * // result will always be 92 days, even in + * // time zones where DST starts and the + * // period has only 92*24-1 hours. + * var result = differenceInDays( + * new Date(2020, 5, 1), + * new Date(2020, 2, 1) + * ) +//=> 92 + */ + + +function differenceInDays(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + var sign = compareLocalAsc(dateLeft, dateRight); + var difference = Math.abs(differenceInCalendarDays(dateLeft, dateRight)); + dateLeft.setDate(dateLeft.getDate() - sign * difference); // Math.abs(diff in full days - diff in calendar days) === 1 if last calendar day is not full + // If so, result must be decreased by 1 in absolute value + + var isLastDayNotFull = compareLocalAsc(dateLeft, dateRight) === -sign; + var result = sign * (difference - isLastDayNotFull); // Prevent negative zero + + return result === 0 ? 0 : result; +} + +/** + * @name differenceInMilliseconds + * @category Millisecond Helpers + * @summary Get the number of milliseconds between the given dates. + * + * @description + * Get the number of milliseconds between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of milliseconds + * @throws {TypeError} 2 arguments required + * + * @example + * // How many milliseconds are between + * // 2 July 2014 12:30:20.600 and 2 July 2014 12:30:21.700? + * var result = differenceInMilliseconds( + * new Date(2014, 6, 2, 12, 30, 21, 700), + * new Date(2014, 6, 2, 12, 30, 20, 600) + * ) + * //=> 1100 + */ + +function differenceInMilliseconds(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + return dateLeft.getTime() - dateRight.getTime(); +} + +var MILLISECONDS_IN_HOUR$2 = 3600000; +/** + * @name differenceInHours + * @category Hour Helpers + * @summary Get the number of hours between the given dates. + * + * @description + * Get the number of hours between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of hours + * @throws {TypeError} 2 arguments required + * + * @example + * // How many hours are between 2 July 2014 06:50:00 and 2 July 2014 19:00:00? + * var result = differenceInHours( + * new Date(2014, 6, 2, 19, 0), + * new Date(2014, 6, 2, 6, 50) + * ) + * //=> 12 + */ + +function differenceInHours(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var diff = differenceInMilliseconds(dirtyDateLeft, dirtyDateRight) / MILLISECONDS_IN_HOUR$2; + return diff > 0 ? Math.floor(diff) : Math.ceil(diff); +} + +var MILLISECONDS_IN_MINUTE$2 = 60000; +/** + * @name differenceInMinutes + * @category Minute Helpers + * @summary Get the number of minutes between the given dates. + * + * @description + * Get the signed number of full (rounded towards 0) minutes between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of minutes + * @throws {TypeError} 2 arguments required + * + * @example + * // How many minutes are between 2 July 2014 12:07:59 and 2 July 2014 12:20:00? + * var result = differenceInMinutes( + * new Date(2014, 6, 2, 12, 20, 0), + * new Date(2014, 6, 2, 12, 7, 59) + * ) + * //=> 12 + * + * @example + * // How many minutes are from 10:01:59 to 10:00:00 + * var result = differenceInMinutes( + * new Date(2000, 0, 1, 10, 0, 0), + * new Date(2000, 0, 1, 10, 1, 59) + * ) + * //=> -1 + */ + +function differenceInMinutes(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var diff = differenceInMilliseconds(dirtyDateLeft, dirtyDateRight) / MILLISECONDS_IN_MINUTE$2; + return diff > 0 ? Math.floor(diff) : Math.ceil(diff); +} + +/** + * @name endOfDay + * @category Day Helpers + * @summary Return the end of a day for the given date. + * + * @description + * Return the end of a day for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a day + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a day for 2 September 2014 11:55:00: + * const result = endOfDay(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Tue Sep 02 2014 23:59:59.999 + */ + +function endOfDay(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setHours(23, 59, 59, 999); + return date; +} + +/** + * @name endOfMonth + * @category Month Helpers + * @summary Return the end of a month for the given date. + * + * @description + * Return the end of a month for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a month + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a month for 2 September 2014 11:55:00: + * const result = endOfMonth(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Tue Sep 30 2014 23:59:59.999 + */ + +function endOfMonth(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var month = date.getMonth(); + date.setFullYear(date.getFullYear(), month + 1, 0); + date.setHours(23, 59, 59, 999); + return date; +} + +/** + * @name isLastDayOfMonth + * @category Month Helpers + * @summary Is the given date the last day of a month? + * + * @description + * Is the given date the last day of a month? + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to check + * @returns {Boolean} the date is the last day of a month + * @throws {TypeError} 1 argument required + * + * @example + * // Is 28 February 2014 the last day of a month? + * var result = isLastDayOfMonth(new Date(2014, 1, 28)) + * //=> true + */ + +function isLastDayOfMonth(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + return endOfDay(date).getTime() === endOfMonth(date).getTime(); +} + +/** + * @name differenceInMonths + * @category Month Helpers + * @summary Get the number of full months between the given dates. + * + * @description + * Get the number of full months between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of full months + * @throws {TypeError} 2 arguments required + * + * @example + * // How many full months are between 31 January 2014 and 1 September 2014? + * var result = differenceInMonths(new Date(2014, 8, 1), new Date(2014, 0, 31)) + * //=> 7 + */ + +function differenceInMonths(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + var sign = compareAsc(dateLeft, dateRight); + var difference = Math.abs(differenceInCalendarMonths(dateLeft, dateRight)); + var result; // Check for the difference of less than month + + if (difference < 1) { + result = 0; + } else { + if (dateLeft.getMonth() === 1 && dateLeft.getDate() > 27) { + // This will check if the date is end of Feb and assign a higher end of month date + // to compare it with Jan + dateLeft.setDate(30); + } + + dateLeft.setMonth(dateLeft.getMonth() - sign * difference); // Math.abs(diff in full months - diff in calendar months) === 1 if last calendar month is not full + // If so, result must be decreased by 1 in absolute value + + var isLastMonthNotFull = compareAsc(dateLeft, dateRight) === -sign; // Check for cases of one full calendar month + + if (isLastDayOfMonth(toDate(dirtyDateLeft)) && difference === 1 && compareAsc(dirtyDateLeft, dateRight) === 1) { + isLastMonthNotFull = false; + } + + result = sign * (difference - isLastMonthNotFull); + } // Prevent negative zero + + + return result === 0 ? 0 : result; +} + +/** + * @name differenceInQuarters + * @category Quarter Helpers + * @summary Get the number of full quarters between the given dates. + * + * @description + * Get the number of full quarters between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of full quarters + * @throws {TypeError} 2 arguments required + * + * @example + * // How many full quarters are between 31 December 2013 and 2 July 2014? + * var result = differenceInQuarters(new Date(2014, 6, 2), new Date(2013, 11, 31)) + * //=> 2 + */ + +function differenceInQuarters(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var diff = differenceInMonths(dirtyDateLeft, dirtyDateRight) / 3; + return diff > 0 ? Math.floor(diff) : Math.ceil(diff); +} + +/** + * @name differenceInSeconds + * @category Second Helpers + * @summary Get the number of seconds between the given dates. + * + * @description + * Get the number of seconds between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of seconds + * @throws {TypeError} 2 arguments required + * + * @example + * // How many seconds are between + * // 2 July 2014 12:30:07.999 and 2 July 2014 12:30:20.000? + * var result = differenceInSeconds( + * new Date(2014, 6, 2, 12, 30, 20, 0), + * new Date(2014, 6, 2, 12, 30, 7, 999) + * ) + * //=> 12 + */ + +function differenceInSeconds(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var diff = differenceInMilliseconds(dirtyDateLeft, dirtyDateRight) / 1000; + return diff > 0 ? Math.floor(diff) : Math.ceil(diff); +} + +/** + * @name differenceInWeeks + * @category Week Helpers + * @summary Get the number of full weeks between the given dates. + * + * @description + * Get the number of full weeks between two dates. Fractional weeks are + * truncated towards zero. + * + * One "full week" is the distance between a local time in one day to the same + * local time 7 days earlier or later. A full week can sometimes be less than + * or more than 7*24 hours if a daylight savings change happens between two dates. + * + * To ignore DST and only measure exact 7*24-hour periods, use this instead: + * `Math.floor(differenceInHours(dateLeft, dateRight)/(7*24))|0`. + * + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of full weeks + * @throws {TypeError} 2 arguments required + * + * @example + * // How many full weeks are between 5 July 2014 and 20 July 2014? + * var result = differenceInWeeks(new Date(2014, 6, 20), new Date(2014, 6, 5)) + * //=> 2 + * + * // How many full weeks are between + * // 1 March 2020 0:00 and 6 June 2020 0:00 ? + * // Note: because local time is used, the + * // result will always be 8 weeks (54 days), + * // even if DST starts and the period has + * // only 54*24-1 hours. + * var result = differenceInWeeks( + * new Date(2020, 5, 1), + * new Date(2020, 2, 6) + * ) + * //=> 8 + */ + +function differenceInWeeks(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var diff = differenceInDays(dirtyDateLeft, dirtyDateRight) / 7; + return diff > 0 ? Math.floor(diff) : Math.ceil(diff); +} + +/** + * @name differenceInYears + * @category Year Helpers + * @summary Get the number of full years between the given dates. + * + * @description + * Get the number of full years between the given dates. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} dateLeft - the later date + * @param {Date|Number} dateRight - the earlier date + * @returns {Number} the number of full years + * @throws {TypeError} 2 arguments required + * + * @example + * // How many full years are between 31 December 2013 and 11 February 2015? + * var result = differenceInYears(new Date(2015, 1, 11), new Date(2013, 11, 31)) + * //=> 1 + */ + +function differenceInYears(dirtyDateLeft, dirtyDateRight) { + requiredArgs(2, arguments); + var dateLeft = toDate(dirtyDateLeft); + var dateRight = toDate(dirtyDateRight); + var sign = compareAsc(dateLeft, dateRight); + var difference = Math.abs(differenceInCalendarYears(dateLeft, dateRight)); // Set both dates to a valid leap year for accurate comparison when dealing + // with leap days + + dateLeft.setFullYear('1584'); + dateRight.setFullYear('1584'); // Math.abs(diff in full years - diff in calendar years) === 1 if last calendar year is not full + // If so, result must be decreased by 1 in absolute value + + var isLastYearNotFull = compareAsc(dateLeft, dateRight) === -sign; + var result = sign * (difference - isLastYearNotFull); // Prevent negative zero + + return result === 0 ? 0 : result; +} + +/** + * @name startOfQuarter + * @category Quarter Helpers + * @summary Return the start of a year quarter for the given date. + * + * @description + * Return the start of a year quarter for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a quarter + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a quarter for 2 September 2014 11:55:00: + * const result = startOfQuarter(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Tue Jul 01 2014 00:00:00 + */ + +function startOfQuarter(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var currentMonth = date.getMonth(); + var month = currentMonth - currentMonth % 3; + date.setMonth(month, 1); + date.setHours(0, 0, 0, 0); + return date; +} + +/** + * @name startOfMonth + * @category Month Helpers + * @summary Return the start of a month for the given date. + * + * @description + * Return the start of a month for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a month + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a month for 2 September 2014 11:55:00: + * const result = startOfMonth(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Mon Sep 01 2014 00:00:00 + */ + +function startOfMonth(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setDate(1); + date.setHours(0, 0, 0, 0); + return date; +} + +/** + * @name startOfYear + * @category Year Helpers + * @summary Return the start of a year for the given date. + * + * @description + * Return the start of a year for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a year + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a year for 2 September 2014 11:55:00: + * const result = startOfYear(new Date(2014, 8, 2, 11, 55, 00)) + * //=> Wed Jan 01 2014 00:00:00 + */ + +function startOfYear(dirtyDate) { + requiredArgs(1, arguments); + var cleanDate = toDate(dirtyDate); + var date = new Date(0); + date.setFullYear(cleanDate.getFullYear(), 0, 1); + date.setHours(0, 0, 0, 0); + return date; +} + +/** + * @name endOfYear + * @category Year Helpers + * @summary Return the end of a year for the given date. + * + * @description + * Return the end of a year for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a year + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a year for 2 September 2014 11:55:00: + * var result = endOfYear(new Date(2014, 8, 2, 11, 55, 00)) + * //=> Wed Dec 31 2014 23:59:59.999 + */ + +function endOfYear(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var year = date.getFullYear(); + date.setFullYear(year + 1, 0, 0); + date.setHours(23, 59, 59, 999); + return date; +} + +/** + * @name endOfHour + * @category Hour Helpers + * @summary Return the end of an hour for the given date. + * + * @description + * Return the end of an hour for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of an hour + * @throws {TypeError} 1 argument required + * + * @example + * // The end of an hour for 2 September 2014 11:55:00: + * const result = endOfHour(new Date(2014, 8, 2, 11, 55)) + * //=> Tue Sep 02 2014 11:59:59.999 + */ + +function endOfHour(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setMinutes(59, 59, 999); + return date; +} + +/** + * @name endOfWeek + * @category Week Helpers + * @summary Return the end of a week for the given date. + * + * @description + * Return the end of a week for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @param {Object} [options] - an object with options. + * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale} + * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday) + * @returns {Date} the end of a week + * @throws {TypeError} 1 argument required + * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6 + * + * @example + * // The end of a week for 2 September 2014 11:55:00: + * const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Sat Sep 06 2014 23:59:59.999 + * + * @example + * // If the week starts on Monday, the end of the week for 2 September 2014 11:55:00: + * const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0), { weekStartsOn: 1 }) + * //=> Sun Sep 07 2014 23:59:59.999 + */ +function endOfWeek(dirtyDate, dirtyOptions) { + requiredArgs(1, arguments); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeWeekStartsOn = locale && locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + var date = toDate(dirtyDate); + var day = date.getDay(); + var diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn); + date.setDate(date.getDate() + diff); + date.setHours(23, 59, 59, 999); + return date; +} + +/** + * @name endOfMinute + * @category Minute Helpers + * @summary Return the end of a minute for the given date. + * + * @description + * Return the end of a minute for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a minute + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a minute for 1 December 2014 22:15:45.400: + * const result = endOfMinute(new Date(2014, 11, 1, 22, 15, 45, 400)) + * //=> Mon Dec 01 2014 22:15:59.999 + */ + +function endOfMinute(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setSeconds(59, 999); + return date; +} + +/** + * @name endOfQuarter + * @category Quarter Helpers + * @summary Return the end of a year quarter for the given date. + * + * @description + * Return the end of a year quarter for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a quarter + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a quarter for 2 September 2014 11:55:00: + * const result = endOfQuarter(new Date(2014, 8, 2, 11, 55, 0)) + * //=> Tue Sep 30 2014 23:59:59.999 + */ + +function endOfQuarter(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var currentMonth = date.getMonth(); + var month = currentMonth - currentMonth % 3 + 3; + date.setMonth(month, 0); + date.setHours(23, 59, 59, 999); + return date; +} + +/** + * @name endOfSecond + * @category Second Helpers + * @summary Return the end of a second for the given date. + * + * @description + * Return the end of a second for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the end of a second + * @throws {TypeError} 1 argument required + * + * @example + * // The end of a second for 1 December 2014 22:15:45.400: + * const result = endOfSecond(new Date(2014, 11, 1, 22, 15, 45, 400)) + * //=> Mon Dec 01 2014 22:15:45.999 + */ + +function endOfSecond(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setMilliseconds(999); + return date; +} + +var formatDistanceLocale = { + lessThanXSeconds: { + one: 'less than a second', + other: 'less than {{count}} seconds' + }, + xSeconds: { + one: '1 second', + other: '{{count}} seconds' + }, + halfAMinute: 'half a minute', + lessThanXMinutes: { + one: 'less than a minute', + other: 'less than {{count}} minutes' + }, + xMinutes: { + one: '1 minute', + other: '{{count}} minutes' + }, + aboutXHours: { + one: 'about 1 hour', + other: 'about {{count}} hours' + }, + xHours: { + one: '1 hour', + other: '{{count}} hours' + }, + xDays: { + one: '1 day', + other: '{{count}} days' + }, + aboutXWeeks: { + one: 'about 1 week', + other: 'about {{count}} weeks' + }, + xWeeks: { + one: '1 week', + other: '{{count}} weeks' + }, + aboutXMonths: { + one: 'about 1 month', + other: 'about {{count}} months' + }, + xMonths: { + one: '1 month', + other: '{{count}} months' + }, + aboutXYears: { + one: 'about 1 year', + other: 'about {{count}} years' + }, + xYears: { + one: '1 year', + other: '{{count}} years' + }, + overXYears: { + one: 'over 1 year', + other: 'over {{count}} years' + }, + almostXYears: { + one: 'almost 1 year', + other: 'almost {{count}} years' + } +}; +function formatDistance(token, count, options) { + options = options || {}; + var result; + + if (typeof formatDistanceLocale[token] === 'string') { + result = formatDistanceLocale[token]; + } else if (count === 1) { + result = formatDistanceLocale[token].one; + } else { + result = formatDistanceLocale[token].other.replace('{{count}}', count); + } + + if (options.addSuffix) { + if (options.comparison > 0) { + return 'in ' + result; + } else { + return result + ' ago'; + } + } + + return result; +} + +function buildFormatLongFn(args) { + return function (dirtyOptions) { + var options = dirtyOptions || {}; + var width = options.width ? String(options.width) : args.defaultWidth; + var format = args.formats[width] || args.formats[args.defaultWidth]; + return format; + }; +} + +var dateFormats = { + full: 'EEEE, MMMM do, y', + long: 'MMMM do, y', + medium: 'MMM d, y', + short: 'MM/dd/yyyy' +}; +var timeFormats = { + full: 'h:mm:ss a zzzz', + long: 'h:mm:ss a z', + medium: 'h:mm:ss a', + short: 'h:mm a' +}; +var dateTimeFormats = { + full: "{{date}} 'at' {{time}}", + long: "{{date}} 'at' {{time}}", + medium: '{{date}}, {{time}}', + short: '{{date}}, {{time}}' +}; +var formatLong = { + date: buildFormatLongFn({ + formats: dateFormats, + defaultWidth: 'full' + }), + time: buildFormatLongFn({ + formats: timeFormats, + defaultWidth: 'full' + }), + dateTime: buildFormatLongFn({ + formats: dateTimeFormats, + defaultWidth: 'full' + }) +}; +var formatLong$1 = formatLong; + +var formatRelativeLocale = { + lastWeek: "'last' eeee 'at' p", + yesterday: "'yesterday at' p", + today: "'today at' p", + tomorrow: "'tomorrow at' p", + nextWeek: "eeee 'at' p", + other: 'P' +}; +function formatRelative(token, _date, _baseDate, _options) { + return formatRelativeLocale[token]; +} + +function buildLocalizeFn(args) { + return function (dirtyIndex, dirtyOptions) { + var options = dirtyOptions || {}; + var context = options.context ? String(options.context) : 'standalone'; + var valuesArray; + + if (context === 'formatting' && args.formattingValues) { + var defaultWidth = args.defaultFormattingWidth || args.defaultWidth; + var width = options.width ? String(options.width) : defaultWidth; + valuesArray = args.formattingValues[width] || args.formattingValues[defaultWidth]; + } else { + var _defaultWidth = args.defaultWidth; + + var _width = options.width ? String(options.width) : args.defaultWidth; + + valuesArray = args.values[_width] || args.values[_defaultWidth]; + } + + var index = args.argumentCallback ? args.argumentCallback(dirtyIndex) : dirtyIndex; + return valuesArray[index]; + }; +} + +var eraValues = { + narrow: ['B', 'A'], + abbreviated: ['BC', 'AD'], + wide: ['Before Christ', 'Anno Domini'] +}; +var quarterValues = { + narrow: ['1', '2', '3', '4'], + abbreviated: ['Q1', 'Q2', 'Q3', 'Q4'], + wide: ['1st quarter', '2nd quarter', '3rd quarter', '4th quarter'] // Note: in English, the names of days of the week and months are capitalized. + // If you are making a new locale based on this one, check if the same is true for the language you're working on. + // Generally, formatted dates should look like they are in the middle of a sentence, + // e.g. in Spanish language the weekdays and months should be in the lowercase. + +}; +var monthValues = { + narrow: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'], + abbreviated: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + wide: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] +}; +var dayValues = { + narrow: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], + short: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + abbreviated: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + wide: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] +}; +var dayPeriodValues = { + narrow: { + am: 'a', + pm: 'p', + midnight: 'mi', + noon: 'n', + morning: 'morning', + afternoon: 'afternoon', + evening: 'evening', + night: 'night' + }, + abbreviated: { + am: 'AM', + pm: 'PM', + midnight: 'midnight', + noon: 'noon', + morning: 'morning', + afternoon: 'afternoon', + evening: 'evening', + night: 'night' + }, + wide: { + am: 'a.m.', + pm: 'p.m.', + midnight: 'midnight', + noon: 'noon', + morning: 'morning', + afternoon: 'afternoon', + evening: 'evening', + night: 'night' + } +}; +var formattingDayPeriodValues = { + narrow: { + am: 'a', + pm: 'p', + midnight: 'mi', + noon: 'n', + morning: 'in the morning', + afternoon: 'in the afternoon', + evening: 'in the evening', + night: 'at night' + }, + abbreviated: { + am: 'AM', + pm: 'PM', + midnight: 'midnight', + noon: 'noon', + morning: 'in the morning', + afternoon: 'in the afternoon', + evening: 'in the evening', + night: 'at night' + }, + wide: { + am: 'a.m.', + pm: 'p.m.', + midnight: 'midnight', + noon: 'noon', + morning: 'in the morning', + afternoon: 'in the afternoon', + evening: 'in the evening', + night: 'at night' + } +}; + +function ordinalNumber(dirtyNumber, _dirtyOptions) { + var number = Number(dirtyNumber); // If ordinal numbers depend on context, for example, + // if they are different for different grammatical genders, + // use `options.unit`: + // + // var options = dirtyOptions || {} + // var unit = String(options.unit) + // + // where `unit` can be 'year', 'quarter', 'month', 'week', 'date', 'dayOfYear', + // 'day', 'hour', 'minute', 'second' + + var rem100 = number % 100; + + if (rem100 > 20 || rem100 < 10) { + switch (rem100 % 10) { + case 1: + return number + 'st'; + + case 2: + return number + 'nd'; + + case 3: + return number + 'rd'; + } + } + + return number + 'th'; +} + +var localize = { + ordinalNumber: ordinalNumber, + era: buildLocalizeFn({ + values: eraValues, + defaultWidth: 'wide' + }), + quarter: buildLocalizeFn({ + values: quarterValues, + defaultWidth: 'wide', + argumentCallback: function (quarter) { + return Number(quarter) - 1; + } + }), + month: buildLocalizeFn({ + values: monthValues, + defaultWidth: 'wide' + }), + day: buildLocalizeFn({ + values: dayValues, + defaultWidth: 'wide' + }), + dayPeriod: buildLocalizeFn({ + values: dayPeriodValues, + defaultWidth: 'wide', + formattingValues: formattingDayPeriodValues, + defaultFormattingWidth: 'wide' + }) +}; +var localize$1 = localize; + +function buildMatchPatternFn(args) { + return function (dirtyString, dirtyOptions) { + var string = String(dirtyString); + var options = dirtyOptions || {}; + var matchResult = string.match(args.matchPattern); + + if (!matchResult) { + return null; + } + + var matchedString = matchResult[0]; + var parseResult = string.match(args.parsePattern); + + if (!parseResult) { + return null; + } + + var value = args.valueCallback ? args.valueCallback(parseResult[0]) : parseResult[0]; + value = options.valueCallback ? options.valueCallback(value) : value; + return { + value: value, + rest: string.slice(matchedString.length) + }; + }; +} + +function buildMatchFn(args) { + return function (dirtyString, dirtyOptions) { + var string = String(dirtyString); + var options = dirtyOptions || {}; + var width = options.width; + var matchPattern = width && args.matchPatterns[width] || args.matchPatterns[args.defaultMatchWidth]; + var matchResult = string.match(matchPattern); + + if (!matchResult) { + return null; + } + + var matchedString = matchResult[0]; + var parsePatterns = width && args.parsePatterns[width] || args.parsePatterns[args.defaultParseWidth]; + var value; + + if (Object.prototype.toString.call(parsePatterns) === '[object Array]') { + value = findIndex(parsePatterns, function (pattern) { + return pattern.test(matchedString); + }); + } else { + value = findKey(parsePatterns, function (pattern) { + return pattern.test(matchedString); + }); + } + + value = args.valueCallback ? args.valueCallback(value) : value; + value = options.valueCallback ? options.valueCallback(value) : value; + return { + value: value, + rest: string.slice(matchedString.length) + }; + }; +} + +function findKey(object, predicate) { + for (var key in object) { + if (object.hasOwnProperty(key) && predicate(object[key])) { + return key; + } + } +} + +function findIndex(array, predicate) { + for (var key = 0; key < array.length; key++) { + if (predicate(array[key])) { + return key; + } + } +} + +var matchOrdinalNumberPattern = /^(\d+)(th|st|nd|rd)?/i; +var parseOrdinalNumberPattern = /\d+/i; +var matchEraPatterns = { + narrow: /^(b|a)/i, + abbreviated: /^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i, + wide: /^(before christ|before common era|anno domini|common era)/i +}; +var parseEraPatterns = { + any: [/^b/i, /^(a|c)/i] +}; +var matchQuarterPatterns = { + narrow: /^[1234]/i, + abbreviated: /^q[1234]/i, + wide: /^[1234](th|st|nd|rd)? quarter/i +}; +var parseQuarterPatterns = { + any: [/1/i, /2/i, /3/i, /4/i] +}; +var matchMonthPatterns = { + narrow: /^[jfmasond]/i, + abbreviated: /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i, + wide: /^(january|february|march|april|may|june|july|august|september|october|november|december)/i +}; +var parseMonthPatterns = { + narrow: [/^j/i, /^f/i, /^m/i, /^a/i, /^m/i, /^j/i, /^j/i, /^a/i, /^s/i, /^o/i, /^n/i, /^d/i], + any: [/^ja/i, /^f/i, /^mar/i, /^ap/i, /^may/i, /^jun/i, /^jul/i, /^au/i, /^s/i, /^o/i, /^n/i, /^d/i] +}; +var matchDayPatterns = { + narrow: /^[smtwf]/i, + short: /^(su|mo|tu|we|th|fr|sa)/i, + abbreviated: /^(sun|mon|tue|wed|thu|fri|sat)/i, + wide: /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i +}; +var parseDayPatterns = { + narrow: [/^s/i, /^m/i, /^t/i, /^w/i, /^t/i, /^f/i, /^s/i], + any: [/^su/i, /^m/i, /^tu/i, /^w/i, /^th/i, /^f/i, /^sa/i] +}; +var matchDayPeriodPatterns = { + narrow: /^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i, + any: /^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i +}; +var parseDayPeriodPatterns = { + any: { + am: /^a/i, + pm: /^p/i, + midnight: /^mi/i, + noon: /^no/i, + morning: /morning/i, + afternoon: /afternoon/i, + evening: /evening/i, + night: /night/i + } +}; +var match = { + ordinalNumber: buildMatchPatternFn({ + matchPattern: matchOrdinalNumberPattern, + parsePattern: parseOrdinalNumberPattern, + valueCallback: function (value) { + return parseInt(value, 10); + } + }), + era: buildMatchFn({ + matchPatterns: matchEraPatterns, + defaultMatchWidth: 'wide', + parsePatterns: parseEraPatterns, + defaultParseWidth: 'any' + }), + quarter: buildMatchFn({ + matchPatterns: matchQuarterPatterns, + defaultMatchWidth: 'wide', + parsePatterns: parseQuarterPatterns, + defaultParseWidth: 'any', + valueCallback: function (index) { + return index + 1; + } + }), + month: buildMatchFn({ + matchPatterns: matchMonthPatterns, + defaultMatchWidth: 'wide', + parsePatterns: parseMonthPatterns, + defaultParseWidth: 'any' + }), + day: buildMatchFn({ + matchPatterns: matchDayPatterns, + defaultMatchWidth: 'wide', + parsePatterns: parseDayPatterns, + defaultParseWidth: 'any' + }), + dayPeriod: buildMatchFn({ + matchPatterns: matchDayPeriodPatterns, + defaultMatchWidth: 'any', + parsePatterns: parseDayPeriodPatterns, + defaultParseWidth: 'any' + }) +}; +var match$1 = match; + +/** + * @type {Locale} + * @category Locales + * @summary English locale (United States). + * @language English + * @iso-639-2 eng + * @author Sasha Koss [@kossnocorp]{@link https://github.com/kossnocorp} + * @author Lesha Koss [@leshakoss]{@link https://github.com/leshakoss} + */ + +var locale = { + code: 'en-US', + formatDistance: formatDistance, + formatLong: formatLong$1, + formatRelative: formatRelative, + localize: localize$1, + match: match$1, + options: { + weekStartsOn: 0 + /* Sunday */ + , + firstWeekContainsDate: 1 + } +}; +var defaultLocale = locale; + +/** + * @name subMilliseconds + * @category Millisecond Helpers + * @summary Subtract the specified number of milliseconds from the given date. + * + * @description + * Subtract the specified number of milliseconds from the given date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the date to be changed + * @param {Number} amount - the amount of milliseconds to be subtracted. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`. + * @returns {Date} the new date with the milliseconds subtracted + * @throws {TypeError} 2 arguments required + * + * @example + * // Subtract 750 milliseconds from 10 July 2014 12:45:30.000: + * const result = subMilliseconds(new Date(2014, 6, 10, 12, 45, 30, 0), 750) + * //=> Thu Jul 10 2014 12:45:29.250 + */ + +function subMilliseconds(dirtyDate, dirtyAmount) { + requiredArgs(2, arguments); + var amount = toInteger(dirtyAmount); + return addMilliseconds(dirtyDate, -amount); +} + +function addLeadingZeros(number, targetLength) { + var sign = number < 0 ? '-' : ''; + var output = Math.abs(number).toString(); + + while (output.length < targetLength) { + output = '0' + output; + } + + return sign + output; +} + +/* + * | | Unit | | Unit | + * |-----|--------------------------------|-----|--------------------------------| + * | a | AM, PM | A* | | + * | d | Day of month | D | | + * | h | Hour [1-12] | H | Hour [0-23] | + * | m | Minute | M | Month | + * | s | Second | S | Fraction of second | + * | y | Year (abs) | Y | | + * + * Letters marked by * are not implemented but reserved by Unicode standard. + */ + +var formatters$2 = { + // Year + y: function (date, token) { + // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_tokens + // | Year | y | yy | yyy | yyyy | yyyyy | + // |----------|-------|----|-------|-------|-------| + // | AD 1 | 1 | 01 | 001 | 0001 | 00001 | + // | AD 12 | 12 | 12 | 012 | 0012 | 00012 | + // | AD 123 | 123 | 23 | 123 | 0123 | 00123 | + // | AD 1234 | 1234 | 34 | 1234 | 1234 | 01234 | + // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 | + var signedYear = date.getUTCFullYear(); // Returns 1 for 1 BC (which is year 0 in JavaScript) + + var year = signedYear > 0 ? signedYear : 1 - signedYear; + return addLeadingZeros(token === 'yy' ? year % 100 : year, token.length); + }, + // Month + M: function (date, token) { + var month = date.getUTCMonth(); + return token === 'M' ? String(month + 1) : addLeadingZeros(month + 1, 2); + }, + // Day of the month + d: function (date, token) { + return addLeadingZeros(date.getUTCDate(), token.length); + }, + // AM or PM + a: function (date, token) { + var dayPeriodEnumValue = date.getUTCHours() / 12 >= 1 ? 'pm' : 'am'; + + switch (token) { + case 'a': + case 'aa': + return dayPeriodEnumValue.toUpperCase(); + + case 'aaa': + return dayPeriodEnumValue; + + case 'aaaaa': + return dayPeriodEnumValue[0]; + + case 'aaaa': + default: + return dayPeriodEnumValue === 'am' ? 'a.m.' : 'p.m.'; + } + }, + // Hour [1-12] + h: function (date, token) { + return addLeadingZeros(date.getUTCHours() % 12 || 12, token.length); + }, + // Hour [0-23] + H: function (date, token) { + return addLeadingZeros(date.getUTCHours(), token.length); + }, + // Minute + m: function (date, token) { + return addLeadingZeros(date.getUTCMinutes(), token.length); + }, + // Second + s: function (date, token) { + return addLeadingZeros(date.getUTCSeconds(), token.length); + }, + // Fraction of second + S: function (date, token) { + var numberOfDigits = token.length; + var milliseconds = date.getUTCMilliseconds(); + var fractionalSeconds = Math.floor(milliseconds * Math.pow(10, numberOfDigits - 3)); + return addLeadingZeros(fractionalSeconds, token.length); + } +}; +var formatters$3 = formatters$2; + +var MILLISECONDS_IN_DAY = 86400000; // This function will be a part of public API when UTC function will be implemented. +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function getUTCDayOfYear(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var timestamp = date.getTime(); + date.setUTCMonth(0, 1); + date.setUTCHours(0, 0, 0, 0); + var startOfYearTimestamp = date.getTime(); + var difference = timestamp - startOfYearTimestamp; + return Math.floor(difference / MILLISECONDS_IN_DAY) + 1; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function startOfUTCISOWeek(dirtyDate) { + requiredArgs(1, arguments); + var weekStartsOn = 1; + var date = toDate(dirtyDate); + var day = date.getUTCDay(); + var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn; + date.setUTCDate(date.getUTCDate() - diff); + date.setUTCHours(0, 0, 0, 0); + return date; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function getUTCISOWeekYear(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var year = date.getUTCFullYear(); + var fourthOfJanuaryOfNextYear = new Date(0); + fourthOfJanuaryOfNextYear.setUTCFullYear(year + 1, 0, 4); + fourthOfJanuaryOfNextYear.setUTCHours(0, 0, 0, 0); + var startOfNextYear = startOfUTCISOWeek(fourthOfJanuaryOfNextYear); + var fourthOfJanuaryOfThisYear = new Date(0); + fourthOfJanuaryOfThisYear.setUTCFullYear(year, 0, 4); + fourthOfJanuaryOfThisYear.setUTCHours(0, 0, 0, 0); + var startOfThisYear = startOfUTCISOWeek(fourthOfJanuaryOfThisYear); + + if (date.getTime() >= startOfNextYear.getTime()) { + return year + 1; + } else if (date.getTime() >= startOfThisYear.getTime()) { + return year; + } else { + return year - 1; + } +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function startOfUTCISOWeekYear(dirtyDate) { + requiredArgs(1, arguments); + var year = getUTCISOWeekYear(dirtyDate); + var fourthOfJanuary = new Date(0); + fourthOfJanuary.setUTCFullYear(year, 0, 4); + fourthOfJanuary.setUTCHours(0, 0, 0, 0); + var date = startOfUTCISOWeek(fourthOfJanuary); + return date; +} + +var MILLISECONDS_IN_WEEK$1 = 604800000; // This function will be a part of public API when UTC function will be implemented. +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function getUTCISOWeek(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var diff = startOfUTCISOWeek(date).getTime() - startOfUTCISOWeekYear(date).getTime(); // Round the number of days to the nearest integer + // because the number of milliseconds in a week is not constant + // (e.g. it's different in the week of the daylight saving time clock shift) + + return Math.round(diff / MILLISECONDS_IN_WEEK$1) + 1; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function startOfUTCWeek(dirtyDate, dirtyOptions) { + requiredArgs(1, arguments); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeWeekStartsOn = locale && locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + var date = toDate(dirtyDate); + var day = date.getUTCDay(); + var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn; + date.setUTCDate(date.getUTCDate() - diff); + date.setUTCHours(0, 0, 0, 0); + return date; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function getUTCWeekYear(dirtyDate, dirtyOptions) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate, dirtyOptions); + var year = date.getUTCFullYear(); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeFirstWeekContainsDate = locale && locale.options && locale.options.firstWeekContainsDate; + var defaultFirstWeekContainsDate = localeFirstWeekContainsDate == null ? 1 : toInteger(localeFirstWeekContainsDate); + var firstWeekContainsDate = options.firstWeekContainsDate == null ? defaultFirstWeekContainsDate : toInteger(options.firstWeekContainsDate); // Test if weekStartsOn is between 1 and 7 _and_ is not NaN + + if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) { + throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively'); + } + + var firstWeekOfNextYear = new Date(0); + firstWeekOfNextYear.setUTCFullYear(year + 1, 0, firstWeekContainsDate); + firstWeekOfNextYear.setUTCHours(0, 0, 0, 0); + var startOfNextYear = startOfUTCWeek(firstWeekOfNextYear, dirtyOptions); + var firstWeekOfThisYear = new Date(0); + firstWeekOfThisYear.setUTCFullYear(year, 0, firstWeekContainsDate); + firstWeekOfThisYear.setUTCHours(0, 0, 0, 0); + var startOfThisYear = startOfUTCWeek(firstWeekOfThisYear, dirtyOptions); + + if (date.getTime() >= startOfNextYear.getTime()) { + return year + 1; + } else if (date.getTime() >= startOfThisYear.getTime()) { + return year; + } else { + return year - 1; + } +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function startOfUTCWeekYear(dirtyDate, dirtyOptions) { + requiredArgs(1, arguments); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeFirstWeekContainsDate = locale && locale.options && locale.options.firstWeekContainsDate; + var defaultFirstWeekContainsDate = localeFirstWeekContainsDate == null ? 1 : toInteger(localeFirstWeekContainsDate); + var firstWeekContainsDate = options.firstWeekContainsDate == null ? defaultFirstWeekContainsDate : toInteger(options.firstWeekContainsDate); + var year = getUTCWeekYear(dirtyDate, dirtyOptions); + var firstWeek = new Date(0); + firstWeek.setUTCFullYear(year, 0, firstWeekContainsDate); + firstWeek.setUTCHours(0, 0, 0, 0); + var date = startOfUTCWeek(firstWeek, dirtyOptions); + return date; +} + +var MILLISECONDS_IN_WEEK = 604800000; // This function will be a part of public API when UTC function will be implemented. +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function getUTCWeek(dirtyDate, options) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + var diff = startOfUTCWeek(date, options).getTime() - startOfUTCWeekYear(date, options).getTime(); // Round the number of days to the nearest integer + // because the number of milliseconds in a week is not constant + // (e.g. it's different in the week of the daylight saving time clock shift) + + return Math.round(diff / MILLISECONDS_IN_WEEK) + 1; +} + +var dayPeriodEnum = { + am: 'am', + pm: 'pm', + midnight: 'midnight', + noon: 'noon', + morning: 'morning', + afternoon: 'afternoon', + evening: 'evening', + night: 'night' + /* + * | | Unit | | Unit | + * |-----|--------------------------------|-----|--------------------------------| + * | a | AM, PM | A* | Milliseconds in day | + * | b | AM, PM, noon, midnight | B | Flexible day period | + * | c | Stand-alone local day of week | C* | Localized hour w/ day period | + * | d | Day of month | D | Day of year | + * | e | Local day of week | E | Day of week | + * | f | | F* | Day of week in month | + * | g* | Modified Julian day | G | Era | + * | h | Hour [1-12] | H | Hour [0-23] | + * | i! | ISO day of week | I! | ISO week of year | + * | j* | Localized hour w/ day period | J* | Localized hour w/o day period | + * | k | Hour [1-24] | K | Hour [0-11] | + * | l* | (deprecated) | L | Stand-alone month | + * | m | Minute | M | Month | + * | n | | N | | + * | o! | Ordinal number modifier | O | Timezone (GMT) | + * | p! | Long localized time | P! | Long localized date | + * | q | Stand-alone quarter | Q | Quarter | + * | r* | Related Gregorian year | R! | ISO week-numbering year | + * | s | Second | S | Fraction of second | + * | t! | Seconds timestamp | T! | Milliseconds timestamp | + * | u | Extended year | U* | Cyclic year | + * | v* | Timezone (generic non-locat.) | V* | Timezone (location) | + * | w | Local week of year | W* | Week of month | + * | x | Timezone (ISO-8601 w/o Z) | X | Timezone (ISO-8601) | + * | y | Year (abs) | Y | Local week-numbering year | + * | z | Timezone (specific non-locat.) | Z* | Timezone (aliases) | + * + * Letters marked by * are not implemented but reserved by Unicode standard. + * + * Letters marked by ! are non-standard, but implemented by date-fns: + * - `o` modifies the previous token to turn it into an ordinal (see `format` docs) + * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days, + * i.e. 7 for Sunday, 1 for Monday, etc. + * - `I` is ISO week of year, as opposed to `w` which is local week of year. + * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year. + * `R` is supposed to be used in conjunction with `I` and `i` + * for universal ISO week-numbering date, whereas + * `Y` is supposed to be used in conjunction with `w` and `e` + * for week-numbering date specific to the locale. + * - `P` is long localized date format + * - `p` is long localized time format + */ + +}; +var formatters = { + // Era + G: function (date, token, localize) { + var era = date.getUTCFullYear() > 0 ? 1 : 0; + + switch (token) { + // AD, BC + case 'G': + case 'GG': + case 'GGG': + return localize.era(era, { + width: 'abbreviated' + }); + // A, B + + case 'GGGGG': + return localize.era(era, { + width: 'narrow' + }); + // Anno Domini, Before Christ + + case 'GGGG': + default: + return localize.era(era, { + width: 'wide' + }); + } + }, + // Year + y: function (date, token, localize) { + // Ordinal number + if (token === 'yo') { + var signedYear = date.getUTCFullYear(); // Returns 1 for 1 BC (which is year 0 in JavaScript) + + var year = signedYear > 0 ? signedYear : 1 - signedYear; + return localize.ordinalNumber(year, { + unit: 'year' + }); + } + + return formatters$3.y(date, token); + }, + // Local week-numbering year + Y: function (date, token, localize, options) { + var signedWeekYear = getUTCWeekYear(date, options); // Returns 1 for 1 BC (which is year 0 in JavaScript) + + var weekYear = signedWeekYear > 0 ? signedWeekYear : 1 - signedWeekYear; // Two digit year + + if (token === 'YY') { + var twoDigitYear = weekYear % 100; + return addLeadingZeros(twoDigitYear, 2); + } // Ordinal number + + + if (token === 'Yo') { + return localize.ordinalNumber(weekYear, { + unit: 'year' + }); + } // Padding + + + return addLeadingZeros(weekYear, token.length); + }, + // ISO week-numbering year + R: function (date, token) { + var isoWeekYear = getUTCISOWeekYear(date); // Padding + + return addLeadingZeros(isoWeekYear, token.length); + }, + // Extended year. This is a single number designating the year of this calendar system. + // The main difference between `y` and `u` localizers are B.C. years: + // | Year | `y` | `u` | + // |------|-----|-----| + // | AC 1 | 1 | 1 | + // | BC 1 | 1 | 0 | + // | BC 2 | 2 | -1 | + // Also `yy` always returns the last two digits of a year, + // while `uu` pads single digit years to 2 characters and returns other years unchanged. + u: function (date, token) { + var year = date.getUTCFullYear(); + return addLeadingZeros(year, token.length); + }, + // Quarter + Q: function (date, token, localize) { + var quarter = Math.ceil((date.getUTCMonth() + 1) / 3); + + switch (token) { + // 1, 2, 3, 4 + case 'Q': + return String(quarter); + // 01, 02, 03, 04 + + case 'QQ': + return addLeadingZeros(quarter, 2); + // 1st, 2nd, 3rd, 4th + + case 'Qo': + return localize.ordinalNumber(quarter, { + unit: 'quarter' + }); + // Q1, Q2, Q3, Q4 + + case 'QQQ': + return localize.quarter(quarter, { + width: 'abbreviated', + context: 'formatting' + }); + // 1, 2, 3, 4 (narrow quarter; could be not numerical) + + case 'QQQQQ': + return localize.quarter(quarter, { + width: 'narrow', + context: 'formatting' + }); + // 1st quarter, 2nd quarter, ... + + case 'QQQQ': + default: + return localize.quarter(quarter, { + width: 'wide', + context: 'formatting' + }); + } + }, + // Stand-alone quarter + q: function (date, token, localize) { + var quarter = Math.ceil((date.getUTCMonth() + 1) / 3); + + switch (token) { + // 1, 2, 3, 4 + case 'q': + return String(quarter); + // 01, 02, 03, 04 + + case 'qq': + return addLeadingZeros(quarter, 2); + // 1st, 2nd, 3rd, 4th + + case 'qo': + return localize.ordinalNumber(quarter, { + unit: 'quarter' + }); + // Q1, Q2, Q3, Q4 + + case 'qqq': + return localize.quarter(quarter, { + width: 'abbreviated', + context: 'standalone' + }); + // 1, 2, 3, 4 (narrow quarter; could be not numerical) + + case 'qqqqq': + return localize.quarter(quarter, { + width: 'narrow', + context: 'standalone' + }); + // 1st quarter, 2nd quarter, ... + + case 'qqqq': + default: + return localize.quarter(quarter, { + width: 'wide', + context: 'standalone' + }); + } + }, + // Month + M: function (date, token, localize) { + var month = date.getUTCMonth(); + + switch (token) { + case 'M': + case 'MM': + return formatters$3.M(date, token); + // 1st, 2nd, ..., 12th + + case 'Mo': + return localize.ordinalNumber(month + 1, { + unit: 'month' + }); + // Jan, Feb, ..., Dec + + case 'MMM': + return localize.month(month, { + width: 'abbreviated', + context: 'formatting' + }); + // J, F, ..., D + + case 'MMMMM': + return localize.month(month, { + width: 'narrow', + context: 'formatting' + }); + // January, February, ..., December + + case 'MMMM': + default: + return localize.month(month, { + width: 'wide', + context: 'formatting' + }); + } + }, + // Stand-alone month + L: function (date, token, localize) { + var month = date.getUTCMonth(); + + switch (token) { + // 1, 2, ..., 12 + case 'L': + return String(month + 1); + // 01, 02, ..., 12 + + case 'LL': + return addLeadingZeros(month + 1, 2); + // 1st, 2nd, ..., 12th + + case 'Lo': + return localize.ordinalNumber(month + 1, { + unit: 'month' + }); + // Jan, Feb, ..., Dec + + case 'LLL': + return localize.month(month, { + width: 'abbreviated', + context: 'standalone' + }); + // J, F, ..., D + + case 'LLLLL': + return localize.month(month, { + width: 'narrow', + context: 'standalone' + }); + // January, February, ..., December + + case 'LLLL': + default: + return localize.month(month, { + width: 'wide', + context: 'standalone' + }); + } + }, + // Local week of year + w: function (date, token, localize, options) { + var week = getUTCWeek(date, options); + + if (token === 'wo') { + return localize.ordinalNumber(week, { + unit: 'week' + }); + } + + return addLeadingZeros(week, token.length); + }, + // ISO week of year + I: function (date, token, localize) { + var isoWeek = getUTCISOWeek(date); + + if (token === 'Io') { + return localize.ordinalNumber(isoWeek, { + unit: 'week' + }); + } + + return addLeadingZeros(isoWeek, token.length); + }, + // Day of the month + d: function (date, token, localize) { + if (token === 'do') { + return localize.ordinalNumber(date.getUTCDate(), { + unit: 'date' + }); + } + + return formatters$3.d(date, token); + }, + // Day of year + D: function (date, token, localize) { + var dayOfYear = getUTCDayOfYear(date); + + if (token === 'Do') { + return localize.ordinalNumber(dayOfYear, { + unit: 'dayOfYear' + }); + } + + return addLeadingZeros(dayOfYear, token.length); + }, + // Day of week + E: function (date, token, localize) { + var dayOfWeek = date.getUTCDay(); + + switch (token) { + // Tue + case 'E': + case 'EE': + case 'EEE': + return localize.day(dayOfWeek, { + width: 'abbreviated', + context: 'formatting' + }); + // T + + case 'EEEEE': + return localize.day(dayOfWeek, { + width: 'narrow', + context: 'formatting' + }); + // Tu + + case 'EEEEEE': + return localize.day(dayOfWeek, { + width: 'short', + context: 'formatting' + }); + // Tuesday + + case 'EEEE': + default: + return localize.day(dayOfWeek, { + width: 'wide', + context: 'formatting' + }); + } + }, + // Local day of week + e: function (date, token, localize, options) { + var dayOfWeek = date.getUTCDay(); + var localDayOfWeek = (dayOfWeek - options.weekStartsOn + 8) % 7 || 7; + + switch (token) { + // Numerical value (Nth day of week with current locale or weekStartsOn) + case 'e': + return String(localDayOfWeek); + // Padded numerical value + + case 'ee': + return addLeadingZeros(localDayOfWeek, 2); + // 1st, 2nd, ..., 7th + + case 'eo': + return localize.ordinalNumber(localDayOfWeek, { + unit: 'day' + }); + + case 'eee': + return localize.day(dayOfWeek, { + width: 'abbreviated', + context: 'formatting' + }); + // T + + case 'eeeee': + return localize.day(dayOfWeek, { + width: 'narrow', + context: 'formatting' + }); + // Tu + + case 'eeeeee': + return localize.day(dayOfWeek, { + width: 'short', + context: 'formatting' + }); + // Tuesday + + case 'eeee': + default: + return localize.day(dayOfWeek, { + width: 'wide', + context: 'formatting' + }); + } + }, + // Stand-alone local day of week + c: function (date, token, localize, options) { + var dayOfWeek = date.getUTCDay(); + var localDayOfWeek = (dayOfWeek - options.weekStartsOn + 8) % 7 || 7; + + switch (token) { + // Numerical value (same as in `e`) + case 'c': + return String(localDayOfWeek); + // Padded numerical value + + case 'cc': + return addLeadingZeros(localDayOfWeek, token.length); + // 1st, 2nd, ..., 7th + + case 'co': + return localize.ordinalNumber(localDayOfWeek, { + unit: 'day' + }); + + case 'ccc': + return localize.day(dayOfWeek, { + width: 'abbreviated', + context: 'standalone' + }); + // T + + case 'ccccc': + return localize.day(dayOfWeek, { + width: 'narrow', + context: 'standalone' + }); + // Tu + + case 'cccccc': + return localize.day(dayOfWeek, { + width: 'short', + context: 'standalone' + }); + // Tuesday + + case 'cccc': + default: + return localize.day(dayOfWeek, { + width: 'wide', + context: 'standalone' + }); + } + }, + // ISO day of week + i: function (date, token, localize) { + var dayOfWeek = date.getUTCDay(); + var isoDayOfWeek = dayOfWeek === 0 ? 7 : dayOfWeek; + + switch (token) { + // 2 + case 'i': + return String(isoDayOfWeek); + // 02 + + case 'ii': + return addLeadingZeros(isoDayOfWeek, token.length); + // 2nd + + case 'io': + return localize.ordinalNumber(isoDayOfWeek, { + unit: 'day' + }); + // Tue + + case 'iii': + return localize.day(dayOfWeek, { + width: 'abbreviated', + context: 'formatting' + }); + // T + + case 'iiiii': + return localize.day(dayOfWeek, { + width: 'narrow', + context: 'formatting' + }); + // Tu + + case 'iiiiii': + return localize.day(dayOfWeek, { + width: 'short', + context: 'formatting' + }); + // Tuesday + + case 'iiii': + default: + return localize.day(dayOfWeek, { + width: 'wide', + context: 'formatting' + }); + } + }, + // AM or PM + a: function (date, token, localize) { + var hours = date.getUTCHours(); + var dayPeriodEnumValue = hours / 12 >= 1 ? 'pm' : 'am'; + + switch (token) { + case 'a': + case 'aa': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'abbreviated', + context: 'formatting' + }); + + case 'aaa': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'abbreviated', + context: 'formatting' + }).toLowerCase(); + + case 'aaaaa': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'narrow', + context: 'formatting' + }); + + case 'aaaa': + default: + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'wide', + context: 'formatting' + }); + } + }, + // AM, PM, midnight, noon + b: function (date, token, localize) { + var hours = date.getUTCHours(); + var dayPeriodEnumValue; + + if (hours === 12) { + dayPeriodEnumValue = dayPeriodEnum.noon; + } else if (hours === 0) { + dayPeriodEnumValue = dayPeriodEnum.midnight; + } else { + dayPeriodEnumValue = hours / 12 >= 1 ? 'pm' : 'am'; + } + + switch (token) { + case 'b': + case 'bb': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'abbreviated', + context: 'formatting' + }); + + case 'bbb': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'abbreviated', + context: 'formatting' + }).toLowerCase(); + + case 'bbbbb': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'narrow', + context: 'formatting' + }); + + case 'bbbb': + default: + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'wide', + context: 'formatting' + }); + } + }, + // in the morning, in the afternoon, in the evening, at night + B: function (date, token, localize) { + var hours = date.getUTCHours(); + var dayPeriodEnumValue; + + if (hours >= 17) { + dayPeriodEnumValue = dayPeriodEnum.evening; + } else if (hours >= 12) { + dayPeriodEnumValue = dayPeriodEnum.afternoon; + } else if (hours >= 4) { + dayPeriodEnumValue = dayPeriodEnum.morning; + } else { + dayPeriodEnumValue = dayPeriodEnum.night; + } + + switch (token) { + case 'B': + case 'BB': + case 'BBB': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'abbreviated', + context: 'formatting' + }); + + case 'BBBBB': + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'narrow', + context: 'formatting' + }); + + case 'BBBB': + default: + return localize.dayPeriod(dayPeriodEnumValue, { + width: 'wide', + context: 'formatting' + }); + } + }, + // Hour [1-12] + h: function (date, token, localize) { + if (token === 'ho') { + var hours = date.getUTCHours() % 12; + if (hours === 0) hours = 12; + return localize.ordinalNumber(hours, { + unit: 'hour' + }); + } + + return formatters$3.h(date, token); + }, + // Hour [0-23] + H: function (date, token, localize) { + if (token === 'Ho') { + return localize.ordinalNumber(date.getUTCHours(), { + unit: 'hour' + }); + } + + return formatters$3.H(date, token); + }, + // Hour [0-11] + K: function (date, token, localize) { + var hours = date.getUTCHours() % 12; + + if (token === 'Ko') { + return localize.ordinalNumber(hours, { + unit: 'hour' + }); + } + + return addLeadingZeros(hours, token.length); + }, + // Hour [1-24] + k: function (date, token, localize) { + var hours = date.getUTCHours(); + if (hours === 0) hours = 24; + + if (token === 'ko') { + return localize.ordinalNumber(hours, { + unit: 'hour' + }); + } + + return addLeadingZeros(hours, token.length); + }, + // Minute + m: function (date, token, localize) { + if (token === 'mo') { + return localize.ordinalNumber(date.getUTCMinutes(), { + unit: 'minute' + }); + } + + return formatters$3.m(date, token); + }, + // Second + s: function (date, token, localize) { + if (token === 'so') { + return localize.ordinalNumber(date.getUTCSeconds(), { + unit: 'second' + }); + } + + return formatters$3.s(date, token); + }, + // Fraction of second + S: function (date, token) { + return formatters$3.S(date, token); + }, + // Timezone (ISO-8601. If offset is 0, output is always `'Z'`) + X: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timezoneOffset = originalDate.getTimezoneOffset(); + + if (timezoneOffset === 0) { + return 'Z'; + } + + switch (token) { + // Hours and optional minutes + case 'X': + return formatTimezoneWithOptionalMinutes(timezoneOffset); + // Hours, minutes and optional seconds without `:` delimiter + // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets + // so this token always has the same output as `XX` + + case 'XXXX': + case 'XX': + // Hours and minutes without `:` delimiter + return formatTimezone(timezoneOffset); + // Hours, minutes and optional seconds with `:` delimiter + // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets + // so this token always has the same output as `XXX` + + case 'XXXXX': + case 'XXX': // Hours and minutes with `:` delimiter + + default: + return formatTimezone(timezoneOffset, ':'); + } + }, + // Timezone (ISO-8601. If offset is 0, output is `'+00:00'` or equivalent) + x: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timezoneOffset = originalDate.getTimezoneOffset(); + + switch (token) { + // Hours and optional minutes + case 'x': + return formatTimezoneWithOptionalMinutes(timezoneOffset); + // Hours, minutes and optional seconds without `:` delimiter + // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets + // so this token always has the same output as `xx` + + case 'xxxx': + case 'xx': + // Hours and minutes without `:` delimiter + return formatTimezone(timezoneOffset); + // Hours, minutes and optional seconds with `:` delimiter + // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets + // so this token always has the same output as `xxx` + + case 'xxxxx': + case 'xxx': // Hours and minutes with `:` delimiter + + default: + return formatTimezone(timezoneOffset, ':'); + } + }, + // Timezone (GMT) + O: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timezoneOffset = originalDate.getTimezoneOffset(); + + switch (token) { + // Short + case 'O': + case 'OO': + case 'OOO': + return 'GMT' + formatTimezoneShort(timezoneOffset, ':'); + // Long + + case 'OOOO': + default: + return 'GMT' + formatTimezone(timezoneOffset, ':'); + } + }, + // Timezone (specific non-location) + z: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timezoneOffset = originalDate.getTimezoneOffset(); + + switch (token) { + // Short + case 'z': + case 'zz': + case 'zzz': + return 'GMT' + formatTimezoneShort(timezoneOffset, ':'); + // Long + + case 'zzzz': + default: + return 'GMT' + formatTimezone(timezoneOffset, ':'); + } + }, + // Seconds timestamp + t: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timestamp = Math.floor(originalDate.getTime() / 1000); + return addLeadingZeros(timestamp, token.length); + }, + // Milliseconds timestamp + T: function (date, token, _localize, options) { + var originalDate = options._originalDate || date; + var timestamp = originalDate.getTime(); + return addLeadingZeros(timestamp, token.length); + } +}; + +function formatTimezoneShort(offset, dirtyDelimiter) { + var sign = offset > 0 ? '-' : '+'; + var absOffset = Math.abs(offset); + var hours = Math.floor(absOffset / 60); + var minutes = absOffset % 60; + + if (minutes === 0) { + return sign + String(hours); + } + + var delimiter = dirtyDelimiter || ''; + return sign + String(hours) + delimiter + addLeadingZeros(minutes, 2); +} + +function formatTimezoneWithOptionalMinutes(offset, dirtyDelimiter) { + if (offset % 60 === 0) { + var sign = offset > 0 ? '-' : '+'; + return sign + addLeadingZeros(Math.abs(offset) / 60, 2); + } + + return formatTimezone(offset, dirtyDelimiter); +} + +function formatTimezone(offset, dirtyDelimiter) { + var delimiter = dirtyDelimiter || ''; + var sign = offset > 0 ? '-' : '+'; + var absOffset = Math.abs(offset); + var hours = addLeadingZeros(Math.floor(absOffset / 60), 2); + var minutes = addLeadingZeros(absOffset % 60, 2); + return sign + hours + delimiter + minutes; +} + +var formatters$1 = formatters; + +function dateLongFormatter(pattern, formatLong) { + switch (pattern) { + case 'P': + return formatLong.date({ + width: 'short' + }); + + case 'PP': + return formatLong.date({ + width: 'medium' + }); + + case 'PPP': + return formatLong.date({ + width: 'long' + }); + + case 'PPPP': + default: + return formatLong.date({ + width: 'full' + }); + } +} + +function timeLongFormatter(pattern, formatLong) { + switch (pattern) { + case 'p': + return formatLong.time({ + width: 'short' + }); + + case 'pp': + return formatLong.time({ + width: 'medium' + }); + + case 'ppp': + return formatLong.time({ + width: 'long' + }); + + case 'pppp': + default: + return formatLong.time({ + width: 'full' + }); + } +} + +function dateTimeLongFormatter(pattern, formatLong) { + var matchResult = pattern.match(/(P+)(p+)?/); + var datePattern = matchResult[1]; + var timePattern = matchResult[2]; + + if (!timePattern) { + return dateLongFormatter(pattern, formatLong); + } + + var dateTimeFormat; + + switch (datePattern) { + case 'P': + dateTimeFormat = formatLong.dateTime({ + width: 'short' + }); + break; + + case 'PP': + dateTimeFormat = formatLong.dateTime({ + width: 'medium' + }); + break; + + case 'PPP': + dateTimeFormat = formatLong.dateTime({ + width: 'long' + }); + break; + + case 'PPPP': + default: + dateTimeFormat = formatLong.dateTime({ + width: 'full' + }); + break; + } + + return dateTimeFormat.replace('{{date}}', dateLongFormatter(datePattern, formatLong)).replace('{{time}}', timeLongFormatter(timePattern, formatLong)); +} + +var longFormatters = { + p: timeLongFormatter, + P: dateTimeLongFormatter +}; +var longFormatters$1 = longFormatters; + +var protectedDayOfYearTokens = ['D', 'DD']; +var protectedWeekYearTokens = ['YY', 'YYYY']; +function isProtectedDayOfYearToken(token) { + return protectedDayOfYearTokens.indexOf(token) !== -1; +} +function isProtectedWeekYearToken(token) { + return protectedWeekYearTokens.indexOf(token) !== -1; +} +function throwProtectedError(token, format, input) { + if (token === 'YYYY') { + throw new RangeError("Use `yyyy` instead of `YYYY` (in `".concat(format, "`) for formatting years to the input `").concat(input, "`; see: https://git.io/fxCyr")); + } else if (token === 'YY') { + throw new RangeError("Use `yy` instead of `YY` (in `".concat(format, "`) for formatting years to the input `").concat(input, "`; see: https://git.io/fxCyr")); + } else if (token === 'D') { + throw new RangeError("Use `d` instead of `D` (in `".concat(format, "`) for formatting days of the month to the input `").concat(input, "`; see: https://git.io/fxCyr")); + } else if (token === 'DD') { + throw new RangeError("Use `dd` instead of `DD` (in `".concat(format, "`) for formatting days of the month to the input `").concat(input, "`; see: https://git.io/fxCyr")); + } +} + +// - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token +// (one of the certain letters followed by `o`) +// - (\w)\1* matches any sequences of the same letter +// - '' matches two quote characters in a row +// - '(''|[^'])+('|$) matches anything surrounded by two quote characters ('), +// except a single quote symbol, which ends the sequence. +// Two quote characters do not end the sequence. +// If there is no matching single quote +// then the sequence will continue until the end of the string. +// - . matches any single character unmatched by previous parts of the RegExps + +var formattingTokensRegExp$1 = /[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g; // This RegExp catches symbols escaped by quotes, and also +// sequences of symbols P, p, and the combinations like `PPPPPPPppppp` + +var longFormattingTokensRegExp$1 = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g; +var escapedStringRegExp$1 = /^'([^]*?)'?$/; +var doubleQuoteRegExp$1 = /''/g; +var unescapedLatinCharacterRegExp$1 = /[a-zA-Z]/; +/** + * @name format + * @category Common Helpers + * @summary Format the date. + * + * @description + * Return the formatted date string in the given format. The result may vary by locale. + * + * > ⚠️ Please note that the `format` tokens differ from Moment.js and other libraries. + * > See: https://git.io/fxCyr + * + * The characters wrapped between two single quotes characters (') are escaped. + * Two single quotes in a row, whether inside or outside a quoted sequence, represent a 'real' single quote. + * (see the last example) + * + * Format of the string is based on Unicode Technical Standard #35: + * https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + * with a few additions (see note 7 below the table). + * + * Accepted patterns: + * | Unit | Pattern | Result examples | Notes | + * |---------------------------------|---------|-----------------------------------|-------| + * | Era | G..GGG | AD, BC | | + * | | GGGG | Anno Domini, Before Christ | 2 | + * | | GGGGG | A, B | | + * | Calendar year | y | 44, 1, 1900, 2017 | 5 | + * | | yo | 44th, 1st, 0th, 17th | 5,7 | + * | | yy | 44, 01, 00, 17 | 5 | + * | | yyy | 044, 001, 1900, 2017 | 5 | + * | | yyyy | 0044, 0001, 1900, 2017 | 5 | + * | | yyyyy | ... | 3,5 | + * | Local week-numbering year | Y | 44, 1, 1900, 2017 | 5 | + * | | Yo | 44th, 1st, 1900th, 2017th | 5,7 | + * | | YY | 44, 01, 00, 17 | 5,8 | + * | | YYY | 044, 001, 1900, 2017 | 5 | + * | | YYYY | 0044, 0001, 1900, 2017 | 5,8 | + * | | YYYYY | ... | 3,5 | + * | ISO week-numbering year | R | -43, 0, 1, 1900, 2017 | 5,7 | + * | | RR | -43, 00, 01, 1900, 2017 | 5,7 | + * | | RRR | -043, 000, 001, 1900, 2017 | 5,7 | + * | | RRRR | -0043, 0000, 0001, 1900, 2017 | 5,7 | + * | | RRRRR | ... | 3,5,7 | + * | Extended year | u | -43, 0, 1, 1900, 2017 | 5 | + * | | uu | -43, 01, 1900, 2017 | 5 | + * | | uuu | -043, 001, 1900, 2017 | 5 | + * | | uuuu | -0043, 0001, 1900, 2017 | 5 | + * | | uuuuu | ... | 3,5 | + * | Quarter (formatting) | Q | 1, 2, 3, 4 | | + * | | Qo | 1st, 2nd, 3rd, 4th | 7 | + * | | QQ | 01, 02, 03, 04 | | + * | | QQQ | Q1, Q2, Q3, Q4 | | + * | | QQQQ | 1st quarter, 2nd quarter, ... | 2 | + * | | QQQQQ | 1, 2, 3, 4 | 4 | + * | Quarter (stand-alone) | q | 1, 2, 3, 4 | | + * | | qo | 1st, 2nd, 3rd, 4th | 7 | + * | | qq | 01, 02, 03, 04 | | + * | | qqq | Q1, Q2, Q3, Q4 | | + * | | qqqq | 1st quarter, 2nd quarter, ... | 2 | + * | | qqqqq | 1, 2, 3, 4 | 4 | + * | Month (formatting) | M | 1, 2, ..., 12 | | + * | | Mo | 1st, 2nd, ..., 12th | 7 | + * | | MM | 01, 02, ..., 12 | | + * | | MMM | Jan, Feb, ..., Dec | | + * | | MMMM | January, February, ..., December | 2 | + * | | MMMMM | J, F, ..., D | | + * | Month (stand-alone) | L | 1, 2, ..., 12 | | + * | | Lo | 1st, 2nd, ..., 12th | 7 | + * | | LL | 01, 02, ..., 12 | | + * | | LLL | Jan, Feb, ..., Dec | | + * | | LLLL | January, February, ..., December | 2 | + * | | LLLLL | J, F, ..., D | | + * | Local week of year | w | 1, 2, ..., 53 | | + * | | wo | 1st, 2nd, ..., 53th | 7 | + * | | ww | 01, 02, ..., 53 | | + * | ISO week of year | I | 1, 2, ..., 53 | 7 | + * | | Io | 1st, 2nd, ..., 53th | 7 | + * | | II | 01, 02, ..., 53 | 7 | + * | Day of month | d | 1, 2, ..., 31 | | + * | | do | 1st, 2nd, ..., 31st | 7 | + * | | dd | 01, 02, ..., 31 | | + * | Day of year | D | 1, 2, ..., 365, 366 | 9 | + * | | Do | 1st, 2nd, ..., 365th, 366th | 7 | + * | | DD | 01, 02, ..., 365, 366 | 9 | + * | | DDD | 001, 002, ..., 365, 366 | | + * | | DDDD | ... | 3 | + * | Day of week (formatting) | E..EEE | Mon, Tue, Wed, ..., Sun | | + * | | EEEE | Monday, Tuesday, ..., Sunday | 2 | + * | | EEEEE | M, T, W, T, F, S, S | | + * | | EEEEEE | Mo, Tu, We, Th, Fr, Su, Sa | | + * | ISO day of week (formatting) | i | 1, 2, 3, ..., 7 | 7 | + * | | io | 1st, 2nd, ..., 7th | 7 | + * | | ii | 01, 02, ..., 07 | 7 | + * | | iii | Mon, Tue, Wed, ..., Sun | 7 | + * | | iiii | Monday, Tuesday, ..., Sunday | 2,7 | + * | | iiiii | M, T, W, T, F, S, S | 7 | + * | | iiiiii | Mo, Tu, We, Th, Fr, Su, Sa | 7 | + * | Local day of week (formatting) | e | 2, 3, 4, ..., 1 | | + * | | eo | 2nd, 3rd, ..., 1st | 7 | + * | | ee | 02, 03, ..., 01 | | + * | | eee | Mon, Tue, Wed, ..., Sun | | + * | | eeee | Monday, Tuesday, ..., Sunday | 2 | + * | | eeeee | M, T, W, T, F, S, S | | + * | | eeeeee | Mo, Tu, We, Th, Fr, Su, Sa | | + * | Local day of week (stand-alone) | c | 2, 3, 4, ..., 1 | | + * | | co | 2nd, 3rd, ..., 1st | 7 | + * | | cc | 02, 03, ..., 01 | | + * | | ccc | Mon, Tue, Wed, ..., Sun | | + * | | cccc | Monday, Tuesday, ..., Sunday | 2 | + * | | ccccc | M, T, W, T, F, S, S | | + * | | cccccc | Mo, Tu, We, Th, Fr, Su, Sa | | + * | AM, PM | a..aa | AM, PM | | + * | | aaa | am, pm | | + * | | aaaa | a.m., p.m. | 2 | + * | | aaaaa | a, p | | + * | AM, PM, noon, midnight | b..bb | AM, PM, noon, midnight | | + * | | bbb | am, pm, noon, midnight | | + * | | bbbb | a.m., p.m., noon, midnight | 2 | + * | | bbbbb | a, p, n, mi | | + * | Flexible day period | B..BBB | at night, in the morning, ... | | + * | | BBBB | at night, in the morning, ... | 2 | + * | | BBBBB | at night, in the morning, ... | | + * | Hour [1-12] | h | 1, 2, ..., 11, 12 | | + * | | ho | 1st, 2nd, ..., 11th, 12th | 7 | + * | | hh | 01, 02, ..., 11, 12 | | + * | Hour [0-23] | H | 0, 1, 2, ..., 23 | | + * | | Ho | 0th, 1st, 2nd, ..., 23rd | 7 | + * | | HH | 00, 01, 02, ..., 23 | | + * | Hour [0-11] | K | 1, 2, ..., 11, 0 | | + * | | Ko | 1st, 2nd, ..., 11th, 0th | 7 | + * | | KK | 01, 02, ..., 11, 00 | | + * | Hour [1-24] | k | 24, 1, 2, ..., 23 | | + * | | ko | 24th, 1st, 2nd, ..., 23rd | 7 | + * | | kk | 24, 01, 02, ..., 23 | | + * | Minute | m | 0, 1, ..., 59 | | + * | | mo | 0th, 1st, ..., 59th | 7 | + * | | mm | 00, 01, ..., 59 | | + * | Second | s | 0, 1, ..., 59 | | + * | | so | 0th, 1st, ..., 59th | 7 | + * | | ss | 00, 01, ..., 59 | | + * | Fraction of second | S | 0, 1, ..., 9 | | + * | | SS | 00, 01, ..., 99 | | + * | | SSS | 000, 0001, ..., 999 | | + * | | SSSS | ... | 3 | + * | Timezone (ISO-8601 w/ Z) | X | -08, +0530, Z | | + * | | XX | -0800, +0530, Z | | + * | | XXX | -08:00, +05:30, Z | | + * | | XXXX | -0800, +0530, Z, +123456 | 2 | + * | | XXXXX | -08:00, +05:30, Z, +12:34:56 | | + * | Timezone (ISO-8601 w/o Z) | x | -08, +0530, +00 | | + * | | xx | -0800, +0530, +0000 | | + * | | xxx | -08:00, +05:30, +00:00 | 2 | + * | | xxxx | -0800, +0530, +0000, +123456 | | + * | | xxxxx | -08:00, +05:30, +00:00, +12:34:56 | | + * | Timezone (GMT) | O...OOO | GMT-8, GMT+5:30, GMT+0 | | + * | | OOOO | GMT-08:00, GMT+05:30, GMT+00:00 | 2 | + * | Timezone (specific non-locat.) | z...zzz | GMT-8, GMT+5:30, GMT+0 | 6 | + * | | zzzz | GMT-08:00, GMT+05:30, GMT+00:00 | 2,6 | + * | Seconds timestamp | t | 512969520 | 7 | + * | | tt | ... | 3,7 | + * | Milliseconds timestamp | T | 512969520900 | 7 | + * | | TT | ... | 3,7 | + * | Long localized date | P | 04/29/1453 | 7 | + * | | PP | Apr 29, 1453 | 7 | + * | | PPP | April 29th, 1453 | 7 | + * | | PPPP | Friday, April 29th, 1453 | 2,7 | + * | Long localized time | p | 12:00 AM | 7 | + * | | pp | 12:00:00 AM | 7 | + * | | ppp | 12:00:00 AM GMT+2 | 7 | + * | | pppp | 12:00:00 AM GMT+02:00 | 2,7 | + * | Combination of date and time | Pp | 04/29/1453, 12:00 AM | 7 | + * | | PPpp | Apr 29, 1453, 12:00:00 AM | 7 | + * | | PPPppp | April 29th, 1453 at ... | 7 | + * | | PPPPpppp| Friday, April 29th, 1453 at ... | 2,7 | + * Notes: + * 1. "Formatting" units (e.g. formatting quarter) in the default en-US locale + * are the same as "stand-alone" units, but are different in some languages. + * "Formatting" units are declined according to the rules of the language + * in the context of a date. "Stand-alone" units are always nominative singular: + * + * `format(new Date(2017, 10, 6), 'do LLLL', {locale: cs}) //=> '6. listopad'` + * + * `format(new Date(2017, 10, 6), 'do MMMM', {locale: cs}) //=> '6. listopadu'` + * + * 2. Any sequence of the identical letters is a pattern, unless it is escaped by + * the single quote characters (see below). + * If the sequence is longer than listed in table (e.g. `EEEEEEEEEEE`) + * the output will be the same as default pattern for this unit, usually + * the longest one (in case of ISO weekdays, `EEEE`). Default patterns for units + * are marked with "2" in the last column of the table. + * + * `format(new Date(2017, 10, 6), 'MMM') //=> 'Nov'` + * + * `format(new Date(2017, 10, 6), 'MMMM') //=> 'November'` + * + * `format(new Date(2017, 10, 6), 'MMMMM') //=> 'N'` + * + * `format(new Date(2017, 10, 6), 'MMMMMM') //=> 'November'` + * + * `format(new Date(2017, 10, 6), 'MMMMMMM') //=> 'November'` + * + * 3. Some patterns could be unlimited length (such as `yyyyyyyy`). + * The output will be padded with zeros to match the length of the pattern. + * + * `format(new Date(2017, 10, 6), 'yyyyyyyy') //=> '00002017'` + * + * 4. `QQQQQ` and `qqqqq` could be not strictly numerical in some locales. + * These tokens represent the shortest form of the quarter. + * + * 5. The main difference between `y` and `u` patterns are B.C. years: + * + * | Year | `y` | `u` | + * |------|-----|-----| + * | AC 1 | 1 | 1 | + * | BC 1 | 1 | 0 | + * | BC 2 | 2 | -1 | + * + * Also `yy` always returns the last two digits of a year, + * while `uu` pads single digit years to 2 characters and returns other years unchanged: + * + * | Year | `yy` | `uu` | + * |------|------|------| + * | 1 | 01 | 01 | + * | 14 | 14 | 14 | + * | 376 | 76 | 376 | + * | 1453 | 53 | 1453 | + * + * The same difference is true for local and ISO week-numbering years (`Y` and `R`), + * except local week-numbering years are dependent on `options.weekStartsOn` + * and `options.firstWeekContainsDate` (compare [getISOWeekYear]{@link https://date-fns.org/docs/getISOWeekYear} + * and [getWeekYear]{@link https://date-fns.org/docs/getWeekYear}). + * + * 6. Specific non-location timezones are currently unavailable in `date-fns`, + * so right now these tokens fall back to GMT timezones. + * + * 7. These patterns are not in the Unicode Technical Standard #35: + * - `i`: ISO day of week + * - `I`: ISO week of year + * - `R`: ISO week-numbering year + * - `t`: seconds timestamp + * - `T`: milliseconds timestamp + * - `o`: ordinal number modifier + * - `P`: long localized date + * - `p`: long localized time + * + * 8. `YY` and `YYYY` tokens represent week-numbering years but they are often confused with years. + * You should enable `options.useAdditionalWeekYearTokens` to use them. See: https://git.io/fxCyr + * + * 9. `D` and `DD` tokens represent days of the year but they are ofthen confused with days of the month. + * You should enable `options.useAdditionalDayOfYearTokens` to use them. See: https://git.io/fxCyr + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * - The second argument is now required for the sake of explicitness. + * + * ```javascript + * // Before v2.0.0 + * format(new Date(2016, 0, 1)) + * + * // v2.0.0 onward + * format(new Date(2016, 0, 1), "yyyy-MM-dd'T'HH:mm:ss.SSSxxx") + * ``` + * + * - New format string API for `format` function + * which is based on [Unicode Technical Standard #35](https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table). + * See [this post](https://blog.date-fns.org/post/unicode-tokens-in-date-fns-v2-sreatyki91jg) for more details. + * + * - Characters are now escaped using single quote symbols (`'`) instead of square brackets. + * + * @param {Date|Number} date - the original date + * @param {String} format - the string of tokens + * @param {Object} [options] - an object with options. + * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale} + * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday) + * @param {Number} [options.firstWeekContainsDate=1] - the day of January, which is + * @param {Boolean} [options.useAdditionalWeekYearTokens=false] - if true, allows usage of the week-numbering year tokens `YY` and `YYYY`; + * see: https://git.io/fxCyr + * @param {Boolean} [options.useAdditionalDayOfYearTokens=false] - if true, allows usage of the day of year tokens `D` and `DD`; + * see: https://git.io/fxCyr + * @returns {String} the formatted date string + * @throws {TypeError} 2 arguments required + * @throws {RangeError} `date` must not be Invalid Date + * @throws {RangeError} `options.locale` must contain `localize` property + * @throws {RangeError} `options.locale` must contain `formatLong` property + * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6 + * @throws {RangeError} `options.firstWeekContainsDate` must be between 1 and 7 + * @throws {RangeError} use `yyyy` instead of `YYYY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `yy` instead of `YY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `d` instead of `D` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `dd` instead of `DD` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} format string contains an unescaped latin alphabet character + * + * @example + * // Represent 11 February 2014 in middle-endian format: + * var result = format(new Date(2014, 1, 11), 'MM/dd/yyyy') + * //=> '02/11/2014' + * + * @example + * // Represent 2 July 2014 in Esperanto: + * import { eoLocale } from 'date-fns/locale/eo' + * var result = format(new Date(2014, 6, 2), "do 'de' MMMM yyyy", { + * locale: eoLocale + * }) + * //=> '2-a de julio 2014' + * + * @example + * // Escape string by single quote characters: + * var result = format(new Date(2014, 6, 2, 15), "h 'o''clock'") + * //=> "3 o'clock" + */ + +function format(dirtyDate, dirtyFormatStr, dirtyOptions) { + requiredArgs(2, arguments); + var formatStr = String(dirtyFormatStr); + var options = dirtyOptions || {}; + var locale = options.locale || defaultLocale; + var localeFirstWeekContainsDate = locale.options && locale.options.firstWeekContainsDate; + var defaultFirstWeekContainsDate = localeFirstWeekContainsDate == null ? 1 : toInteger(localeFirstWeekContainsDate); + var firstWeekContainsDate = options.firstWeekContainsDate == null ? defaultFirstWeekContainsDate : toInteger(options.firstWeekContainsDate); // Test if weekStartsOn is between 1 and 7 _and_ is not NaN + + if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) { + throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively'); + } + + var localeWeekStartsOn = locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + if (!locale.localize) { + throw new RangeError('locale must contain localize property'); + } + + if (!locale.formatLong) { + throw new RangeError('locale must contain formatLong property'); + } + + var originalDate = toDate(dirtyDate); + + if (!isValid(originalDate)) { + throw new RangeError('Invalid time value'); + } // Convert the date in system timezone to the same date in UTC+00:00 timezone. + // This ensures that when UTC functions will be implemented, locales will be compatible with them. + // See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/376 + + + var timezoneOffset = getTimezoneOffsetInMilliseconds(originalDate); + var utcDate = subMilliseconds(originalDate, timezoneOffset); + var formatterOptions = { + firstWeekContainsDate: firstWeekContainsDate, + weekStartsOn: weekStartsOn, + locale: locale, + _originalDate: originalDate + }; + var result = formatStr.match(longFormattingTokensRegExp$1).map(function (substring) { + var firstCharacter = substring[0]; + + if (firstCharacter === 'p' || firstCharacter === 'P') { + var longFormatter = longFormatters$1[firstCharacter]; + return longFormatter(substring, locale.formatLong, formatterOptions); + } + + return substring; + }).join('').match(formattingTokensRegExp$1).map(function (substring) { + // Replace two single quote characters with one single quote character + if (substring === "''") { + return "'"; + } + + var firstCharacter = substring[0]; + + if (firstCharacter === "'") { + return cleanEscapedString$1(substring); + } + + var formatter = formatters$1[firstCharacter]; + + if (formatter) { + if (!options.useAdditionalWeekYearTokens && isProtectedWeekYearToken(substring)) { + throwProtectedError(substring, dirtyFormatStr, dirtyDate); + } + + if (!options.useAdditionalDayOfYearTokens && isProtectedDayOfYearToken(substring)) { + throwProtectedError(substring, dirtyFormatStr, dirtyDate); + } + + return formatter(utcDate, substring, locale.localize, formatterOptions); + } + + if (firstCharacter.match(unescapedLatinCharacterRegExp$1)) { + throw new RangeError('Format string contains an unescaped latin alphabet character `' + firstCharacter + '`'); + } + + return substring; + }).join(''); + return result; +} + +function cleanEscapedString$1(input) { + return input.match(escapedStringRegExp$1)[1].replace(doubleQuoteRegExp$1, "'"); +} + +function assign(target, dirtyObject) { + if (target == null) { + throw new TypeError('assign requires that input parameter not be null or undefined'); + } + + dirtyObject = dirtyObject || {}; + + for (var property in dirtyObject) { + if (dirtyObject.hasOwnProperty(property)) { + target[property] = dirtyObject[property]; + } + } + + return target; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function setUTCDay(dirtyDate, dirtyDay, dirtyOptions) { + requiredArgs(2, arguments); + var options = dirtyOptions || {}; + var locale = options.locale; + var localeWeekStartsOn = locale && locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + var date = toDate(dirtyDate); + var day = toInteger(dirtyDay); + var currentDay = date.getUTCDay(); + var remainder = day % 7; + var dayIndex = (remainder + 7) % 7; + var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay; + date.setUTCDate(date.getUTCDate() + diff); + return date; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function setUTCISODay(dirtyDate, dirtyDay) { + requiredArgs(2, arguments); + var day = toInteger(dirtyDay); + + if (day % 7 === 0) { + day = day - 7; + } + + var weekStartsOn = 1; + var date = toDate(dirtyDate); + var currentDay = date.getUTCDay(); + var remainder = day % 7; + var dayIndex = (remainder + 7) % 7; + var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay; + date.setUTCDate(date.getUTCDate() + diff); + return date; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function setUTCISOWeek(dirtyDate, dirtyISOWeek) { + requiredArgs(2, arguments); + var date = toDate(dirtyDate); + var isoWeek = toInteger(dirtyISOWeek); + var diff = getUTCISOWeek(date) - isoWeek; + date.setUTCDate(date.getUTCDate() - diff * 7); + return date; +} + +// See issue: https://github.com/date-fns/date-fns/issues/376 + +function setUTCWeek(dirtyDate, dirtyWeek, options) { + requiredArgs(2, arguments); + var date = toDate(dirtyDate); + var week = toInteger(dirtyWeek); + var diff = getUTCWeek(date, options) - week; + date.setUTCDate(date.getUTCDate() - diff * 7); + return date; +} + +var MILLISECONDS_IN_HOUR$1 = 3600000; +var MILLISECONDS_IN_MINUTE$1 = 60000; +var MILLISECONDS_IN_SECOND = 1000; +var numericPatterns = { + month: /^(1[0-2]|0?\d)/, + // 0 to 12 + date: /^(3[0-1]|[0-2]?\d)/, + // 0 to 31 + dayOfYear: /^(36[0-6]|3[0-5]\d|[0-2]?\d?\d)/, + // 0 to 366 + week: /^(5[0-3]|[0-4]?\d)/, + // 0 to 53 + hour23h: /^(2[0-3]|[0-1]?\d)/, + // 0 to 23 + hour24h: /^(2[0-4]|[0-1]?\d)/, + // 0 to 24 + hour11h: /^(1[0-1]|0?\d)/, + // 0 to 11 + hour12h: /^(1[0-2]|0?\d)/, + // 0 to 12 + minute: /^[0-5]?\d/, + // 0 to 59 + second: /^[0-5]?\d/, + // 0 to 59 + singleDigit: /^\d/, + // 0 to 9 + twoDigits: /^\d{1,2}/, + // 0 to 99 + threeDigits: /^\d{1,3}/, + // 0 to 999 + fourDigits: /^\d{1,4}/, + // 0 to 9999 + anyDigitsSigned: /^-?\d+/, + singleDigitSigned: /^-?\d/, + // 0 to 9, -0 to -9 + twoDigitsSigned: /^-?\d{1,2}/, + // 0 to 99, -0 to -99 + threeDigitsSigned: /^-?\d{1,3}/, + // 0 to 999, -0 to -999 + fourDigitsSigned: /^-?\d{1,4}/ // 0 to 9999, -0 to -9999 + +}; +var timezonePatterns = { + basicOptionalMinutes: /^([+-])(\d{2})(\d{2})?|Z/, + basic: /^([+-])(\d{2})(\d{2})|Z/, + basicOptionalSeconds: /^([+-])(\d{2})(\d{2})((\d{2}))?|Z/, + extended: /^([+-])(\d{2}):(\d{2})|Z/, + extendedOptionalSeconds: /^([+-])(\d{2}):(\d{2})(:(\d{2}))?|Z/ +}; + +function parseNumericPattern(pattern, string, valueCallback) { + var matchResult = string.match(pattern); + + if (!matchResult) { + return null; + } + + var value = parseInt(matchResult[0], 10); + return { + value: valueCallback ? valueCallback(value) : value, + rest: string.slice(matchResult[0].length) + }; +} + +function parseTimezonePattern(pattern, string) { + var matchResult = string.match(pattern); + + if (!matchResult) { + return null; + } // Input is 'Z' + + + if (matchResult[0] === 'Z') { + return { + value: 0, + rest: string.slice(1) + }; + } + + var sign = matchResult[1] === '+' ? 1 : -1; + var hours = matchResult[2] ? parseInt(matchResult[2], 10) : 0; + var minutes = matchResult[3] ? parseInt(matchResult[3], 10) : 0; + var seconds = matchResult[5] ? parseInt(matchResult[5], 10) : 0; + return { + value: sign * (hours * MILLISECONDS_IN_HOUR$1 + minutes * MILLISECONDS_IN_MINUTE$1 + seconds * MILLISECONDS_IN_SECOND), + rest: string.slice(matchResult[0].length) + }; +} + +function parseAnyDigitsSigned(string, valueCallback) { + return parseNumericPattern(numericPatterns.anyDigitsSigned, string, valueCallback); +} + +function parseNDigits(n, string, valueCallback) { + switch (n) { + case 1: + return parseNumericPattern(numericPatterns.singleDigit, string, valueCallback); + + case 2: + return parseNumericPattern(numericPatterns.twoDigits, string, valueCallback); + + case 3: + return parseNumericPattern(numericPatterns.threeDigits, string, valueCallback); + + case 4: + return parseNumericPattern(numericPatterns.fourDigits, string, valueCallback); + + default: + return parseNumericPattern(new RegExp('^\\d{1,' + n + '}'), string, valueCallback); + } +} + +function parseNDigitsSigned(n, string, valueCallback) { + switch (n) { + case 1: + return parseNumericPattern(numericPatterns.singleDigitSigned, string, valueCallback); + + case 2: + return parseNumericPattern(numericPatterns.twoDigitsSigned, string, valueCallback); + + case 3: + return parseNumericPattern(numericPatterns.threeDigitsSigned, string, valueCallback); + + case 4: + return parseNumericPattern(numericPatterns.fourDigitsSigned, string, valueCallback); + + default: + return parseNumericPattern(new RegExp('^-?\\d{1,' + n + '}'), string, valueCallback); + } +} + +function dayPeriodEnumToHours(enumValue) { + switch (enumValue) { + case 'morning': + return 4; + + case 'evening': + return 17; + + case 'pm': + case 'noon': + case 'afternoon': + return 12; + + case 'am': + case 'midnight': + case 'night': + default: + return 0; + } +} + +function normalizeTwoDigitYear(twoDigitYear, currentYear) { + var isCommonEra = currentYear > 0; // Absolute number of the current year: + // 1 -> 1 AC + // 0 -> 1 BC + // -1 -> 2 BC + + var absCurrentYear = isCommonEra ? currentYear : 1 - currentYear; + var result; + + if (absCurrentYear <= 50) { + result = twoDigitYear || 100; + } else { + var rangeEnd = absCurrentYear + 50; + var rangeEndCentury = Math.floor(rangeEnd / 100) * 100; + var isPreviousCentury = twoDigitYear >= rangeEnd % 100; + result = twoDigitYear + rangeEndCentury - (isPreviousCentury ? 100 : 0); + } + + return isCommonEra ? result : 1 - result; +} + +var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +var DAYS_IN_MONTH_LEAP_YEAR = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; // User for validation + +function isLeapYearIndex$1(year) { + return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0; +} +/* + * | | Unit | | Unit | + * |-----|--------------------------------|-----|--------------------------------| + * | a | AM, PM | A* | Milliseconds in day | + * | b | AM, PM, noon, midnight | B | Flexible day period | + * | c | Stand-alone local day of week | C* | Localized hour w/ day period | + * | d | Day of month | D | Day of year | + * | e | Local day of week | E | Day of week | + * | f | | F* | Day of week in month | + * | g* | Modified Julian day | G | Era | + * | h | Hour [1-12] | H | Hour [0-23] | + * | i! | ISO day of week | I! | ISO week of year | + * | j* | Localized hour w/ day period | J* | Localized hour w/o day period | + * | k | Hour [1-24] | K | Hour [0-11] | + * | l* | (deprecated) | L | Stand-alone month | + * | m | Minute | M | Month | + * | n | | N | | + * | o! | Ordinal number modifier | O* | Timezone (GMT) | + * | p | | P | | + * | q | Stand-alone quarter | Q | Quarter | + * | r* | Related Gregorian year | R! | ISO week-numbering year | + * | s | Second | S | Fraction of second | + * | t! | Seconds timestamp | T! | Milliseconds timestamp | + * | u | Extended year | U* | Cyclic year | + * | v* | Timezone (generic non-locat.) | V* | Timezone (location) | + * | w | Local week of year | W* | Week of month | + * | x | Timezone (ISO-8601 w/o Z) | X | Timezone (ISO-8601) | + * | y | Year (abs) | Y | Local week-numbering year | + * | z* | Timezone (specific non-locat.) | Z* | Timezone (aliases) | + * + * Letters marked by * are not implemented but reserved by Unicode standard. + * + * Letters marked by ! are non-standard, but implemented by date-fns: + * - `o` modifies the previous token to turn it into an ordinal (see `parse` docs) + * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days, + * i.e. 7 for Sunday, 1 for Monday, etc. + * - `I` is ISO week of year, as opposed to `w` which is local week of year. + * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year. + * `R` is supposed to be used in conjunction with `I` and `i` + * for universal ISO week-numbering date, whereas + * `Y` is supposed to be used in conjunction with `w` and `e` + * for week-numbering date specific to the locale. + */ + + +var parsers = { + // Era + G: { + priority: 140, + parse: function (string, token, match, _options) { + switch (token) { + // AD, BC + case 'G': + case 'GG': + case 'GGG': + return match.era(string, { + width: 'abbreviated' + }) || match.era(string, { + width: 'narrow' + }); + // A, B + + case 'GGGGG': + return match.era(string, { + width: 'narrow' + }); + // Anno Domini, Before Christ + + case 'GGGG': + default: + return match.era(string, { + width: 'wide' + }) || match.era(string, { + width: 'abbreviated' + }) || match.era(string, { + width: 'narrow' + }); + } + }, + set: function (date, flags, value, _options) { + flags.era = value; + date.setUTCFullYear(value, 0, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['R', 'u', 't', 'T'] + }, + // Year + y: { + // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns + // | Year | y | yy | yyy | yyyy | yyyyy | + // |----------|-------|----|-------|-------|-------| + // | AD 1 | 1 | 01 | 001 | 0001 | 00001 | + // | AD 12 | 12 | 12 | 012 | 0012 | 00012 | + // | AD 123 | 123 | 23 | 123 | 0123 | 00123 | + // | AD 1234 | 1234 | 34 | 1234 | 1234 | 01234 | + // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 | + priority: 130, + parse: function (string, token, match, _options) { + var valueCallback = function (year) { + return { + year: year, + isTwoDigitYear: token === 'yy' + }; + }; + + switch (token) { + case 'y': + return parseNDigits(4, string, valueCallback); + + case 'yo': + return match.ordinalNumber(string, { + unit: 'year', + valueCallback: valueCallback + }); + + default: + return parseNDigits(token.length, string, valueCallback); + } + }, + validate: function (_date, value, _options) { + return value.isTwoDigitYear || value.year > 0; + }, + set: function (date, flags, value, _options) { + var currentYear = date.getUTCFullYear(); + + if (value.isTwoDigitYear) { + var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear); + date.setUTCFullYear(normalizedTwoDigitYear, 0, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + } + + var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year; + date.setUTCFullYear(year, 0, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'u', 'w', 'I', 'i', 'e', 'c', 't', 'T'] + }, + // Local week-numbering year + Y: { + priority: 130, + parse: function (string, token, match, _options) { + var valueCallback = function (year) { + return { + year: year, + isTwoDigitYear: token === 'YY' + }; + }; + + switch (token) { + case 'Y': + return parseNDigits(4, string, valueCallback); + + case 'Yo': + return match.ordinalNumber(string, { + unit: 'year', + valueCallback: valueCallback + }); + + default: + return parseNDigits(token.length, string, valueCallback); + } + }, + validate: function (_date, value, _options) { + return value.isTwoDigitYear || value.year > 0; + }, + set: function (date, flags, value, options) { + var currentYear = getUTCWeekYear(date, options); + + if (value.isTwoDigitYear) { + var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear); + date.setUTCFullYear(normalizedTwoDigitYear, 0, options.firstWeekContainsDate); + date.setUTCHours(0, 0, 0, 0); + return startOfUTCWeek(date, options); + } + + var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year; + date.setUTCFullYear(year, 0, options.firstWeekContainsDate); + date.setUTCHours(0, 0, 0, 0); + return startOfUTCWeek(date, options); + }, + incompatibleTokens: ['y', 'R', 'u', 'Q', 'q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T'] + }, + // ISO week-numbering year + R: { + priority: 130, + parse: function (string, token, _match, _options) { + if (token === 'R') { + return parseNDigitsSigned(4, string); + } + + return parseNDigitsSigned(token.length, string); + }, + set: function (_date, _flags, value, _options) { + var firstWeekOfYear = new Date(0); + firstWeekOfYear.setUTCFullYear(value, 0, 4); + firstWeekOfYear.setUTCHours(0, 0, 0, 0); + return startOfUTCISOWeek(firstWeekOfYear); + }, + incompatibleTokens: ['G', 'y', 'Y', 'u', 'Q', 'q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T'] + }, + // Extended year + u: { + priority: 130, + parse: function (string, token, _match, _options) { + if (token === 'u') { + return parseNDigitsSigned(4, string); + } + + return parseNDigitsSigned(token.length, string); + }, + set: function (date, _flags, value, _options) { + date.setUTCFullYear(value, 0, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['G', 'y', 'Y', 'R', 'w', 'I', 'i', 'e', 'c', 't', 'T'] + }, + // Quarter + Q: { + priority: 120, + parse: function (string, token, match, _options) { + switch (token) { + // 1, 2, 3, 4 + case 'Q': + case 'QQ': + // 01, 02, 03, 04 + return parseNDigits(token.length, string); + // 1st, 2nd, 3rd, 4th + + case 'Qo': + return match.ordinalNumber(string, { + unit: 'quarter' + }); + // Q1, Q2, Q3, Q4 + + case 'QQQ': + return match.quarter(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.quarter(string, { + width: 'narrow', + context: 'formatting' + }); + // 1, 2, 3, 4 (narrow quarter; could be not numerical) + + case 'QQQQQ': + return match.quarter(string, { + width: 'narrow', + context: 'formatting' + }); + // 1st quarter, 2nd quarter, ... + + case 'QQQQ': + default: + return match.quarter(string, { + width: 'wide', + context: 'formatting' + }) || match.quarter(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.quarter(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 4; + }, + set: function (date, _flags, value, _options) { + date.setUTCMonth((value - 1) * 3, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T'] + }, + // Stand-alone quarter + q: { + priority: 120, + parse: function (string, token, match, _options) { + switch (token) { + // 1, 2, 3, 4 + case 'q': + case 'qq': + // 01, 02, 03, 04 + return parseNDigits(token.length, string); + // 1st, 2nd, 3rd, 4th + + case 'qo': + return match.ordinalNumber(string, { + unit: 'quarter' + }); + // Q1, Q2, Q3, Q4 + + case 'qqq': + return match.quarter(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.quarter(string, { + width: 'narrow', + context: 'standalone' + }); + // 1, 2, 3, 4 (narrow quarter; could be not numerical) + + case 'qqqqq': + return match.quarter(string, { + width: 'narrow', + context: 'standalone' + }); + // 1st quarter, 2nd quarter, ... + + case 'qqqq': + default: + return match.quarter(string, { + width: 'wide', + context: 'standalone' + }) || match.quarter(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.quarter(string, { + width: 'narrow', + context: 'standalone' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 4; + }, + set: function (date, _flags, value, _options) { + date.setUTCMonth((value - 1) * 3, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'Q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T'] + }, + // Month + M: { + priority: 110, + parse: function (string, token, match, _options) { + var valueCallback = function (value) { + return value - 1; + }; + + switch (token) { + // 1, 2, ..., 12 + case 'M': + return parseNumericPattern(numericPatterns.month, string, valueCallback); + // 01, 02, ..., 12 + + case 'MM': + return parseNDigits(2, string, valueCallback); + // 1st, 2nd, ..., 12th + + case 'Mo': + return match.ordinalNumber(string, { + unit: 'month', + valueCallback: valueCallback + }); + // Jan, Feb, ..., Dec + + case 'MMM': + return match.month(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.month(string, { + width: 'narrow', + context: 'formatting' + }); + // J, F, ..., D + + case 'MMMMM': + return match.month(string, { + width: 'narrow', + context: 'formatting' + }); + // January, February, ..., December + + case 'MMMM': + default: + return match.month(string, { + width: 'wide', + context: 'formatting' + }) || match.month(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.month(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 11; + }, + set: function (date, _flags, value, _options) { + date.setUTCMonth(value, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'q', 'Q', 'L', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T'] + }, + // Stand-alone month + L: { + priority: 110, + parse: function (string, token, match, _options) { + var valueCallback = function (value) { + return value - 1; + }; + + switch (token) { + // 1, 2, ..., 12 + case 'L': + return parseNumericPattern(numericPatterns.month, string, valueCallback); + // 01, 02, ..., 12 + + case 'LL': + return parseNDigits(2, string, valueCallback); + // 1st, 2nd, ..., 12th + + case 'Lo': + return match.ordinalNumber(string, { + unit: 'month', + valueCallback: valueCallback + }); + // Jan, Feb, ..., Dec + + case 'LLL': + return match.month(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.month(string, { + width: 'narrow', + context: 'standalone' + }); + // J, F, ..., D + + case 'LLLLL': + return match.month(string, { + width: 'narrow', + context: 'standalone' + }); + // January, February, ..., December + + case 'LLLL': + default: + return match.month(string, { + width: 'wide', + context: 'standalone' + }) || match.month(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.month(string, { + width: 'narrow', + context: 'standalone' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 11; + }, + set: function (date, _flags, value, _options) { + date.setUTCMonth(value, 1); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'q', 'Q', 'M', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T'] + }, + // Local week of year + w: { + priority: 100, + parse: function (string, token, match, _options) { + switch (token) { + case 'w': + return parseNumericPattern(numericPatterns.week, string); + + case 'wo': + return match.ordinalNumber(string, { + unit: 'week' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 53; + }, + set: function (date, _flags, value, options) { + return startOfUTCWeek(setUTCWeek(date, value, options), options); + }, + incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T'] + }, + // ISO week of year + I: { + priority: 100, + parse: function (string, token, match, _options) { + switch (token) { + case 'I': + return parseNumericPattern(numericPatterns.week, string); + + case 'Io': + return match.ordinalNumber(string, { + unit: 'week' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 53; + }, + set: function (date, _flags, value, options) { + return startOfUTCISOWeek(setUTCISOWeek(date, value, options), options); + }, + incompatibleTokens: ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T'] + }, + // Day of the month + d: { + priority: 90, + subPriority: 1, + parse: function (string, token, match, _options) { + switch (token) { + case 'd': + return parseNumericPattern(numericPatterns.date, string); + + case 'do': + return match.ordinalNumber(string, { + unit: 'date' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (date, value, _options) { + var year = date.getUTCFullYear(); + var isLeapYear = isLeapYearIndex$1(year); + var month = date.getUTCMonth(); + + if (isLeapYear) { + return value >= 1 && value <= DAYS_IN_MONTH_LEAP_YEAR[month]; + } else { + return value >= 1 && value <= DAYS_IN_MONTH[month]; + } + }, + set: function (date, _flags, value, _options) { + date.setUTCDate(value); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'q', 'Q', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T'] + }, + // Day of year + D: { + priority: 90, + subPriority: 1, + parse: function (string, token, match, _options) { + switch (token) { + case 'D': + case 'DD': + return parseNumericPattern(numericPatterns.dayOfYear, string); + + case 'Do': + return match.ordinalNumber(string, { + unit: 'date' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (date, value, _options) { + var year = date.getUTCFullYear(); + var isLeapYear = isLeapYearIndex$1(year); + + if (isLeapYear) { + return value >= 1 && value <= 366; + } else { + return value >= 1 && value <= 365; + } + }, + set: function (date, _flags, value, _options) { + date.setUTCMonth(0, value); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['Y', 'R', 'q', 'Q', 'M', 'L', 'w', 'I', 'd', 'E', 'i', 'e', 'c', 't', 'T'] + }, + // Day of week + E: { + priority: 90, + parse: function (string, token, match, _options) { + switch (token) { + // Tue + case 'E': + case 'EE': + case 'EEE': + return match.day(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // T + + case 'EEEEE': + return match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // Tu + + case 'EEEEEE': + return match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // Tuesday + + case 'EEEE': + default: + return match.day(string, { + width: 'wide', + context: 'formatting' + }) || match.day(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 6; + }, + set: function (date, _flags, value, options) { + date = setUTCDay(date, value, options); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['D', 'i', 'e', 'c', 't', 'T'] + }, + // Local day of week + e: { + priority: 90, + parse: function (string, token, match, options) { + var valueCallback = function (value) { + var wholeWeekDays = Math.floor((value - 1) / 7) * 7; + return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays; + }; + + switch (token) { + // 3 + case 'e': + case 'ee': + // 03 + return parseNDigits(token.length, string, valueCallback); + // 3rd + + case 'eo': + return match.ordinalNumber(string, { + unit: 'day', + valueCallback: valueCallback + }); + // Tue + + case 'eee': + return match.day(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // T + + case 'eeeee': + return match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // Tu + + case 'eeeeee': + return match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + // Tuesday + + case 'eeee': + default: + return match.day(string, { + width: 'wide', + context: 'formatting' + }) || match.day(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.day(string, { + width: 'short', + context: 'formatting' + }) || match.day(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 6; + }, + set: function (date, _flags, value, options) { + date = setUTCDay(date, value, options); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'c', 't', 'T'] + }, + // Stand-alone local day of week + c: { + priority: 90, + parse: function (string, token, match, options) { + var valueCallback = function (value) { + var wholeWeekDays = Math.floor((value - 1) / 7) * 7; + return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays; + }; + + switch (token) { + // 3 + case 'c': + case 'cc': + // 03 + return parseNDigits(token.length, string, valueCallback); + // 3rd + + case 'co': + return match.ordinalNumber(string, { + unit: 'day', + valueCallback: valueCallback + }); + // Tue + + case 'ccc': + return match.day(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.day(string, { + width: 'short', + context: 'standalone' + }) || match.day(string, { + width: 'narrow', + context: 'standalone' + }); + // T + + case 'ccccc': + return match.day(string, { + width: 'narrow', + context: 'standalone' + }); + // Tu + + case 'cccccc': + return match.day(string, { + width: 'short', + context: 'standalone' + }) || match.day(string, { + width: 'narrow', + context: 'standalone' + }); + // Tuesday + + case 'cccc': + default: + return match.day(string, { + width: 'wide', + context: 'standalone' + }) || match.day(string, { + width: 'abbreviated', + context: 'standalone' + }) || match.day(string, { + width: 'short', + context: 'standalone' + }) || match.day(string, { + width: 'narrow', + context: 'standalone' + }); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 6; + }, + set: function (date, _flags, value, options) { + date = setUTCDay(date, value, options); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'e', 't', 'T'] + }, + // ISO day of week + i: { + priority: 90, + parse: function (string, token, match, _options) { + var valueCallback = function (value) { + if (value === 0) { + return 7; + } + + return value; + }; + + switch (token) { + // 2 + case 'i': + case 'ii': + // 02 + return parseNDigits(token.length, string); + // 2nd + + case 'io': + return match.ordinalNumber(string, { + unit: 'day' + }); + // Tue + + case 'iii': + return match.day(string, { + width: 'abbreviated', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'short', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'narrow', + context: 'formatting', + valueCallback: valueCallback + }); + // T + + case 'iiiii': + return match.day(string, { + width: 'narrow', + context: 'formatting', + valueCallback: valueCallback + }); + // Tu + + case 'iiiiii': + return match.day(string, { + width: 'short', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'narrow', + context: 'formatting', + valueCallback: valueCallback + }); + // Tuesday + + case 'iiii': + default: + return match.day(string, { + width: 'wide', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'abbreviated', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'short', + context: 'formatting', + valueCallback: valueCallback + }) || match.day(string, { + width: 'narrow', + context: 'formatting', + valueCallback: valueCallback + }); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 7; + }, + set: function (date, _flags, value, options) { + date = setUTCISODay(date, value, options); + date.setUTCHours(0, 0, 0, 0); + return date; + }, + incompatibleTokens: ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'E', 'e', 'c', 't', 'T'] + }, + // AM or PM + a: { + priority: 80, + parse: function (string, token, match, _options) { + switch (token) { + case 'a': + case 'aa': + case 'aaa': + return match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'aaaaa': + return match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'aaaa': + default: + return match.dayPeriod(string, { + width: 'wide', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + set: function (date, _flags, value, _options) { + date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0); + return date; + }, + incompatibleTokens: ['b', 'B', 'H', 'K', 'k', 't', 'T'] + }, + // AM, PM, midnight + b: { + priority: 80, + parse: function (string, token, match, _options) { + switch (token) { + case 'b': + case 'bb': + case 'bbb': + return match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'bbbbb': + return match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'bbbb': + default: + return match.dayPeriod(string, { + width: 'wide', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + set: function (date, _flags, value, _options) { + date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0); + return date; + }, + incompatibleTokens: ['a', 'B', 'H', 'K', 'k', 't', 'T'] + }, + // in the morning, in the afternoon, in the evening, at night + B: { + priority: 80, + parse: function (string, token, match, _options) { + switch (token) { + case 'B': + case 'BB': + case 'BBB': + return match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'BBBBB': + return match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + + case 'BBBB': + default: + return match.dayPeriod(string, { + width: 'wide', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'abbreviated', + context: 'formatting' + }) || match.dayPeriod(string, { + width: 'narrow', + context: 'formatting' + }); + } + }, + set: function (date, _flags, value, _options) { + date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0); + return date; + }, + incompatibleTokens: ['a', 'b', 't', 'T'] + }, + // Hour [1-12] + h: { + priority: 70, + parse: function (string, token, match, _options) { + switch (token) { + case 'h': + return parseNumericPattern(numericPatterns.hour12h, string); + + case 'ho': + return match.ordinalNumber(string, { + unit: 'hour' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 12; + }, + set: function (date, _flags, value, _options) { + var isPM = date.getUTCHours() >= 12; + + if (isPM && value < 12) { + date.setUTCHours(value + 12, 0, 0, 0); + } else if (!isPM && value === 12) { + date.setUTCHours(0, 0, 0, 0); + } else { + date.setUTCHours(value, 0, 0, 0); + } + + return date; + }, + incompatibleTokens: ['H', 'K', 'k', 't', 'T'] + }, + // Hour [0-23] + H: { + priority: 70, + parse: function (string, token, match, _options) { + switch (token) { + case 'H': + return parseNumericPattern(numericPatterns.hour23h, string); + + case 'Ho': + return match.ordinalNumber(string, { + unit: 'hour' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 23; + }, + set: function (date, _flags, value, _options) { + date.setUTCHours(value, 0, 0, 0); + return date; + }, + incompatibleTokens: ['a', 'b', 'h', 'K', 'k', 't', 'T'] + }, + // Hour [0-11] + K: { + priority: 70, + parse: function (string, token, match, _options) { + switch (token) { + case 'K': + return parseNumericPattern(numericPatterns.hour11h, string); + + case 'Ko': + return match.ordinalNumber(string, { + unit: 'hour' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 11; + }, + set: function (date, _flags, value, _options) { + var isPM = date.getUTCHours() >= 12; + + if (isPM && value < 12) { + date.setUTCHours(value + 12, 0, 0, 0); + } else { + date.setUTCHours(value, 0, 0, 0); + } + + return date; + }, + incompatibleTokens: ['a', 'b', 'h', 'H', 'k', 't', 'T'] + }, + // Hour [1-24] + k: { + priority: 70, + parse: function (string, token, match, _options) { + switch (token) { + case 'k': + return parseNumericPattern(numericPatterns.hour24h, string); + + case 'ko': + return match.ordinalNumber(string, { + unit: 'hour' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 1 && value <= 24; + }, + set: function (date, _flags, value, _options) { + var hours = value <= 24 ? value % 24 : value; + date.setUTCHours(hours, 0, 0, 0); + return date; + }, + incompatibleTokens: ['a', 'b', 'h', 'H', 'K', 't', 'T'] + }, + // Minute + m: { + priority: 60, + parse: function (string, token, match, _options) { + switch (token) { + case 'm': + return parseNumericPattern(numericPatterns.minute, string); + + case 'mo': + return match.ordinalNumber(string, { + unit: 'minute' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 59; + }, + set: function (date, _flags, value, _options) { + date.setUTCMinutes(value, 0, 0); + return date; + }, + incompatibleTokens: ['t', 'T'] + }, + // Second + s: { + priority: 50, + parse: function (string, token, match, _options) { + switch (token) { + case 's': + return parseNumericPattern(numericPatterns.second, string); + + case 'so': + return match.ordinalNumber(string, { + unit: 'second' + }); + + default: + return parseNDigits(token.length, string); + } + }, + validate: function (_date, value, _options) { + return value >= 0 && value <= 59; + }, + set: function (date, _flags, value, _options) { + date.setUTCSeconds(value, 0); + return date; + }, + incompatibleTokens: ['t', 'T'] + }, + // Fraction of second + S: { + priority: 30, + parse: function (string, token, _match, _options) { + var valueCallback = function (value) { + return Math.floor(value * Math.pow(10, -token.length + 3)); + }; + + return parseNDigits(token.length, string, valueCallback); + }, + set: function (date, _flags, value, _options) { + date.setUTCMilliseconds(value); + return date; + }, + incompatibleTokens: ['t', 'T'] + }, + // Timezone (ISO-8601. +00:00 is `'Z'`) + X: { + priority: 10, + parse: function (string, token, _match, _options) { + switch (token) { + case 'X': + return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, string); + + case 'XX': + return parseTimezonePattern(timezonePatterns.basic, string); + + case 'XXXX': + return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, string); + + case 'XXXXX': + return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, string); + + case 'XXX': + default: + return parseTimezonePattern(timezonePatterns.extended, string); + } + }, + set: function (date, flags, value, _options) { + if (flags.timestampIsSet) { + return date; + } + + return new Date(date.getTime() - value); + }, + incompatibleTokens: ['t', 'T', 'x'] + }, + // Timezone (ISO-8601) + x: { + priority: 10, + parse: function (string, token, _match, _options) { + switch (token) { + case 'x': + return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, string); + + case 'xx': + return parseTimezonePattern(timezonePatterns.basic, string); + + case 'xxxx': + return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, string); + + case 'xxxxx': + return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, string); + + case 'xxx': + default: + return parseTimezonePattern(timezonePatterns.extended, string); + } + }, + set: function (date, flags, value, _options) { + if (flags.timestampIsSet) { + return date; + } + + return new Date(date.getTime() - value); + }, + incompatibleTokens: ['t', 'T', 'X'] + }, + // Seconds timestamp + t: { + priority: 40, + parse: function (string, _token, _match, _options) { + return parseAnyDigitsSigned(string); + }, + set: function (_date, _flags, value, _options) { + return [new Date(value * 1000), { + timestampIsSet: true + }]; + }, + incompatibleTokens: '*' + }, + // Milliseconds timestamp + T: { + priority: 20, + parse: function (string, _token, _match, _options) { + return parseAnyDigitsSigned(string); + }, + set: function (_date, _flags, value, _options) { + return [new Date(value), { + timestampIsSet: true + }]; + }, + incompatibleTokens: '*' + } +}; +var parsers$1 = parsers; + +var TIMEZONE_UNIT_PRIORITY = 10; // This RegExp consists of three parts separated by `|`: +// - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token +// (one of the certain letters followed by `o`) +// - (\w)\1* matches any sequences of the same letter +// - '' matches two quote characters in a row +// - '(''|[^'])+('|$) matches anything surrounded by two quote characters ('), +// except a single quote symbol, which ends the sequence. +// Two quote characters do not end the sequence. +// If there is no matching single quote +// then the sequence will continue until the end of the string. +// - . matches any single character unmatched by previous parts of the RegExps + +var formattingTokensRegExp = /[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g; // This RegExp catches symbols escaped by quotes, and also +// sequences of symbols P, p, and the combinations like `PPPPPPPppppp` + +var longFormattingTokensRegExp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g; +var escapedStringRegExp = /^'([^]*?)'?$/; +var doubleQuoteRegExp = /''/g; +var notWhitespaceRegExp = /\S/; +var unescapedLatinCharacterRegExp = /[a-zA-Z]/; +/** + * @name parse + * @category Common Helpers + * @summary Parse the date. + * + * @description + * Return the date parsed from string using the given format string. + * + * > ⚠️ Please note that the `format` tokens differ from Moment.js and other libraries. + * > See: https://git.io/fxCyr + * + * The characters in the format string wrapped between two single quotes characters (') are escaped. + * Two single quotes in a row, whether inside or outside a quoted sequence, represent a 'real' single quote. + * + * Format of the format string is based on Unicode Technical Standard #35: + * https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table + * with a few additions (see note 5 below the table). + * + * Not all tokens are compatible. Combinations that don't make sense or could lead to bugs are prohibited + * and will throw `RangeError`. For example usage of 24-hour format token with AM/PM token will throw an exception: + * + * ```javascript + * parse('23 AM', 'HH a', new Date()) + * //=> RangeError: The format string mustn't contain `HH` and `a` at the same time + * ``` + * + * See the compatibility table: https://docs.google.com/spreadsheets/d/e/2PACX-1vQOPU3xUhplll6dyoMmVUXHKl_8CRDs6_ueLmex3SoqwhuolkuN3O05l4rqx5h1dKX8eb46Ul-CCSrq/pubhtml?gid=0&single=true + * + * Accepted format string patterns: + * | Unit |Prior| Pattern | Result examples | Notes | + * |---------------------------------|-----|---------|-----------------------------------|-------| + * | Era | 140 | G..GGG | AD, BC | | + * | | | GGGG | Anno Domini, Before Christ | 2 | + * | | | GGGGG | A, B | | + * | Calendar year | 130 | y | 44, 1, 1900, 2017, 9999 | 4 | + * | | | yo | 44th, 1st, 1900th, 9999999th | 4,5 | + * | | | yy | 44, 01, 00, 17 | 4 | + * | | | yyy | 044, 001, 123, 999 | 4 | + * | | | yyyy | 0044, 0001, 1900, 2017 | 4 | + * | | | yyyyy | ... | 2,4 | + * | Local week-numbering year | 130 | Y | 44, 1, 1900, 2017, 9000 | 4 | + * | | | Yo | 44th, 1st, 1900th, 9999999th | 4,5 | + * | | | YY | 44, 01, 00, 17 | 4,6 | + * | | | YYY | 044, 001, 123, 999 | 4 | + * | | | YYYY | 0044, 0001, 1900, 2017 | 4,6 | + * | | | YYYYY | ... | 2,4 | + * | ISO week-numbering year | 130 | R | -43, 1, 1900, 2017, 9999, -9999 | 4,5 | + * | | | RR | -43, 01, 00, 17 | 4,5 | + * | | | RRR | -043, 001, 123, 999, -999 | 4,5 | + * | | | RRRR | -0043, 0001, 2017, 9999, -9999 | 4,5 | + * | | | RRRRR | ... | 2,4,5 | + * | Extended year | 130 | u | -43, 1, 1900, 2017, 9999, -999 | 4 | + * | | | uu | -43, 01, 99, -99 | 4 | + * | | | uuu | -043, 001, 123, 999, -999 | 4 | + * | | | uuuu | -0043, 0001, 2017, 9999, -9999 | 4 | + * | | | uuuuu | ... | 2,4 | + * | Quarter (formatting) | 120 | Q | 1, 2, 3, 4 | | + * | | | Qo | 1st, 2nd, 3rd, 4th | 5 | + * | | | QQ | 01, 02, 03, 04 | | + * | | | QQQ | Q1, Q2, Q3, Q4 | | + * | | | QQQQ | 1st quarter, 2nd quarter, ... | 2 | + * | | | QQQQQ | 1, 2, 3, 4 | 4 | + * | Quarter (stand-alone) | 120 | q | 1, 2, 3, 4 | | + * | | | qo | 1st, 2nd, 3rd, 4th | 5 | + * | | | qq | 01, 02, 03, 04 | | + * | | | qqq | Q1, Q2, Q3, Q4 | | + * | | | qqqq | 1st quarter, 2nd quarter, ... | 2 | + * | | | qqqqq | 1, 2, 3, 4 | 3 | + * | Month (formatting) | 110 | M | 1, 2, ..., 12 | | + * | | | Mo | 1st, 2nd, ..., 12th | 5 | + * | | | MM | 01, 02, ..., 12 | | + * | | | MMM | Jan, Feb, ..., Dec | | + * | | | MMMM | January, February, ..., December | 2 | + * | | | MMMMM | J, F, ..., D | | + * | Month (stand-alone) | 110 | L | 1, 2, ..., 12 | | + * | | | Lo | 1st, 2nd, ..., 12th | 5 | + * | | | LL | 01, 02, ..., 12 | | + * | | | LLL | Jan, Feb, ..., Dec | | + * | | | LLLL | January, February, ..., December | 2 | + * | | | LLLLL | J, F, ..., D | | + * | Local week of year | 100 | w | 1, 2, ..., 53 | | + * | | | wo | 1st, 2nd, ..., 53th | 5 | + * | | | ww | 01, 02, ..., 53 | | + * | ISO week of year | 100 | I | 1, 2, ..., 53 | 5 | + * | | | Io | 1st, 2nd, ..., 53th | 5 | + * | | | II | 01, 02, ..., 53 | 5 | + * | Day of month | 90 | d | 1, 2, ..., 31 | | + * | | | do | 1st, 2nd, ..., 31st | 5 | + * | | | dd | 01, 02, ..., 31 | | + * | Day of year | 90 | D | 1, 2, ..., 365, 366 | 7 | + * | | | Do | 1st, 2nd, ..., 365th, 366th | 5 | + * | | | DD | 01, 02, ..., 365, 366 | 7 | + * | | | DDD | 001, 002, ..., 365, 366 | | + * | | | DDDD | ... | 2 | + * | Day of week (formatting) | 90 | E..EEE | Mon, Tue, Wed, ..., Sun | | + * | | | EEEE | Monday, Tuesday, ..., Sunday | 2 | + * | | | EEEEE | M, T, W, T, F, S, S | | + * | | | EEEEEE | Mo, Tu, We, Th, Fr, Su, Sa | | + * | ISO day of week (formatting) | 90 | i | 1, 2, 3, ..., 7 | 5 | + * | | | io | 1st, 2nd, ..., 7th | 5 | + * | | | ii | 01, 02, ..., 07 | 5 | + * | | | iii | Mon, Tue, Wed, ..., Sun | 5 | + * | | | iiii | Monday, Tuesday, ..., Sunday | 2,5 | + * | | | iiiii | M, T, W, T, F, S, S | 5 | + * | | | iiiiii | Mo, Tu, We, Th, Fr, Su, Sa | 5 | + * | Local day of week (formatting) | 90 | e | 2, 3, 4, ..., 1 | | + * | | | eo | 2nd, 3rd, ..., 1st | 5 | + * | | | ee | 02, 03, ..., 01 | | + * | | | eee | Mon, Tue, Wed, ..., Sun | | + * | | | eeee | Monday, Tuesday, ..., Sunday | 2 | + * | | | eeeee | M, T, W, T, F, S, S | | + * | | | eeeeee | Mo, Tu, We, Th, Fr, Su, Sa | | + * | Local day of week (stand-alone) | 90 | c | 2, 3, 4, ..., 1 | | + * | | | co | 2nd, 3rd, ..., 1st | 5 | + * | | | cc | 02, 03, ..., 01 | | + * | | | ccc | Mon, Tue, Wed, ..., Sun | | + * | | | cccc | Monday, Tuesday, ..., Sunday | 2 | + * | | | ccccc | M, T, W, T, F, S, S | | + * | | | cccccc | Mo, Tu, We, Th, Fr, Su, Sa | | + * | AM, PM | 80 | a..aaa | AM, PM | | + * | | | aaaa | a.m., p.m. | 2 | + * | | | aaaaa | a, p | | + * | AM, PM, noon, midnight | 80 | b..bbb | AM, PM, noon, midnight | | + * | | | bbbb | a.m., p.m., noon, midnight | 2 | + * | | | bbbbb | a, p, n, mi | | + * | Flexible day period | 80 | B..BBB | at night, in the morning, ... | | + * | | | BBBB | at night, in the morning, ... | 2 | + * | | | BBBBB | at night, in the morning, ... | | + * | Hour [1-12] | 70 | h | 1, 2, ..., 11, 12 | | + * | | | ho | 1st, 2nd, ..., 11th, 12th | 5 | + * | | | hh | 01, 02, ..., 11, 12 | | + * | Hour [0-23] | 70 | H | 0, 1, 2, ..., 23 | | + * | | | Ho | 0th, 1st, 2nd, ..., 23rd | 5 | + * | | | HH | 00, 01, 02, ..., 23 | | + * | Hour [0-11] | 70 | K | 1, 2, ..., 11, 0 | | + * | | | Ko | 1st, 2nd, ..., 11th, 0th | 5 | + * | | | KK | 01, 02, ..., 11, 00 | | + * | Hour [1-24] | 70 | k | 24, 1, 2, ..., 23 | | + * | | | ko | 24th, 1st, 2nd, ..., 23rd | 5 | + * | | | kk | 24, 01, 02, ..., 23 | | + * | Minute | 60 | m | 0, 1, ..., 59 | | + * | | | mo | 0th, 1st, ..., 59th | 5 | + * | | | mm | 00, 01, ..., 59 | | + * | Second | 50 | s | 0, 1, ..., 59 | | + * | | | so | 0th, 1st, ..., 59th | 5 | + * | | | ss | 00, 01, ..., 59 | | + * | Seconds timestamp | 40 | t | 512969520 | | + * | | | tt | ... | 2 | + * | Fraction of second | 30 | S | 0, 1, ..., 9 | | + * | | | SS | 00, 01, ..., 99 | | + * | | | SSS | 000, 0001, ..., 999 | | + * | | | SSSS | ... | 2 | + * | Milliseconds timestamp | 20 | T | 512969520900 | | + * | | | TT | ... | 2 | + * | Timezone (ISO-8601 w/ Z) | 10 | X | -08, +0530, Z | | + * | | | XX | -0800, +0530, Z | | + * | | | XXX | -08:00, +05:30, Z | | + * | | | XXXX | -0800, +0530, Z, +123456 | 2 | + * | | | XXXXX | -08:00, +05:30, Z, +12:34:56 | | + * | Timezone (ISO-8601 w/o Z) | 10 | x | -08, +0530, +00 | | + * | | | xx | -0800, +0530, +0000 | | + * | | | xxx | -08:00, +05:30, +00:00 | 2 | + * | | | xxxx | -0800, +0530, +0000, +123456 | | + * | | | xxxxx | -08:00, +05:30, +00:00, +12:34:56 | | + * | Long localized date | NA | P | 05/29/1453 | 5,8 | + * | | | PP | May 29, 1453 | | + * | | | PPP | May 29th, 1453 | | + * | | | PPPP | Sunday, May 29th, 1453 | 2,5,8 | + * | Long localized time | NA | p | 12:00 AM | 5,8 | + * | | | pp | 12:00:00 AM | | + * | Combination of date and time | NA | Pp | 05/29/1453, 12:00 AM | | + * | | | PPpp | May 29, 1453, 12:00:00 AM | | + * | | | PPPpp | May 29th, 1453 at ... | | + * | | | PPPPpp | Sunday, May 29th, 1453 at ... | 2,5,8 | + * Notes: + * 1. "Formatting" units (e.g. formatting quarter) in the default en-US locale + * are the same as "stand-alone" units, but are different in some languages. + * "Formatting" units are declined according to the rules of the language + * in the context of a date. "Stand-alone" units are always nominative singular. + * In `format` function, they will produce different result: + * + * `format(new Date(2017, 10, 6), 'do LLLL', {locale: cs}) //=> '6. listopad'` + * + * `format(new Date(2017, 10, 6), 'do MMMM', {locale: cs}) //=> '6. listopadu'` + * + * `parse` will try to match both formatting and stand-alone units interchangably. + * + * 2. Any sequence of the identical letters is a pattern, unless it is escaped by + * the single quote characters (see below). + * If the sequence is longer than listed in table: + * - for numerical units (`yyyyyyyy`) `parse` will try to match a number + * as wide as the sequence + * - for text units (`MMMMMMMM`) `parse` will try to match the widest variation of the unit. + * These variations are marked with "2" in the last column of the table. + * + * 3. `QQQQQ` and `qqqqq` could be not strictly numerical in some locales. + * These tokens represent the shortest form of the quarter. + * + * 4. The main difference between `y` and `u` patterns are B.C. years: + * + * | Year | `y` | `u` | + * |------|-----|-----| + * | AC 1 | 1 | 1 | + * | BC 1 | 1 | 0 | + * | BC 2 | 2 | -1 | + * + * Also `yy` will try to guess the century of two digit year by proximity with `referenceDate`: + * + * `parse('50', 'yy', new Date(2018, 0, 1)) //=> Sat Jan 01 2050 00:00:00` + * + * `parse('75', 'yy', new Date(2018, 0, 1)) //=> Wed Jan 01 1975 00:00:00` + * + * while `uu` will just assign the year as is: + * + * `parse('50', 'uu', new Date(2018, 0, 1)) //=> Sat Jan 01 0050 00:00:00` + * + * `parse('75', 'uu', new Date(2018, 0, 1)) //=> Tue Jan 01 0075 00:00:00` + * + * The same difference is true for local and ISO week-numbering years (`Y` and `R`), + * except local week-numbering years are dependent on `options.weekStartsOn` + * and `options.firstWeekContainsDate` (compare [setISOWeekYear]{@link https://date-fns.org/docs/setISOWeekYear} + * and [setWeekYear]{@link https://date-fns.org/docs/setWeekYear}). + * + * 5. These patterns are not in the Unicode Technical Standard #35: + * - `i`: ISO day of week + * - `I`: ISO week of year + * - `R`: ISO week-numbering year + * - `o`: ordinal number modifier + * - `P`: long localized date + * - `p`: long localized time + * + * 6. `YY` and `YYYY` tokens represent week-numbering years but they are often confused with years. + * You should enable `options.useAdditionalWeekYearTokens` to use them. See: https://git.io/fxCyr + * + * 7. `D` and `DD` tokens represent days of the year but they are ofthen confused with days of the month. + * You should enable `options.useAdditionalDayOfYearTokens` to use them. See: https://git.io/fxCyr + * + * 8. `P+` tokens do not have a defined priority since they are merely aliases to other tokens based + * on the given locale. + * + * using `en-US` locale: `P` => `MM/dd/yyyy` + * using `en-US` locale: `p` => `hh:mm a` + * using `pt-BR` locale: `P` => `dd/MM/yyyy` + * using `pt-BR` locale: `p` => `HH:mm` + * + * Values will be assigned to the date in the descending order of its unit's priority. + * Units of an equal priority overwrite each other in the order of appearance. + * + * If no values of higher priority are parsed (e.g. when parsing string 'January 1st' without a year), + * the values will be taken from 3rd argument `referenceDate` which works as a context of parsing. + * + * `referenceDate` must be passed for correct work of the function. + * If you're not sure which `referenceDate` to supply, create a new instance of Date: + * `parse('02/11/2014', 'MM/dd/yyyy', new Date())` + * In this case parsing will be done in the context of the current date. + * If `referenceDate` is `Invalid Date` or a value not convertible to valid `Date`, + * then `Invalid Date` will be returned. + * + * The result may vary by locale. + * + * If `formatString` matches with `dateString` but does not provides tokens, `referenceDate` will be returned. + * + * If parsing failed, `Invalid Date` will be returned. + * Invalid Date is a Date, whose time value is NaN. + * Time value of Date: http://es5.github.io/#x15.9.1.1 + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * - Old `parse` was renamed to `toDate`. + * Now `parse` is a new function which parses a string using a provided format. + * + * ```javascript + * // Before v2.0.0 + * parse('2016-01-01') + * + * // v2.0.0 onward (toDate no longer accepts a string) + * toDate(1392098430000) // Unix to timestamp + * toDate(new Date(2014, 1, 11, 11, 30, 30)) // Cloning the date + * parse('2016-01-01', 'yyyy-MM-dd', new Date()) + * ``` + * + * @param {String} dateString - the string to parse + * @param {String} formatString - the string of tokens + * @param {Date|Number} referenceDate - defines values missing from the parsed dateString + * @param {Object} [options] - an object with options. + * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale} + * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday) + * @param {1|2|3|4|5|6|7} [options.firstWeekContainsDate=1] - the day of January, which is always in the first week of the year + * @param {Boolean} [options.useAdditionalWeekYearTokens=false] - if true, allows usage of the week-numbering year tokens `YY` and `YYYY`; + * see: https://git.io/fxCyr + * @param {Boolean} [options.useAdditionalDayOfYearTokens=false] - if true, allows usage of the day of year tokens `D` and `DD`; + * see: https://git.io/fxCyr + * @returns {Date} the parsed date + * @throws {TypeError} 3 arguments required + * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6 + * @throws {RangeError} `options.firstWeekContainsDate` must be between 1 and 7 + * @throws {RangeError} `options.locale` must contain `match` property + * @throws {RangeError} use `yyyy` instead of `YYYY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `yy` instead of `YY` for formatting years using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `d` instead of `D` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} use `dd` instead of `DD` for formatting days of the month using [format provided] to the input [input provided]; see: https://git.io/fxCyr + * @throws {RangeError} format string contains an unescaped latin alphabet character + * + * @example + * // Parse 11 February 2014 from middle-endian format: + * var result = parse('02/11/2014', 'MM/dd/yyyy', new Date()) + * //=> Tue Feb 11 2014 00:00:00 + * + * @example + * // Parse 28th of February in Esperanto locale in the context of 2010 year: + * import eo from 'date-fns/locale/eo' + * var result = parse('28-a de februaro', "do 'de' MMMM", new Date(2010, 0, 1), { + * locale: eo + * }) + * //=> Sun Feb 28 2010 00:00:00 + */ + +function parse(dirtyDateString, dirtyFormatString, dirtyReferenceDate, dirtyOptions) { + requiredArgs(3, arguments); + var dateString = String(dirtyDateString); + var formatString = String(dirtyFormatString); + var options = dirtyOptions || {}; + var locale = options.locale || defaultLocale; + + if (!locale.match) { + throw new RangeError('locale must contain match property'); + } + + var localeFirstWeekContainsDate = locale.options && locale.options.firstWeekContainsDate; + var defaultFirstWeekContainsDate = localeFirstWeekContainsDate == null ? 1 : toInteger(localeFirstWeekContainsDate); + var firstWeekContainsDate = options.firstWeekContainsDate == null ? defaultFirstWeekContainsDate : toInteger(options.firstWeekContainsDate); // Test if weekStartsOn is between 1 and 7 _and_ is not NaN + + if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) { + throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively'); + } + + var localeWeekStartsOn = locale.options && locale.options.weekStartsOn; + var defaultWeekStartsOn = localeWeekStartsOn == null ? 0 : toInteger(localeWeekStartsOn); + var weekStartsOn = options.weekStartsOn == null ? defaultWeekStartsOn : toInteger(options.weekStartsOn); // Test if weekStartsOn is between 0 and 6 _and_ is not NaN + + if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) { + throw new RangeError('weekStartsOn must be between 0 and 6 inclusively'); + } + + if (formatString === '') { + if (dateString === '') { + return toDate(dirtyReferenceDate); + } else { + return new Date(NaN); + } + } + + var subFnOptions = { + firstWeekContainsDate: firstWeekContainsDate, + weekStartsOn: weekStartsOn, + locale: locale // If timezone isn't specified, it will be set to the system timezone + + }; + var setters = [{ + priority: TIMEZONE_UNIT_PRIORITY, + subPriority: -1, + set: dateToSystemTimezone, + index: 0 + }]; + var i; + var tokens = formatString.match(longFormattingTokensRegExp).map(function (substring) { + var firstCharacter = substring[0]; + + if (firstCharacter === 'p' || firstCharacter === 'P') { + var longFormatter = longFormatters$1[firstCharacter]; + return longFormatter(substring, locale.formatLong, subFnOptions); + } + + return substring; + }).join('').match(formattingTokensRegExp); + var usedTokens = []; + + for (i = 0; i < tokens.length; i++) { + var token = tokens[i]; + + if (!options.useAdditionalWeekYearTokens && isProtectedWeekYearToken(token)) { + throwProtectedError(token, formatString, dirtyDateString); + } + + if (!options.useAdditionalDayOfYearTokens && isProtectedDayOfYearToken(token)) { + throwProtectedError(token, formatString, dirtyDateString); + } + + var firstCharacter = token[0]; + var parser = parsers$1[firstCharacter]; + + if (parser) { + var incompatibleTokens = parser.incompatibleTokens; + + if (Array.isArray(incompatibleTokens)) { + var incompatibleToken = void 0; + + for (var _i = 0; _i < usedTokens.length; _i++) { + var usedToken = usedTokens[_i].token; + + if (incompatibleTokens.indexOf(usedToken) !== -1 || usedToken === firstCharacter) { + incompatibleToken = usedTokens[_i]; + break; + } + } + + if (incompatibleToken) { + throw new RangeError("The format string mustn't contain `".concat(incompatibleToken.fullToken, "` and `").concat(token, "` at the same time")); + } + } else if (parser.incompatibleTokens === '*' && usedTokens.length) { + throw new RangeError("The format string mustn't contain `".concat(token, "` and any other token at the same time")); + } + + usedTokens.push({ + token: firstCharacter, + fullToken: token + }); + var parseResult = parser.parse(dateString, token, locale.match, subFnOptions); + + if (!parseResult) { + return new Date(NaN); + } + + setters.push({ + priority: parser.priority, + subPriority: parser.subPriority || 0, + set: parser.set, + validate: parser.validate, + value: parseResult.value, + index: setters.length + }); + dateString = parseResult.rest; + } else { + if (firstCharacter.match(unescapedLatinCharacterRegExp)) { + throw new RangeError('Format string contains an unescaped latin alphabet character `' + firstCharacter + '`'); + } // Replace two single quote characters with one single quote character + + + if (token === "''") { + token = "'"; + } else if (firstCharacter === "'") { + token = cleanEscapedString(token); + } // Cut token from string, or, if string doesn't match the token, return Invalid Date + + + if (dateString.indexOf(token) === 0) { + dateString = dateString.slice(token.length); + } else { + return new Date(NaN); + } + } + } // Check if the remaining input contains something other than whitespace + + + if (dateString.length > 0 && notWhitespaceRegExp.test(dateString)) { + return new Date(NaN); + } + + var uniquePrioritySetters = setters.map(function (setter) { + return setter.priority; + }).sort(function (a, b) { + return b - a; + }).filter(function (priority, index, array) { + return array.indexOf(priority) === index; + }).map(function (priority) { + return setters.filter(function (setter) { + return setter.priority === priority; + }).sort(function (a, b) { + return b.subPriority - a.subPriority; + }); + }).map(function (setterArray) { + return setterArray[0]; + }); + var date = toDate(dirtyReferenceDate); + + if (isNaN(date)) { + return new Date(NaN); + } // Convert the date in system timezone to the same date in UTC+00:00 timezone. + // This ensures that when UTC functions will be implemented, locales will be compatible with them. + // See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/37 + + + var utcDate = subMilliseconds(date, getTimezoneOffsetInMilliseconds(date)); + var flags = {}; + + for (i = 0; i < uniquePrioritySetters.length; i++) { + var setter = uniquePrioritySetters[i]; + + if (setter.validate && !setter.validate(utcDate, setter.value, subFnOptions)) { + return new Date(NaN); + } + + var result = setter.set(utcDate, flags, setter.value, subFnOptions); // Result is tuple (date, flags) + + if (result[0]) { + utcDate = result[0]; + assign(flags, result[1]); // Result is date + } else { + utcDate = result; + } + } + + return utcDate; +} + +function dateToSystemTimezone(date, flags) { + if (flags.timestampIsSet) { + return date; + } + + var convertedDate = new Date(0); + convertedDate.setFullYear(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); + convertedDate.setHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()); + return convertedDate; +} + +function cleanEscapedString(input) { + return input.match(escapedStringRegExp)[1].replace(doubleQuoteRegExp, "'"); +} + +/** + * @name startOfHour + * @category Hour Helpers + * @summary Return the start of an hour for the given date. + * + * @description + * Return the start of an hour for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of an hour + * @throws {TypeError} 1 argument required + * + * @example + * // The start of an hour for 2 September 2014 11:55:00: + * const result = startOfHour(new Date(2014, 8, 2, 11, 55)) + * //=> Tue Sep 02 2014 11:00:00 + */ + +function startOfHour(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setMinutes(0, 0, 0); + return date; +} + +/** + * @name startOfMinute + * @category Minute Helpers + * @summary Return the start of a minute for the given date. + * + * @description + * Return the start of a minute for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a minute + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a minute for 1 December 2014 22:15:45.400: + * const result = startOfMinute(new Date(2014, 11, 1, 22, 15, 45, 400)) + * //=> Mon Dec 01 2014 22:15:00 + */ + +function startOfMinute(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setSeconds(0, 0); + return date; +} + +/** + * @name startOfSecond + * @category Second Helpers + * @summary Return the start of a second for the given date. + * + * @description + * Return the start of a second for the given date. + * The result will be in the local timezone. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * @param {Date|Number} date - the original date + * @returns {Date} the start of a second + * @throws {TypeError} 1 argument required + * + * @example + * // The start of a second for 1 December 2014 22:15:45.400: + * const result = startOfSecond(new Date(2014, 11, 1, 22, 15, 45, 400)) + * //=> Mon Dec 01 2014 22:15:45.000 + */ + +function startOfSecond(dirtyDate) { + requiredArgs(1, arguments); + var date = toDate(dirtyDate); + date.setMilliseconds(0); + return date; +} + +var MILLISECONDS_IN_HOUR = 3600000; +var MILLISECONDS_IN_MINUTE = 60000; +var DEFAULT_ADDITIONAL_DIGITS = 2; +var patterns = { + dateTimeDelimiter: /[T ]/, + timeZoneDelimiter: /[Z ]/i, + timezone: /([Z+-].*)$/ +}; +var dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/; +var timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/; +var timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/; +/** + * @name parseISO + * @category Common Helpers + * @summary Parse ISO string + * + * @description + * Parse the given string in ISO 8601 format and return an instance of Date. + * + * Function accepts complete ISO 8601 formats as well as partial implementations. + * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 + * + * If the argument isn't a string, the function cannot parse the string or + * the values are invalid, it returns Invalid Date. + * + * ### v2.0.0 breaking changes: + * + * - [Changes that are common for the whole library](https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#Common-Changes). + * + * - The previous `parse` implementation was renamed to `parseISO`. + * + * ```javascript + * // Before v2.0.0 + * parse('2016-01-01') + * + * // v2.0.0 onward + * parseISO('2016-01-01') + * ``` + * + * - `parseISO` now validates separate date and time values in ISO-8601 strings + * and returns `Invalid Date` if the date is invalid. + * + * ```javascript + * parseISO('2018-13-32') + * //=> Invalid Date + * ``` + * + * - `parseISO` now doesn't fall back to `new Date` constructor + * if it fails to parse a string argument. Instead, it returns `Invalid Date`. + * + * @param {String} argument - the value to convert + * @param {Object} [options] - an object with options. + * @param {0|1|2} [options.additionalDigits=2] - the additional number of digits in the extended year format + * @returns {Date} the parsed date in the local time zone + * @throws {TypeError} 1 argument required + * @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2 + * + * @example + * // Convert string '2014-02-11T11:30:30' to date: + * var result = parseISO('2014-02-11T11:30:30') + * //=> Tue Feb 11 2014 11:30:30 + * + * @example + * // Convert string '+02014101' to date, + * // if the additional number of digits in the extended year format is 1: + * var result = parseISO('+02014101', { additionalDigits: 1 }) + * //=> Fri Apr 11 2014 00:00:00 + */ + +function parseISO(argument, dirtyOptions) { + requiredArgs(1, arguments); + var options = dirtyOptions || {}; + var additionalDigits = options.additionalDigits == null ? DEFAULT_ADDITIONAL_DIGITS : toInteger(options.additionalDigits); + + if (additionalDigits !== 2 && additionalDigits !== 1 && additionalDigits !== 0) { + throw new RangeError('additionalDigits must be 0, 1 or 2'); + } + + if (!(typeof argument === 'string' || Object.prototype.toString.call(argument) === '[object String]')) { + return new Date(NaN); + } + + var dateStrings = splitDateString(argument); + var date; + + if (dateStrings.date) { + var parseYearResult = parseYear(dateStrings.date, additionalDigits); + date = parseDate(parseYearResult.restDateString, parseYearResult.year); + } + + if (isNaN(date) || !date) { + return new Date(NaN); + } + + var timestamp = date.getTime(); + var time = 0; + var offset; + + if (dateStrings.time) { + time = parseTime(dateStrings.time); + + if (isNaN(time) || time === null) { + return new Date(NaN); + } + } + + if (dateStrings.timezone) { + offset = parseTimezone(dateStrings.timezone); + + if (isNaN(offset)) { + return new Date(NaN); + } + } else { + var dirtyDate = new Date(timestamp + time); // js parsed string assuming it's in UTC timezone + // but we need it to be parsed in our timezone + // so we use utc values to build date in our timezone. + // Year values from 0 to 99 map to the years 1900 to 1999 + // so set year explicitly with setFullYear. + + var result = new Date(0); + result.setFullYear(dirtyDate.getUTCFullYear(), dirtyDate.getUTCMonth(), dirtyDate.getUTCDate()); + result.setHours(dirtyDate.getUTCHours(), dirtyDate.getUTCMinutes(), dirtyDate.getUTCSeconds(), dirtyDate.getUTCMilliseconds()); + return result; + } + + return new Date(timestamp + time + offset); +} + +function splitDateString(dateString) { + var dateStrings = {}; + var array = dateString.split(patterns.dateTimeDelimiter); + var timeString; // The regex match should only return at maximum two array elements. + // [date], [time], or [date, time]. + + if (array.length > 2) { + return dateStrings; + } + + if (/:/.test(array[0])) { + dateStrings.date = null; + timeString = array[0]; + } else { + dateStrings.date = array[0]; + timeString = array[1]; + + if (patterns.timeZoneDelimiter.test(dateStrings.date)) { + dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0]; + timeString = dateString.substr(dateStrings.date.length, dateString.length); + } + } + + if (timeString) { + var token = patterns.timezone.exec(timeString); + + if (token) { + dateStrings.time = timeString.replace(token[1], ''); + dateStrings.timezone = token[1]; + } else { + dateStrings.time = timeString; + } + } + + return dateStrings; +} + +function parseYear(dateString, additionalDigits) { + var regex = new RegExp('^(?:(\\d{4}|[+-]\\d{' + (4 + additionalDigits) + '})|(\\d{2}|[+-]\\d{' + (2 + additionalDigits) + '})$)'); + var captures = dateString.match(regex); // Invalid ISO-formatted year + + if (!captures) return { + year: null + }; + var year = captures[1] && parseInt(captures[1]); + var century = captures[2] && parseInt(captures[2]); + return { + year: century == null ? year : century * 100, + restDateString: dateString.slice((captures[1] || captures[2]).length) + }; +} + +function parseDate(dateString, year) { + // Invalid ISO-formatted year + if (year === null) return null; + var captures = dateString.match(dateRegex); // Invalid ISO-formatted string + + if (!captures) return null; + var isWeekDate = !!captures[4]; + var dayOfYear = parseDateUnit(captures[1]); + var month = parseDateUnit(captures[2]) - 1; + var day = parseDateUnit(captures[3]); + var week = parseDateUnit(captures[4]); + var dayOfWeek = parseDateUnit(captures[5]) - 1; + + if (isWeekDate) { + if (!validateWeekDate(year, week, dayOfWeek)) { + return new Date(NaN); + } + + return dayOfISOWeekYear(year, week, dayOfWeek); + } else { + var date = new Date(0); + + if (!validateDate(year, month, day) || !validateDayOfYearDate(year, dayOfYear)) { + return new Date(NaN); + } + + date.setUTCFullYear(year, month, Math.max(dayOfYear, day)); + return date; + } +} + +function parseDateUnit(value) { + return value ? parseInt(value) : 1; +} + +function parseTime(timeString) { + var captures = timeString.match(timeRegex); + if (!captures) return null; // Invalid ISO-formatted time + + var hours = parseTimeUnit(captures[1]); + var minutes = parseTimeUnit(captures[2]); + var seconds = parseTimeUnit(captures[3]); + + if (!validateTime(hours, minutes, seconds)) { + return NaN; + } + + return hours * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE + seconds * 1000; +} + +function parseTimeUnit(value) { + return value && parseFloat(value.replace(',', '.')) || 0; +} + +function parseTimezone(timezoneString) { + if (timezoneString === 'Z') return 0; + var captures = timezoneString.match(timezoneRegex); + if (!captures) return 0; + var sign = captures[1] === '+' ? -1 : 1; + var hours = parseInt(captures[2]); + var minutes = captures[3] && parseInt(captures[3]) || 0; + + if (!validateTimezone(hours, minutes)) { + return NaN; + } + + return sign * (hours * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE); +} + +function dayOfISOWeekYear(isoWeekYear, week, day) { + var date = new Date(0); + date.setUTCFullYear(isoWeekYear, 0, 4); + var fourthOfJanuaryDay = date.getUTCDay() || 7; + var diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay; + date.setUTCDate(date.getUTCDate() + diff); + return date; +} // Validation functions +// February is null to handle the leap year (using ||) + + +var daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +function isLeapYearIndex(year) { + return year % 400 === 0 || year % 4 === 0 && year % 100; +} + +function validateDate(year, month, date) { + return month >= 0 && month <= 11 && date >= 1 && date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28)); +} + +function validateDayOfYearDate(year, dayOfYear) { + return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365); +} + +function validateWeekDate(_year, week, day) { + return week >= 1 && week <= 53 && day >= 0 && day <= 6; +} + +function validateTime(hours, minutes, seconds) { + if (hours === 24) { + return minutes === 0 && seconds === 0; + } + + return seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25; +} + +function validateTimezone(_hours, minutes) { + return minutes >= 0 && minutes <= 59; +} + +const FORMATS = { + datetime: 'MMM d, yyyy, h:mm:ss aaaa', + millisecond: 'h:mm:ss.SSS aaaa', + second: 'h:mm:ss aaaa', + minute: 'h:mm aaaa', + hour: 'ha', + day: 'MMM d', + week: 'PP', + month: 'MMM yyyy', + quarter: 'qqq - yyyy', + year: 'yyyy' +}; + +chart_js._adapters._date.override({ + _id: 'date-fns', // DEBUG + + formats: function() { + return FORMATS; + }, + + parse: function(value, fmt) { + if (value === null || typeof value === 'undefined') { + return null; + } + const type = typeof value; + if (type === 'number' || value instanceof Date) { + value = toDate(value); + } else if (type === 'string') { + if (typeof fmt === 'string') { + value = parse(value, fmt, new Date(), this.options); + } else { + value = parseISO(value, this.options); + } + } + return isValid(value) ? value.getTime() : null; + }, + + format: function(time, fmt) { + return format(time, fmt, this.options); + }, + + add: function(time, amount, unit) { + switch (unit) { + case 'millisecond': return addMilliseconds(time, amount); + case 'second': return addSeconds(time, amount); + case 'minute': return addMinutes(time, amount); + case 'hour': return addHours(time, amount); + case 'day': return addDays(time, amount); + case 'week': return addWeeks(time, amount); + case 'month': return addMonths(time, amount); + case 'quarter': return addQuarters(time, amount); + case 'year': return addYears(time, amount); + default: return time; + } + }, + + diff: function(max, min, unit) { + switch (unit) { + case 'millisecond': return differenceInMilliseconds(max, min); + case 'second': return differenceInSeconds(max, min); + case 'minute': return differenceInMinutes(max, min); + case 'hour': return differenceInHours(max, min); + case 'day': return differenceInDays(max, min); + case 'week': return differenceInWeeks(max, min); + case 'month': return differenceInMonths(max, min); + case 'quarter': return differenceInQuarters(max, min); + case 'year': return differenceInYears(max, min); + default: return 0; + } + }, + + startOf: function(time, unit, weekday) { + switch (unit) { + case 'second': return startOfSecond(time); + case 'minute': return startOfMinute(time); + case 'hour': return startOfHour(time); + case 'day': return startOfDay(time); + case 'week': return startOfWeek(time); + case 'isoWeek': return startOfWeek(time, {weekStartsOn: +weekday}); + case 'month': return startOfMonth(time); + case 'quarter': return startOfQuarter(time); + case 'year': return startOfYear(time); + default: return time; + } + }, + + endOf: function(time, unit) { + switch (unit) { + case 'second': return endOfSecond(time); + case 'minute': return endOfMinute(time); + case 'hour': return endOfHour(time); + case 'day': return endOfDay(time); + case 'week': return endOfWeek(time); + case 'month': return endOfMonth(time); + case 'quarter': return endOfQuarter(time); + case 'year': return endOfYear(time); + default: return time; + } + } +}); + +})); diff --git a/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js b/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js new file mode 100644 index 0000000..37bffe6 --- /dev/null +++ b/static/js/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * chartjs-adapter-date-fns v3.0.0 + * https://www.chartjs.org + * (c) 2022 chartjs-adapter-date-fns Contributors + * Released under the MIT license + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(require("chart.js")):"function"==typeof define&&define.amd?define(["chart.js"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Chart)}(this,(function(t){"use strict";function e(t){if(null===t||!0===t||!1===t)return NaN;var e=Number(t);return isNaN(e)?e:e<0?Math.ceil(e):Math.floor(e)}function r(t,e){if(e.length1?"s":"")+" required, but only "+e.length+" present")}function n(t){r(1,arguments);var e=Object.prototype.toString.call(t);return t instanceof Date||"object"==typeof t&&"[object Date]"===e?new Date(t.getTime()):"number"==typeof t||"[object Number]"===e?new Date(t):("string"!=typeof t&&"[object String]"!==e||"undefined"==typeof console||(console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://git.io/fjule"),console.warn((new Error).stack)),new Date(NaN))}function a(t,a){r(2,arguments);var i=n(t),o=e(a);return isNaN(o)?new Date(NaN):o?(i.setDate(i.getDate()+o),i):i}function i(t,a){r(2,arguments);var i=n(t),o=e(a);if(isNaN(o))return new Date(NaN);if(!o)return i;var u=i.getDate(),s=new Date(i.getTime());s.setMonth(i.getMonth()+o+1,0);var c=s.getDate();return u>=c?s:(i.setFullYear(s.getFullYear(),s.getMonth(),u),i)}function o(t,a){r(2,arguments);var i=n(t).getTime(),o=e(a);return new Date(i+o)}var u=36e5;function s(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getDay(),f=(l0?1:o}function m(t){r(1,arguments);var e=n(t);return!isNaN(e)}function w(t,e){r(2,arguments);var a=n(t),i=n(e),o=a.getFullYear()-i.getFullYear(),u=a.getMonth()-i.getMonth();return 12*o+u}function g(t,e){r(2,arguments);var a=n(t),i=n(e);return a.getFullYear()-i.getFullYear()}function v(t,e){var r=t.getFullYear()-e.getFullYear()||t.getMonth()-e.getMonth()||t.getDate()-e.getDate()||t.getHours()-e.getHours()||t.getMinutes()-e.getMinutes()||t.getSeconds()-e.getSeconds()||t.getMilliseconds()-e.getMilliseconds();return r<0?-1:r>0?1:r}function y(t,e){r(2,arguments);var a=n(t),i=n(e),o=v(a,i),u=Math.abs(f(a,i));a.setDate(a.getDate()-o*u);var s=v(a,i)===-o,c=o*(u-s);return 0===c?0:c}function b(t,e){r(2,arguments);var a=n(t),i=n(e);return a.getTime()-i.getTime()}var T=36e5;function p(t){r(1,arguments);var e=n(t);return e.setHours(23,59,59,999),e}function C(t){r(1,arguments);var e=n(t),a=e.getMonth();return e.setFullYear(e.getFullYear(),a+1,0),e.setHours(23,59,59,999),e}function M(t){r(1,arguments);var e=n(t);return p(e).getTime()===C(e).getTime()}function D(t,e){r(2,arguments);var a,i=n(t),o=n(e),u=h(i,o),s=Math.abs(w(i,o));if(s<1)a=0;else{1===i.getMonth()&&i.getDate()>27&&i.setDate(30),i.setMonth(i.getMonth()-u*s);var c=h(i,o)===-u;M(n(t))&&1===s&&1===h(t,o)&&(c=!1),a=u*(s-c)}return 0===a?0:a}var x={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}};function k(t){return function(e){var r=e||{},n=r.width?String(r.width):t.defaultWidth;return t.formats[n]||t.formats[t.defaultWidth]}}var U={date:k({formats:{full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},defaultWidth:"full"}),time:k({formats:{full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},defaultWidth:"full"}),dateTime:k({formats:{full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},defaultWidth:"full"})},Y={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"};function N(t){return function(e,r){var n,a=r||{};if("formatting"===(a.context?String(a.context):"standalone")&&t.formattingValues){var i=t.defaultFormattingWidth||t.defaultWidth,o=a.width?String(a.width):i;n=t.formattingValues[o]||t.formattingValues[i]}else{var u=t.defaultWidth,s=a.width?String(a.width):t.defaultWidth;n=t.values[s]||t.values[u]}return n[t.argumentCallback?t.argumentCallback(e):e]}}function S(t){return function(e,r){var n=String(e),a=r||{},i=a.width,o=i&&t.matchPatterns[i]||t.matchPatterns[t.defaultMatchWidth],u=n.match(o);if(!u)return null;var s,c=u[0],d=i&&t.parsePatterns[i]||t.parsePatterns[t.defaultParseWidth];return s="[object Array]"===Object.prototype.toString.call(d)?function(t,e){for(var r=0;r0?"in "+n:n+" ago":n},formatLong:U,formatRelative:function(t,e,r,n){return Y[t]},localize:{ordinalNumber:function(t,e){var r=Number(t),n=r%100;if(n>20||n<10)switch(n%10){case 1:return r+"st";case 2:return r+"nd";case 3:return r+"rd"}return r+"th"},era:N({values:{narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},defaultWidth:"wide"}),quarter:N({values:{narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter","2nd quarter","3rd quarter","4th quarter"]},defaultWidth:"wide",argumentCallback:function(t){return Number(t)-1}}),month:N({values:{narrow:["J","F","M","A","M","J","J","A","S","O","N","D"],abbreviated:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],wide:["January","February","March","April","May","June","July","August","September","October","November","December"]},defaultWidth:"wide"}),day:N({values:{narrow:["S","M","T","W","T","F","S"],short:["Su","Mo","Tu","We","Th","Fr","Sa"],abbreviated:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],wide:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"]},defaultWidth:"wide"}),dayPeriod:N({values:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"morning",afternoon:"afternoon",evening:"evening",night:"night"}},defaultWidth:"wide",formattingValues:{narrow:{am:"a",pm:"p",midnight:"mi",noon:"n",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},abbreviated:{am:"AM",pm:"PM",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"},wide:{am:"a.m.",pm:"p.m.",midnight:"midnight",noon:"noon",morning:"in the morning",afternoon:"in the afternoon",evening:"in the evening",night:"at night"}},defaultFormattingWidth:"wide"})},match:{ordinalNumber:(P={matchPattern:/^(\d+)(th|st|nd|rd)?/i,parsePattern:/\d+/i,valueCallback:function(t){return parseInt(t,10)}},function(t,e){var r=String(t),n=e||{},a=r.match(P.matchPattern);if(!a)return null;var i=a[0],o=r.match(P.parsePattern);if(!o)return null;var u=P.valueCallback?P.valueCallback(o[0]):o[0];return{value:u=n.valueCallback?n.valueCallback(u):u,rest:r.slice(i.length)}}),era:S({matchPatterns:{narrow:/^(b|a)/i,abbreviated:/^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,wide:/^(before christ|before common era|anno domini|common era)/i},defaultMatchWidth:"wide",parsePatterns:{any:[/^b/i,/^(a|c)/i]},defaultParseWidth:"any"}),quarter:S({matchPatterns:{narrow:/^[1234]/i,abbreviated:/^q[1234]/i,wide:/^[1234](th|st|nd|rd)? quarter/i},defaultMatchWidth:"wide",parsePatterns:{any:[/1/i,/2/i,/3/i,/4/i]},defaultParseWidth:"any",valueCallback:function(t){return t+1}}),month:S({matchPatterns:{narrow:/^[jfmasond]/i,abbreviated:/^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,wide:/^(january|february|march|april|may|june|july|august|september|october|november|december)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^j/i,/^f/i,/^m/i,/^a/i,/^m/i,/^j/i,/^j/i,/^a/i,/^s/i,/^o/i,/^n/i,/^d/i],any:[/^ja/i,/^f/i,/^mar/i,/^ap/i,/^may/i,/^jun/i,/^jul/i,/^au/i,/^s/i,/^o/i,/^n/i,/^d/i]},defaultParseWidth:"any"}),day:S({matchPatterns:{narrow:/^[smtwf]/i,short:/^(su|mo|tu|we|th|fr|sa)/i,abbreviated:/^(sun|mon|tue|wed|thu|fri|sat)/i,wide:/^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i},defaultMatchWidth:"wide",parsePatterns:{narrow:[/^s/i,/^m/i,/^t/i,/^w/i,/^t/i,/^f/i,/^s/i],any:[/^su/i,/^m/i,/^tu/i,/^w/i,/^th/i,/^f/i,/^sa/i]},defaultParseWidth:"any"}),dayPeriod:S({matchPatterns:{narrow:/^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,any:/^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i},defaultMatchWidth:"any",parsePatterns:{any:{am:/^a/i,pm:/^p/i,midnight:/^mi/i,noon:/^no/i,morning:/morning/i,afternoon:/afternoon/i,evening:/evening/i,night:/night/i}},defaultParseWidth:"any"})},options:{weekStartsOn:0,firstWeekContainsDate:1}};function H(t,n){r(2,arguments);var a=e(n);return o(t,-a)}function E(t,e){for(var r=t<0?"-":"",n=Math.abs(t).toString();n.length0?r:1-r;return E("yy"===e?n%100:n,e.length)},M:function(t,e){var r=t.getUTCMonth();return"M"===e?String(r+1):E(r+1,2)},d:function(t,e){return E(t.getUTCDate(),e.length)},a:function(t,e){var r=t.getUTCHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return r.toUpperCase();case"aaa":return r;case"aaaaa":return r[0];default:return"am"===r?"a.m.":"p.m."}},h:function(t,e){return E(t.getUTCHours()%12||12,e.length)},H:function(t,e){return E(t.getUTCHours(),e.length)},m:function(t,e){return E(t.getUTCMinutes(),e.length)},s:function(t,e){return E(t.getUTCSeconds(),e.length)},S:function(t,e){var r=e.length,n=t.getUTCMilliseconds();return E(Math.floor(n*Math.pow(10,r-3)),e.length)}},F=864e5;function W(t){r(1,arguments);var e=1,a=n(t),i=a.getUTCDay(),o=(i=o.getTime()?a+1:e.getTime()>=s.getTime()?a:a-1}function Q(t){r(1,arguments);var e=L(t),n=new Date(0);n.setUTCFullYear(e,0,4),n.setUTCHours(0,0,0,0);var a=W(n);return a}var R=6048e5;function I(t){r(1,arguments);var e=n(t),a=W(e).getTime()-Q(e).getTime();return Math.round(a/R)+1}function G(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getUTCDay(),f=(l=1&&l<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var f=new Date(0);f.setUTCFullYear(o+1,0,l),f.setUTCHours(0,0,0,0);var h=G(f,a),m=new Date(0);m.setUTCFullYear(o,0,l),m.setUTCHours(0,0,0,0);var w=G(m,a);return i.getTime()>=h.getTime()?o+1:i.getTime()>=w.getTime()?o:o-1}function j(t,n){r(1,arguments);var a=n||{},i=a.locale,o=i&&i.options&&i.options.firstWeekContainsDate,u=null==o?1:e(o),s=null==a.firstWeekContainsDate?u:e(a.firstWeekContainsDate),c=X(t,n),d=new Date(0);d.setUTCFullYear(c,0,s),d.setUTCHours(0,0,0,0);var l=G(d,n);return l}var B=6048e5;function z(t,e){r(1,arguments);var a=n(t),i=G(a,e).getTime()-j(a,e).getTime();return Math.round(i/B)+1}var A="midnight",Z="noon",K="morning",$="afternoon",_="evening",J="night",V={G:function(t,e,r){var n=t.getUTCFullYear()>0?1:0;switch(e){case"G":case"GG":case"GGG":return r.era(n,{width:"abbreviated"});case"GGGGG":return r.era(n,{width:"narrow"});default:return r.era(n,{width:"wide"})}},y:function(t,e,r){if("yo"===e){var n=t.getUTCFullYear(),a=n>0?n:1-n;return r.ordinalNumber(a,{unit:"year"})}return O.y(t,e)},Y:function(t,e,r,n){var a=X(t,n),i=a>0?a:1-a;return"YY"===e?E(i%100,2):"Yo"===e?r.ordinalNumber(i,{unit:"year"}):E(i,e.length)},R:function(t,e){return E(L(t),e.length)},u:function(t,e){return E(t.getUTCFullYear(),e.length)},Q:function(t,e,r){var n=Math.ceil((t.getUTCMonth()+1)/3);switch(e){case"Q":return String(n);case"QQ":return E(n,2);case"Qo":return r.ordinalNumber(n,{unit:"quarter"});case"QQQ":return r.quarter(n,{width:"abbreviated",context:"formatting"});case"QQQQQ":return r.quarter(n,{width:"narrow",context:"formatting"});default:return r.quarter(n,{width:"wide",context:"formatting"})}},q:function(t,e,r){var n=Math.ceil((t.getUTCMonth()+1)/3);switch(e){case"q":return String(n);case"qq":return E(n,2);case"qo":return r.ordinalNumber(n,{unit:"quarter"});case"qqq":return r.quarter(n,{width:"abbreviated",context:"standalone"});case"qqqqq":return r.quarter(n,{width:"narrow",context:"standalone"});default:return r.quarter(n,{width:"wide",context:"standalone"})}},M:function(t,e,r){var n=t.getUTCMonth();switch(e){case"M":case"MM":return O.M(t,e);case"Mo":return r.ordinalNumber(n+1,{unit:"month"});case"MMM":return r.month(n,{width:"abbreviated",context:"formatting"});case"MMMMM":return r.month(n,{width:"narrow",context:"formatting"});default:return r.month(n,{width:"wide",context:"formatting"})}},L:function(t,e,r){var n=t.getUTCMonth();switch(e){case"L":return String(n+1);case"LL":return E(n+1,2);case"Lo":return r.ordinalNumber(n+1,{unit:"month"});case"LLL":return r.month(n,{width:"abbreviated",context:"standalone"});case"LLLLL":return r.month(n,{width:"narrow",context:"standalone"});default:return r.month(n,{width:"wide",context:"standalone"})}},w:function(t,e,r,n){var a=z(t,n);return"wo"===e?r.ordinalNumber(a,{unit:"week"}):E(a,e.length)},I:function(t,e,r){var n=I(t);return"Io"===e?r.ordinalNumber(n,{unit:"week"}):E(n,e.length)},d:function(t,e,r){return"do"===e?r.ordinalNumber(t.getUTCDate(),{unit:"date"}):O.d(t,e)},D:function(t,e,a){var i=function(t){r(1,arguments);var e=n(t),a=e.getTime();e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0);var i=e.getTime(),o=a-i;return Math.floor(o/F)+1}(t);return"Do"===e?a.ordinalNumber(i,{unit:"dayOfYear"}):E(i,e.length)},E:function(t,e,r){var n=t.getUTCDay();switch(e){case"E":case"EE":case"EEE":return r.day(n,{width:"abbreviated",context:"formatting"});case"EEEEE":return r.day(n,{width:"narrow",context:"formatting"});case"EEEEEE":return r.day(n,{width:"short",context:"formatting"});default:return r.day(n,{width:"wide",context:"formatting"})}},e:function(t,e,r,n){var a=t.getUTCDay(),i=(a-n.weekStartsOn+8)%7||7;switch(e){case"e":return String(i);case"ee":return E(i,2);case"eo":return r.ordinalNumber(i,{unit:"day"});case"eee":return r.day(a,{width:"abbreviated",context:"formatting"});case"eeeee":return r.day(a,{width:"narrow",context:"formatting"});case"eeeeee":return r.day(a,{width:"short",context:"formatting"});default:return r.day(a,{width:"wide",context:"formatting"})}},c:function(t,e,r,n){var a=t.getUTCDay(),i=(a-n.weekStartsOn+8)%7||7;switch(e){case"c":return String(i);case"cc":return E(i,e.length);case"co":return r.ordinalNumber(i,{unit:"day"});case"ccc":return r.day(a,{width:"abbreviated",context:"standalone"});case"ccccc":return r.day(a,{width:"narrow",context:"standalone"});case"cccccc":return r.day(a,{width:"short",context:"standalone"});default:return r.day(a,{width:"wide",context:"standalone"})}},i:function(t,e,r){var n=t.getUTCDay(),a=0===n?7:n;switch(e){case"i":return String(a);case"ii":return E(a,e.length);case"io":return r.ordinalNumber(a,{unit:"day"});case"iii":return r.day(n,{width:"abbreviated",context:"formatting"});case"iiiii":return r.day(n,{width:"narrow",context:"formatting"});case"iiiiii":return r.day(n,{width:"short",context:"formatting"});default:return r.day(n,{width:"wide",context:"formatting"})}},a:function(t,e,r){var n=t.getUTCHours()/12>=1?"pm":"am";switch(e){case"a":case"aa":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"aaa":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"}).toLowerCase();case"aaaaa":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},b:function(t,e,r){var n,a=t.getUTCHours();switch(n=12===a?Z:0===a?A:a/12>=1?"pm":"am",e){case"b":case"bb":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"bbb":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"}).toLowerCase();case"bbbbb":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},B:function(t,e,r){var n,a=t.getUTCHours();switch(n=a>=17?_:a>=12?$:a>=4?K:J,e){case"B":case"BB":case"BBB":return r.dayPeriod(n,{width:"abbreviated",context:"formatting"});case"BBBBB":return r.dayPeriod(n,{width:"narrow",context:"formatting"});default:return r.dayPeriod(n,{width:"wide",context:"formatting"})}},h:function(t,e,r){if("ho"===e){var n=t.getUTCHours()%12;return 0===n&&(n=12),r.ordinalNumber(n,{unit:"hour"})}return O.h(t,e)},H:function(t,e,r){return"Ho"===e?r.ordinalNumber(t.getUTCHours(),{unit:"hour"}):O.H(t,e)},K:function(t,e,r){var n=t.getUTCHours()%12;return"Ko"===e?r.ordinalNumber(n,{unit:"hour"}):E(n,e.length)},k:function(t,e,r){var n=t.getUTCHours();return 0===n&&(n=24),"ko"===e?r.ordinalNumber(n,{unit:"hour"}):E(n,e.length)},m:function(t,e,r){return"mo"===e?r.ordinalNumber(t.getUTCMinutes(),{unit:"minute"}):O.m(t,e)},s:function(t,e,r){return"so"===e?r.ordinalNumber(t.getUTCSeconds(),{unit:"second"}):O.s(t,e)},S:function(t,e){return O.S(t,e)},X:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();if(0===a)return"Z";switch(e){case"X":return et(a);case"XXXX":case"XX":return rt(a);default:return rt(a,":")}},x:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"x":return et(a);case"xxxx":case"xx":return rt(a);default:return rt(a,":")}},O:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"O":case"OO":case"OOO":return"GMT"+tt(a,":");default:return"GMT"+rt(a,":")}},z:function(t,e,r,n){var a=(n._originalDate||t).getTimezoneOffset();switch(e){case"z":case"zz":case"zzz":return"GMT"+tt(a,":");default:return"GMT"+rt(a,":")}},t:function(t,e,r,n){var a=n._originalDate||t;return E(Math.floor(a.getTime()/1e3),e.length)},T:function(t,e,r,n){return E((n._originalDate||t).getTime(),e.length)}};function tt(t,e){var r=t>0?"-":"+",n=Math.abs(t),a=Math.floor(n/60),i=n%60;if(0===i)return r+String(a);var o=e||"";return r+String(a)+o+E(i,2)}function et(t,e){return t%60==0?(t>0?"-":"+")+E(Math.abs(t)/60,2):rt(t,e)}function rt(t,e){var r=e||"",n=t>0?"-":"+",a=Math.abs(t);return n+E(Math.floor(a/60),2)+r+E(a%60,2)}var nt=V;function at(t,e){switch(t){case"P":return e.date({width:"short"});case"PP":return e.date({width:"medium"});case"PPP":return e.date({width:"long"});default:return e.date({width:"full"})}}function it(t,e){switch(t){case"p":return e.time({width:"short"});case"pp":return e.time({width:"medium"});case"ppp":return e.time({width:"long"});default:return e.time({width:"full"})}}var ot={p:it,P:function(t,e){var r,n=t.match(/(P+)(p+)?/),a=n[1],i=n[2];if(!i)return at(t,e);switch(a){case"P":r=e.dateTime({width:"short"});break;case"PP":r=e.dateTime({width:"medium"});break;case"PPP":r=e.dateTime({width:"long"});break;default:r=e.dateTime({width:"full"})}return r.replace("{{date}}",at(a,e)).replace("{{time}}",it(i,e))}},ut=ot,st=["D","DD"],ct=["YY","YYYY"];function dt(t){return-1!==st.indexOf(t)}function lt(t){return-1!==ct.indexOf(t)}function ft(t,e,r){if("YYYY"===t)throw new RangeError("Use `yyyy` instead of `YYYY` (in `".concat(e,"`) for formatting years to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("YY"===t)throw new RangeError("Use `yy` instead of `YY` (in `".concat(e,"`) for formatting years to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("D"===t)throw new RangeError("Use `d` instead of `D` (in `".concat(e,"`) for formatting days of the month to the input `").concat(r,"`; see: https://git.io/fxCyr"));if("DD"===t)throw new RangeError("Use `dd` instead of `DD` (in `".concat(e,"`) for formatting days of the month to the input `").concat(r,"`; see: https://git.io/fxCyr"))}var ht=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,mt=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,wt=/^'([^]*?)'?$/,gt=/''/g,vt=/[a-zA-Z]/;function yt(t){return t.match(wt)[1].replace(gt,"'")}function bt(t,e){if(null==t)throw new TypeError("assign requires that input parameter not be null or undefined");for(var r in e=e||{})e.hasOwnProperty(r)&&(t[r]=e[r]);return t}function Tt(t,a,i){r(2,arguments);var o=i||{},u=o.locale,s=u&&u.options&&u.options.weekStartsOn,c=null==s?0:e(s),d=null==o.weekStartsOn?c:e(o.weekStartsOn);if(!(d>=0&&d<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var l=n(t),f=e(a),h=l.getUTCDay(),m=f%7,w=(m+7)%7,g=(w0,a=n?e:1-e;if(a<=50)r=t||100;else{var i=a+50;r=t+100*Math.floor(i/100)-(t>=i%100?100:0)}return n?r:1-r}var Jt=[31,28,31,30,31,30,31,31,30,31,30,31],Vt=[31,29,31,30,31,30,31,31,30,31,30,31];function te(t){return t%400==0||t%4==0&&t%100!=0}var ee={G:{priority:140,parse:function(t,e,r,n){switch(e){case"G":case"GG":case"GGG":return r.era(t,{width:"abbreviated"})||r.era(t,{width:"narrow"});case"GGGGG":return r.era(t,{width:"narrow"});default:return r.era(t,{width:"wide"})||r.era(t,{width:"abbreviated"})||r.era(t,{width:"narrow"})}},set:function(t,e,r,n){return e.era=r,t.setUTCFullYear(r,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["R","u","t","T"]},y:{priority:130,parse:function(t,e,r,n){var a=function(t){return{year:t,isTwoDigitYear:"yy"===e}};switch(e){case"y":return Zt(4,t,a);case"yo":return r.ordinalNumber(t,{unit:"year",valueCallback:a});default:return Zt(e.length,t,a)}},validate:function(t,e,r){return e.isTwoDigitYear||e.year>0},set:function(t,e,r,n){var a=t.getUTCFullYear();if(r.isTwoDigitYear){var i=_t(r.year,a);return t.setUTCFullYear(i,0,1),t.setUTCHours(0,0,0,0),t}var o="era"in e&&1!==e.era?1-r.year:r.year;return t.setUTCFullYear(o,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","u","w","I","i","e","c","t","T"]},Y:{priority:130,parse:function(t,e,r,n){var a=function(t){return{year:t,isTwoDigitYear:"YY"===e}};switch(e){case"Y":return Zt(4,t,a);case"Yo":return r.ordinalNumber(t,{unit:"year",valueCallback:a});default:return Zt(e.length,t,a)}},validate:function(t,e,r){return e.isTwoDigitYear||e.year>0},set:function(t,e,r,n){var a=X(t,n);if(r.isTwoDigitYear){var i=_t(r.year,a);return t.setUTCFullYear(i,0,n.firstWeekContainsDate),t.setUTCHours(0,0,0,0),G(t,n)}var o="era"in e&&1!==e.era?1-r.year:r.year;return t.setUTCFullYear(o,0,n.firstWeekContainsDate),t.setUTCHours(0,0,0,0),G(t,n)},incompatibleTokens:["y","R","u","Q","q","M","L","I","d","D","i","t","T"]},R:{priority:130,parse:function(t,e,r,n){return Kt("R"===e?4:e.length,t)},set:function(t,e,r,n){var a=new Date(0);return a.setUTCFullYear(r,0,4),a.setUTCHours(0,0,0,0),W(a)},incompatibleTokens:["G","y","Y","u","Q","q","M","L","w","d","D","e","c","t","T"]},u:{priority:130,parse:function(t,e,r,n){return Kt("u"===e?4:e.length,t)},set:function(t,e,r,n){return t.setUTCFullYear(r,0,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["G","y","Y","R","w","I","i","e","c","t","T"]},Q:{priority:120,parse:function(t,e,r,n){switch(e){case"Q":case"QQ":return Zt(e.length,t);case"Qo":return r.ordinalNumber(t,{unit:"quarter"});case"QQQ":return r.quarter(t,{width:"abbreviated",context:"formatting"})||r.quarter(t,{width:"narrow",context:"formatting"});case"QQQQQ":return r.quarter(t,{width:"narrow",context:"formatting"});default:return r.quarter(t,{width:"wide",context:"formatting"})||r.quarter(t,{width:"abbreviated",context:"formatting"})||r.quarter(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=1&&e<=4},set:function(t,e,r,n){return t.setUTCMonth(3*(r-1),1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","M","L","w","I","d","D","i","e","c","t","T"]},q:{priority:120,parse:function(t,e,r,n){switch(e){case"q":case"qq":return Zt(e.length,t);case"qo":return r.ordinalNumber(t,{unit:"quarter"});case"qqq":return r.quarter(t,{width:"abbreviated",context:"standalone"})||r.quarter(t,{width:"narrow",context:"standalone"});case"qqqqq":return r.quarter(t,{width:"narrow",context:"standalone"});default:return r.quarter(t,{width:"wide",context:"standalone"})||r.quarter(t,{width:"abbreviated",context:"standalone"})||r.quarter(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=1&&e<=4},set:function(t,e,r,n){return t.setUTCMonth(3*(r-1),1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","Q","M","L","w","I","d","D","i","e","c","t","T"]},M:{priority:110,parse:function(t,e,r,n){var a=function(t){return t-1};switch(e){case"M":return Bt(pt,t,a);case"MM":return Zt(2,t,a);case"Mo":return r.ordinalNumber(t,{unit:"month",valueCallback:a});case"MMM":return r.month(t,{width:"abbreviated",context:"formatting"})||r.month(t,{width:"narrow",context:"formatting"});case"MMMMM":return r.month(t,{width:"narrow",context:"formatting"});default:return r.month(t,{width:"wide",context:"formatting"})||r.month(t,{width:"abbreviated",context:"formatting"})||r.month(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.setUTCMonth(r,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","L","w","I","D","i","e","c","t","T"]},L:{priority:110,parse:function(t,e,r,n){var a=function(t){return t-1};switch(e){case"L":return Bt(pt,t,a);case"LL":return Zt(2,t,a);case"Lo":return r.ordinalNumber(t,{unit:"month",valueCallback:a});case"LLL":return r.month(t,{width:"abbreviated",context:"standalone"})||r.month(t,{width:"narrow",context:"standalone"});case"LLLLL":return r.month(t,{width:"narrow",context:"standalone"});default:return r.month(t,{width:"wide",context:"standalone"})||r.month(t,{width:"abbreviated",context:"standalone"})||r.month(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.setUTCMonth(r,1),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","M","w","I","D","i","e","c","t","T"]},w:{priority:100,parse:function(t,e,r,n){switch(e){case"w":return Bt(Dt,t);case"wo":return r.ordinalNumber(t,{unit:"week"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=53},set:function(t,a,i,o){return G(function(t,a,i){r(2,arguments);var o=n(t),u=e(a),s=z(o,i)-u;return o.setUTCDate(o.getUTCDate()-7*s),o}(t,i,o),o)},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","i","t","T"]},I:{priority:100,parse:function(t,e,r,n){switch(e){case"I":return Bt(Dt,t);case"Io":return r.ordinalNumber(t,{unit:"week"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=53},set:function(t,a,i,o){return W(function(t,a){r(2,arguments);var i=n(t),o=e(a),u=I(i)-o;return i.setUTCDate(i.getUTCDate()-7*u),i}(t,i,o),o)},incompatibleTokens:["y","Y","u","q","Q","M","L","w","d","D","e","c","t","T"]},d:{priority:90,subPriority:1,parse:function(t,e,r,n){switch(e){case"d":return Bt(Ct,t);case"do":return r.ordinalNumber(t,{unit:"date"});default:return Zt(e.length,t)}},validate:function(t,e,r){var n=te(t.getUTCFullYear()),a=t.getUTCMonth();return n?e>=1&&e<=Vt[a]:e>=1&&e<=Jt[a]},set:function(t,e,r,n){return t.setUTCDate(r),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","w","I","D","i","e","c","t","T"]},D:{priority:90,subPriority:1,parse:function(t,e,r,n){switch(e){case"D":case"DD":return Bt(Mt,t);case"Do":return r.ordinalNumber(t,{unit:"date"});default:return Zt(e.length,t)}},validate:function(t,e,r){return te(t.getUTCFullYear())?e>=1&&e<=366:e>=1&&e<=365},set:function(t,e,r,n){return t.setUTCMonth(0,r),t.setUTCHours(0,0,0,0),t},incompatibleTokens:["Y","R","q","Q","M","L","w","I","d","E","i","e","c","t","T"]},E:{priority:90,parse:function(t,e,r,n){switch(e){case"E":case"EE":case"EEE":return r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});case"EEEEE":return r.day(t,{width:"narrow",context:"formatting"});case"EEEEEE":return r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});default:return r.day(t,{width:"wide",context:"formatting"})||r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["D","i","e","c","t","T"]},e:{priority:90,parse:function(t,e,r,n){var a=function(t){var e=7*Math.floor((t-1)/7);return(t+n.weekStartsOn+6)%7+e};switch(e){case"e":case"ee":return Zt(e.length,t,a);case"eo":return r.ordinalNumber(t,{unit:"day",valueCallback:a});case"eee":return r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});case"eeeee":return r.day(t,{width:"narrow",context:"formatting"});case"eeeeee":return r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"});default:return r.day(t,{width:"wide",context:"formatting"})||r.day(t,{width:"abbreviated",context:"formatting"})||r.day(t,{width:"short",context:"formatting"})||r.day(t,{width:"narrow",context:"formatting"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","E","i","c","t","T"]},c:{priority:90,parse:function(t,e,r,n){var a=function(t){var e=7*Math.floor((t-1)/7);return(t+n.weekStartsOn+6)%7+e};switch(e){case"c":case"cc":return Zt(e.length,t,a);case"co":return r.ordinalNumber(t,{unit:"day",valueCallback:a});case"ccc":return r.day(t,{width:"abbreviated",context:"standalone"})||r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"});case"ccccc":return r.day(t,{width:"narrow",context:"standalone"});case"cccccc":return r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"});default:return r.day(t,{width:"wide",context:"standalone"})||r.day(t,{width:"abbreviated",context:"standalone"})||r.day(t,{width:"short",context:"standalone"})||r.day(t,{width:"narrow",context:"standalone"})}},validate:function(t,e,r){return e>=0&&e<=6},set:function(t,e,r,n){return(t=Tt(t,r,n)).setUTCHours(0,0,0,0),t},incompatibleTokens:["y","R","u","q","Q","M","L","I","d","D","E","i","e","t","T"]},i:{priority:90,parse:function(t,e,r,n){var a=function(t){return 0===t?7:t};switch(e){case"i":case"ii":return Zt(e.length,t);case"io":return r.ordinalNumber(t,{unit:"day"});case"iii":return r.day(t,{width:"abbreviated",context:"formatting",valueCallback:a})||r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a});case"iiiii":return r.day(t,{width:"narrow",context:"formatting",valueCallback:a});case"iiiiii":return r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a});default:return r.day(t,{width:"wide",context:"formatting",valueCallback:a})||r.day(t,{width:"abbreviated",context:"formatting",valueCallback:a})||r.day(t,{width:"short",context:"formatting",valueCallback:a})||r.day(t,{width:"narrow",context:"formatting",valueCallback:a})}},validate:function(t,e,r){return e>=1&&e<=7},set:function(t,a,i,o){return t=function(t,a){r(2,arguments);var i=e(a);i%7==0&&(i-=7);var o=1,u=n(t),s=u.getUTCDay(),c=((i%7+7)%7=1&&e<=12},set:function(t,e,r,n){var a=t.getUTCHours()>=12;return a&&r<12?t.setUTCHours(r+12,0,0,0):a||12!==r?t.setUTCHours(r,0,0,0):t.setUTCHours(0,0,0,0),t},incompatibleTokens:["H","K","k","t","T"]},H:{priority:70,parse:function(t,e,r,n){switch(e){case"H":return Bt(xt,t);case"Ho":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=23},set:function(t,e,r,n){return t.setUTCHours(r,0,0,0),t},incompatibleTokens:["a","b","h","K","k","t","T"]},K:{priority:70,parse:function(t,e,r,n){switch(e){case"K":return Bt(Ut,t);case"Ko":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=11},set:function(t,e,r,n){return t.getUTCHours()>=12&&r<12?t.setUTCHours(r+12,0,0,0):t.setUTCHours(r,0,0,0),t},incompatibleTokens:["a","b","h","H","k","t","T"]},k:{priority:70,parse:function(t,e,r,n){switch(e){case"k":return Bt(kt,t);case"ko":return r.ordinalNumber(t,{unit:"hour"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=1&&e<=24},set:function(t,e,r,n){var a=r<=24?r%24:r;return t.setUTCHours(a,0,0,0),t},incompatibleTokens:["a","b","h","H","K","t","T"]},m:{priority:60,parse:function(t,e,r,n){switch(e){case"m":return Bt(Nt,t);case"mo":return r.ordinalNumber(t,{unit:"minute"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=59},set:function(t,e,r,n){return t.setUTCMinutes(r,0,0),t},incompatibleTokens:["t","T"]},s:{priority:50,parse:function(t,e,r,n){switch(e){case"s":return Bt(St,t);case"so":return r.ordinalNumber(t,{unit:"second"});default:return Zt(e.length,t)}},validate:function(t,e,r){return e>=0&&e<=59},set:function(t,e,r,n){return t.setUTCSeconds(r,0),t},incompatibleTokens:["t","T"]},S:{priority:30,parse:function(t,e,r,n){return Zt(e.length,t,(function(t){return Math.floor(t*Math.pow(10,3-e.length))}))},set:function(t,e,r,n){return t.setUTCMilliseconds(r),t},incompatibleTokens:["t","T"]},X:{priority:10,parse:function(t,e,r,n){switch(e){case"X":return zt(Rt,t);case"XX":return zt(It,t);case"XXXX":return zt(Gt,t);case"XXXXX":return zt(jt,t);default:return zt(Xt,t)}},set:function(t,e,r,n){return e.timestampIsSet?t:new Date(t.getTime()-r)},incompatibleTokens:["t","T","x"]},x:{priority:10,parse:function(t,e,r,n){switch(e){case"x":return zt(Rt,t);case"xx":return zt(It,t);case"xxxx":return zt(Gt,t);case"xxxxx":return zt(jt,t);default:return zt(Xt,t)}},set:function(t,e,r,n){return e.timestampIsSet?t:new Date(t.getTime()-r)},incompatibleTokens:["t","T","X"]},t:{priority:40,parse:function(t,e,r,n){return At(t)},set:function(t,e,r,n){return[new Date(1e3*r),{timestampIsSet:!0}]},incompatibleTokens:"*"},T:{priority:20,parse:function(t,e,r,n){return At(t)},set:function(t,e,r,n){return[new Date(r),{timestampIsSet:!0}]},incompatibleTokens:"*"}},re=ee,ne=/[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g,ae=/P+p+|P+|p+|''|'(''|[^'])+('|$)|./g,ie=/^'([^]*?)'?$/,oe=/''/g,ue=/\S/,se=/[a-zA-Z]/;function ce(t,e){if(e.timestampIsSet)return t;var r=new Date(0);return r.setFullYear(t.getUTCFullYear(),t.getUTCMonth(),t.getUTCDate()),r.setHours(t.getUTCHours(),t.getUTCMinutes(),t.getUTCSeconds(),t.getUTCMilliseconds()),r}function de(t){return t.match(ie)[1].replace(oe,"'")}var le=36e5,fe={dateTimeDelimiter:/[T ]/,timeZoneDelimiter:/[Z ]/i,timezone:/([Z+-].*)$/},he=/^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/,me=/^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/,we=/^([+-])(\d{2})(?::?(\d{2}))?$/;function ge(t){var e,r={},n=t.split(fe.dateTimeDelimiter);if(n.length>2)return r;if(/:/.test(n[0])?(r.date=null,e=n[0]):(r.date=n[0],e=n[1],fe.timeZoneDelimiter.test(r.date)&&(r.date=t.split(fe.timeZoneDelimiter)[0],e=t.substr(r.date.length,t.length))),e){var a=fe.timezone.exec(e);a?(r.time=e.replace(a[1],""),r.timezone=a[1]):r.time=e}return r}function ve(t,e){var r=new RegExp("^(?:(\\d{4}|[+-]\\d{"+(4+e)+"})|(\\d{2}|[+-]\\d{"+(2+e)+"})$)"),n=t.match(r);if(!n)return{year:null};var a=n[1]&&parseInt(n[1]),i=n[2]&&parseInt(n[2]);return{year:null==i?a:100*i,restDateString:t.slice((n[1]||n[2]).length)}}function ye(t,e){if(null===e)return null;var r=t.match(he);if(!r)return null;var n=!!r[4],a=be(r[1]),i=be(r[2])-1,o=be(r[3]),u=be(r[4]),s=be(r[5])-1;if(n)return function(t,e,r){return e>=1&&e<=53&&r>=0&&r<=6}(0,u,s)?function(t,e,r){var n=new Date(0);n.setUTCFullYear(t,0,4);var a=n.getUTCDay()||7,i=7*(e-1)+r+1-a;return n.setUTCDate(n.getUTCDate()+i),n}(e,u,s):new Date(NaN);var c=new Date(0);return function(t,e,r){return e>=0&&e<=11&&r>=1&&r<=(Me[e]||(De(t)?29:28))}(e,i,o)&&function(t,e){return e>=1&&e<=(De(t)?366:365)}(e,a)?(c.setUTCFullYear(e,i,Math.max(a,o)),c):new Date(NaN)}function be(t){return t?parseInt(t):1}function Te(t){var e=t.match(me);if(!e)return null;var r=pe(e[1]),n=pe(e[2]),a=pe(e[3]);return function(t,e,r){if(24===t)return 0===e&&0===r;return r>=0&&r<60&&e>=0&&e<60&&t>=0&&t<25}(r,n,a)?r*le+6e4*n+1e3*a:NaN}function pe(t){return t&&parseFloat(t.replace(",","."))||0}function Ce(t){if("Z"===t)return 0;var e=t.match(we);if(!e)return 0;var r="+"===e[1]?-1:1,n=parseInt(e[2]),a=e[3]&&parseInt(e[3])||0;return function(t,e){return e>=0&&e<=59}(0,a)?r*(n*le+6e4*a):NaN}var Me=[31,null,31,30,31,30,31,31,30,31,30,31];function De(t){return t%400==0||t%4==0&&t%100}const xe={datetime:"MMM d, yyyy, h:mm:ss aaaa",millisecond:"h:mm:ss.SSS aaaa",second:"h:mm:ss aaaa",minute:"h:mm aaaa",hour:"ha",day:"MMM d",week:"PP",month:"MMM yyyy",quarter:"qqq - yyyy",year:"yyyy"};t._adapters._date.override({_id:"date-fns",formats:function(){return xe},parse:function(t,a){if(null==t)return null;const i=typeof t;return"number"===i||t instanceof Date?t=n(t):"string"===i&&(t="string"==typeof a?function(t,a,i,o){r(3,arguments);var u=String(t),s=String(a),d=o||{},l=d.locale||q;if(!l.match)throw new RangeError("locale must contain match property");var f=l.options&&l.options.firstWeekContainsDate,h=null==f?1:e(f),m=null==d.firstWeekContainsDate?h:e(d.firstWeekContainsDate);if(!(m>=1&&m<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var w=l.options&&l.options.weekStartsOn,g=null==w?0:e(w),v=null==d.weekStartsOn?g:e(d.weekStartsOn);if(!(v>=0&&v<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");if(""===s)return""===u?n(i):new Date(NaN);var y,b={firstWeekContainsDate:m,weekStartsOn:v,locale:l},T=[{priority:10,subPriority:-1,set:ce,index:0}],p=s.match(ae).map((function(t){var e=t[0];return"p"===e||"P"===e?(0,ut[e])(t,l.formatLong,b):t})).join("").match(ne),C=[];for(y=0;y0&&ue.test(u))return new Date(NaN);var P=T.map((function(t){return t.priority})).sort((function(t,e){return e-t})).filter((function(t,e,r){return r.indexOf(t)===e})).map((function(t){return T.filter((function(e){return e.priority===t})).sort((function(t,e){return e.subPriority-t.subPriority}))})).map((function(t){return t[0]})),E=n(i);if(isNaN(E))return new Date(NaN);var O=H(E,c(E)),F={};for(y=0;y=1&&f<=7))throw new RangeError("firstWeekContainsDate must be between 1 and 7 inclusively");var h=s.options&&s.options.weekStartsOn,w=null==h?0:e(h),g=null==u.weekStartsOn?w:e(u.weekStartsOn);if(!(g>=0&&g<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");if(!s.localize)throw new RangeError("locale must contain localize property");if(!s.formatLong)throw new RangeError("locale must contain formatLong property");var v=n(t);if(!m(v))throw new RangeError("Invalid time value");var y=c(v),b=H(v,y),T={firstWeekContainsDate:f,weekStartsOn:g,locale:s,_originalDate:v},p=o.match(mt).map((function(t){var e=t[0];return"p"===e||"P"===e?(0,ut[e])(t,s.formatLong,T):t})).join("").match(ht).map((function(e){if("''"===e)return"'";var r=e[0];if("'"===r)return yt(e);var n=nt[r];if(n)return!u.useAdditionalWeekYearTokens&<(e)&&ft(e,a,t),!u.useAdditionalDayOfYearTokens&&dt(e)&&ft(e,a,t),n(b,e,s.localize,T);if(r.match(vt))throw new RangeError("Format string contains an unescaped latin alphabet character `"+r+"`");return e})).join("");return p}(t,a,this.options)},add:function(t,n,s){switch(s){case"millisecond":return o(t,n);case"second":return function(t,n){r(2,arguments);var a=e(n);return o(t,1e3*a)}(t,n);case"minute":return function(t,n){r(2,arguments);var a=e(n);return o(t,6e4*a)}(t,n);case"hour":return function(t,n){r(2,arguments);var a=e(n);return o(t,a*u)}(t,n);case"day":return a(t,n);case"week":return function(t,n){r(2,arguments);var i=e(n),o=7*i;return a(t,o)}(t,n);case"month":return i(t,n);case"quarter":return function(t,n){r(2,arguments);var a=e(n),o=3*a;return i(t,o)}(t,n);case"year":return function(t,n){r(2,arguments);var a=e(n);return i(t,12*a)}(t,n);default:return t}},diff:function(t,e,a){switch(a){case"millisecond":return b(t,e);case"second":return function(t,e){r(2,arguments);var n=b(t,e)/1e3;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"minute":return function(t,e){r(2,arguments);var n=b(t,e)/6e4;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"hour":return function(t,e){r(2,arguments);var n=b(t,e)/T;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"day":return y(t,e);case"week":return function(t,e){r(2,arguments);var n=y(t,e)/7;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"month":return D(t,e);case"quarter":return function(t,e){r(2,arguments);var n=D(t,e)/3;return n>0?Math.floor(n):Math.ceil(n)}(t,e);case"year":return function(t,e){r(2,arguments);var a=n(t),i=n(e),o=h(a,i),u=Math.abs(g(a,i));a.setFullYear("1584"),i.setFullYear("1584");var s=h(a,i)===-o,c=o*(u-s);return 0===c?0:c}(t,e);default:return 0}},startOf:function(t,e,a){switch(e){case"second":return function(t){r(1,arguments);var e=n(t);return e.setMilliseconds(0),e}(t);case"minute":return function(t){r(1,arguments);var e=n(t);return e.setSeconds(0,0),e}(t);case"hour":return function(t){r(1,arguments);var e=n(t);return e.setMinutes(0,0,0),e}(t);case"day":return d(t);case"week":return s(t);case"isoWeek":return s(t,{weekStartsOn:+a});case"month":return function(t){r(1,arguments);var e=n(t);return e.setDate(1),e.setHours(0,0,0,0),e}(t);case"quarter":return function(t){r(1,arguments);var e=n(t),a=e.getMonth(),i=a-a%3;return e.setMonth(i,1),e.setHours(0,0,0,0),e}(t);case"year":return function(t){r(1,arguments);var e=n(t),a=new Date(0);return a.setFullYear(e.getFullYear(),0,1),a.setHours(0,0,0,0),a}(t);default:return t}},endOf:function(t,a){switch(a){case"second":return function(t){r(1,arguments);var e=n(t);return e.setMilliseconds(999),e}(t);case"minute":return function(t){r(1,arguments);var e=n(t);return e.setSeconds(59,999),e}(t);case"hour":return function(t){r(1,arguments);var e=n(t);return e.setMinutes(59,59,999),e}(t);case"day":return p(t);case"week":return function(t,a){r(1,arguments);var i=a||{},o=i.locale,u=o&&o.options&&o.options.weekStartsOn,s=null==u?0:e(u),c=null==i.weekStartsOn?s:e(i.weekStartsOn);if(!(c>=0&&c<=6))throw new RangeError("weekStartsOn must be between 0 and 6 inclusively");var d=n(t),l=d.getDay(),f=6+(l=2.8.0", + "date-fns": ">=2.0.0" + } +} diff --git a/static/js/chartjs-plugin-annotation/LICENSE.md b/static/js/chartjs-plugin-annotation/LICENSE.md new file mode 100644 index 0000000..ad5ebb7 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2016-2021 chartjs-plugin-annotation Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/static/js/chartjs-plugin-annotation/README.md b/static/js/chartjs-plugin-annotation/README.md new file mode 100644 index 0000000..b93230b --- /dev/null +++ b/static/js/chartjs-plugin-annotation/README.md @@ -0,0 +1,41 @@ +# chartjs-plugin-annotation.js + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/chartjs/chartjs-plugin-annotation/ci.yml?branch=master)](https://github.com/chartjs/chartjs-plugin-annotation/actions/workflows/ci.yml) +[![Coverage Status](https://coveralls.io/repos/github/chartjs/chartjs-plugin-annotation/badge.svg?branch=master)](https://coveralls.io/github/chartjs/chartjs-plugin-annotation?branch=master) +[![release](https://img.shields.io/github/v/release/chartjs/chartjs-plugin-annotation?include_prereleases)](https://github.com/chartjs/chartjs-plugin-annotation/releases) +[![npm (latest)](https://img.shields.io/npm/v/chartjs-plugin-annotation/latest)](https://www.npmjs.com/package/chartjs-plugin-annotation/v/latest) +[![documentation](https://img.shields.io/static/v1?message=Documentation&color=informational)](https://www.chartjs.org/chartjs-plugin-annotation/index) +Awesome + +An annotation plugin for Chart.js >= 4.0.0 + +--- +> This plugin needs to be registered. It does not function as inline plugin. +--- + +For Chart.js 3.7.0 to 3.9.1 support, use [version 2.2.1 of this plugin](https://github.com/chartjs/chartjs-plugin-annotation/releases/tag/v2.2.1) + +For Chart.js 3.0.0 to 3.6.2 support, use [version 1.4.0 of this plugin](https://github.com/chartjs/chartjs-plugin-annotation/releases/tag/v1.4.0) + +For Chart.js 2.4.0 to 2.9.x support, use [version 0.5.7 of this plugin](https://github.com/chartjs/chartjs-plugin-annotation/releases/tag/v0.5.7) + +This plugin draws lines, boxes, points, labels, polygons and ellipses on the chart area. + +Annotations work with line, bar, scatter and bubble charts that use linear, logarithmic, time, or category scales. +Furthermore you can use a doughnut label annotation which can be used to add contents (text, image, canvas) in the middle area of the doughnut charts. + +![Example Screenshot](docs/guide/banner.png) + +[View this example](https://www.chartjs.org/chartjs-plugin-annotation/latest/samples/intro.html) + +## Documentation + +You can find documentation for chartjs-plugin-annotation at [www.chartjs.org/chartjs-plugin-annotation](https://www.chartjs.org/chartjs-plugin-annotation/index). + +## Contributing + +Before submitting an issue or a pull request to the project, please take a moment to look over the [contributing guidelines](CONTRIBUTING.md) first. + +## License + +Chart.Annotation.js is available under the [MIT license](LICENSE.md). diff --git a/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs new file mode 100644 index 0000000..7842f73 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.cjs @@ -0,0 +1,3040 @@ +/*! +* chartjs-plugin-annotation v3.1.0 +* https://www.chartjs.org/chartjs-plugin-annotation/index + * (c) 2024 chartjs-plugin-annotation Contributors + * Released under the MIT License + */ +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js'), require('chart.js/helpers')) : +typeof define === 'function' && define.amd ? define(['chart.js', 'chart.js/helpers'], factory) : +(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global["chartjs-plugin-annotation"] = factory(global.Chart, global.Chart.helpers)); +})(this, (function (chart_js, helpers) { 'use strict'; + +/** + * @typedef { import("chart.js").ChartEvent } ChartEvent + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const interaction = { + modes: { + /** + * Point mode returns all elements that hit test based on the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @return {AnnotationElement[]} - elements that are found + */ + point(visibleElements, event) { + return filterElements(visibleElements, event, {intersect: true}); + }, + + /** + * Nearest mode returns the element closest to the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found (only 1 element) + */ + nearest(visibleElements, event, options) { + return getNearestItem(visibleElements, event, options); + }, + /** + * x mode returns the elements that hit-test at the current x coordinate + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ + x(visibleElements, event, options) { + return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'x'}); + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ + y(visibleElements, event, options) { + return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'y'}); + } + } +}; + +/** + * Returns all elements that hit test based on the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ +function getElements(visibleElements, event, options) { + const mode = interaction.modes[options.mode] || interaction.modes.nearest; + return mode(visibleElements, event, options); +} + +function inRangeByAxis(element, event, axis) { + if (axis !== 'x' && axis !== 'y') { + return element.inRange(event.x, event.y, 'x', true) || element.inRange(event.x, event.y, 'y', true); + } + return element.inRange(event.x, event.y, axis, true); +} + +function getPointByAxis(event, center, axis) { + if (axis === 'x') { + return {x: event.x, y: center.y}; + } else if (axis === 'y') { + return {x: center.x, y: event.y}; + } + return center; +} + +function filterElements(visibleElements, event, options) { + return visibleElements.filter((element) => options.intersect ? element.inRange(event.x, event.y) : inRangeByAxis(element, event, options.axis)); +} + +function getNearestItem(visibleElements, event, options) { + let minDistance = Number.POSITIVE_INFINITY; + + return filterElements(visibleElements, event, options) + .reduce((nearestItems, element) => { + const center = element.getCenterPoint(); + const evenPoint = getPointByAxis(event, center, options.axis); + const distance = helpers.distanceBetweenPoints(event, evenPoint); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + + return nearestItems; + }, []) + .sort((a, b) => a._index - b._index) + .slice(0, 1); // return only the top item; +} + +/** + * @typedef {import('chart.js').Point} Point + */ + +/** + * Rotate a `point` relative to `center` point by `angle` + * @param {Point} point - the point to rotate + * @param {Point} center - center point for rotation + * @param {number} angle - angle for rotation, in radians + * @returns {Point} rotated point + */ +function rotated(point, center, angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const cx = center.x; + const cy = center.y; + + return { + x: cx + cos * (point.x - cx) - sin * (point.y - cy), + y: cy + sin * (point.x - cx) + cos * (point.y - cy) + }; +} + +const isOlderPart = (act, req) => req > act || (act.length > req.length && act.slice(0, req.length) === req); + +/** + * @typedef { import('chart.js').Point } Point + * @typedef { import('chart.js').InteractionAxis } InteractionAxis + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const EPSILON = 0.001; +const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); + +/** + * @param {{value: number, start: number, end: number}} limit + * @param {number} hitSize + * @returns {boolean} + */ +const inLimit = (limit, hitSize) => limit.value >= limit.start - hitSize && limit.value <= limit.end + hitSize; + +/** + * @param {Object} obj + * @param {number} from + * @param {number} to + * @returns {Object} + */ +function clampAll(obj, from, to) { + for (const key of Object.keys(obj)) { + obj[key] = clamp(obj[key], from, to); + } + return obj; +} + +/** + * @param {Point} point + * @param {Point} center + * @param {number} radius + * @param {number} hitSize + * @returns {boolean} + */ +function inPointRange(point, center, radius, hitSize) { + if (!point || !center || radius <= 0) { + return false; + } + return (Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)) <= Math.pow(radius + hitSize, 2); +} + +/** + * @param {Point} point + * @param {{x: number, y: number, x2: number, y2: number}} rect + * @param {InteractionAxis} axis + * @param {{borderWidth: number, hitTolerance: number}} hitsize + * @returns {boolean} + */ +function inBoxRange(point, {x, y, x2, y2}, axis, {borderWidth, hitTolerance}) { + const hitSize = (borderWidth + hitTolerance) / 2; + const inRangeX = point.x >= x - hitSize - EPSILON && point.x <= x2 + hitSize + EPSILON; + const inRangeY = point.y >= y - hitSize - EPSILON && point.y <= y2 + hitSize + EPSILON; + if (axis === 'x') { + return inRangeX; + } else if (axis === 'y') { + return inRangeY; + } + return inRangeX && inRangeY; +} + +/** + * @param {Point} point + * @param {rect: {x: number, y: number, x2: number, y2: number}, center: {x: number, y: number}} element + * @param {InteractionAxis} axis + * @param {{rotation: number, borderWidth: number, hitTolerance: number}} + * @returns {boolean} + */ +function inLabelRange(point, {rect, center}, axis, {rotation, borderWidth, hitTolerance}) { + const rotPoint = rotated(point, center, helpers.toRadians(-rotation)); + return inBoxRange(rotPoint, rect, axis, {borderWidth, hitTolerance}); +} + +/** + * @param {AnnotationElement} element + * @param {boolean} useFinalPosition + * @returns {Point} + */ +function getElementCenterPoint(element, useFinalPosition) { + const {centerX, centerY} = element.getProps(['centerX', 'centerY'], useFinalPosition); + return {x: centerX, y: centerY}; +} + +/** + * @param {string} pkg + * @param {string} min + * @param {string} ver + * @param {boolean} [strict=true] + * @returns {boolean} + */ +function requireVersion(pkg, min, ver, strict = true) { + const parts = ver.split('.'); + let i = 0; + for (const req of min.split('.')) { + const act = parts[i++]; + if (parseInt(req, 10) < parseInt(act, 10)) { + break; + } + if (isOlderPart(act, req)) { + if (strict) { + throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`); + } else { + return false; + } + } + } + return true; +} + +const isPercentString = (s) => typeof s === 'string' && s.endsWith('%'); +const toPercent = (s) => parseFloat(s) / 100; +const toPositivePercent = (s) => clamp(toPercent(s), 0, 1); + +const boxAppering = (x, y) => ({x, y, x2: x, y2: y, width: 0, height: 0}); +const defaultInitAnimation = { + box: (properties) => boxAppering(properties.centerX, properties.centerY), + doughnutLabel: (properties) => boxAppering(properties.centerX, properties.centerY), + ellipse: (properties) => ({centerX: properties.centerX, centerY: properties.centerX, radius: 0, width: 0, height: 0}), + label: (properties) => boxAppering(properties.centerX, properties.centerY), + line: (properties) => boxAppering(properties.x, properties.y), + point: (properties) => ({centerX: properties.centerX, centerY: properties.centerY, radius: 0, width: 0, height: 0}), + polygon: (properties) => boxAppering(properties.centerX, properties.centerY) +}; + +/** + * @typedef { import('chart.js').FontSpec } FontSpec + * @typedef { import('chart.js').Point } Point + * @typedef { import('chart.js').Padding } Padding + * @typedef { import('../../types/element').AnnotationBoxModel } AnnotationBoxModel + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + * @typedef { import('../../types/options').AnnotationPointCoordinates } AnnotationPointCoordinates + * @typedef { import('../../types/label').CoreLabelOptions } CoreLabelOptions + * @typedef { import('../../types/label').LabelPositionObject } LabelPositionObject + */ + +/** + * @param {number} size + * @param {number|string} position + * @returns {number} + */ +function getRelativePosition(size, position) { + if (position === 'start') { + return 0; + } + if (position === 'end') { + return size; + } + if (isPercentString(position)) { + return toPositivePercent(position) * size; + } + return size / 2; +} + +/** + * @param {number} size + * @param {number|string} value + * @param {boolean} [positivePercent=true] + * @returns {number} + */ +function getSize(size, value, positivePercent = true) { + if (typeof value === 'number') { + return value; + } else if (isPercentString(value)) { + return (positivePercent ? toPositivePercent(value) : toPercent(value)) * size; + } + return size; +} + +/** + * @param {{x: number, width: number}} size + * @param {CoreLabelOptions} options + * @returns {number} + */ +function calculateTextAlignment(size, options) { + const {x, width} = size; + const textAlign = options.textAlign; + if (textAlign === 'center') { + return x + width / 2; + } else if (textAlign === 'end' || textAlign === 'right') { + return x + width; + } + return x; +} + +/** + * @param {Point} point + * @param {{height: number, width: number}} labelSize + * @param {{borderWidth: number, position: {LabelPositionObject|string}, xAdjust: number, yAdjust: number}} options + * @param {Padding|undefined} padding + * @returns {{x: number, y: number, x2: number, y2: number, height: number, width: number, centerX: number, centerY: number}} + */ +function measureLabelRectangle(point, labelSize, {borderWidth, position, xAdjust, yAdjust}, padding) { + const hasPadding = helpers.isObject(padding); + const width = labelSize.width + (hasPadding ? padding.width : 0) + borderWidth; + const height = labelSize.height + (hasPadding ? padding.height : 0) + borderWidth; + const positionObj = toPosition(position); + const x = calculateLabelPosition$1(point.x, width, xAdjust, positionObj.x); + const y = calculateLabelPosition$1(point.y, height, yAdjust, positionObj.y); + + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2 + }; +} + +/** + * @param {LabelPositionObject|string} value + * @param {string|number} defaultValue + * @returns {LabelPositionObject} + */ +function toPosition(value, defaultValue = 'center') { + if (helpers.isObject(value)) { + return { + x: helpers.valueOrDefault(value.x, defaultValue), + y: helpers.valueOrDefault(value.y, defaultValue), + }; + } + value = helpers.valueOrDefault(value, defaultValue); + return { + x: value, + y: value + }; +} + +/** + * @param {CoreLabelOptions} options + * @param {number} fitRatio + * @returns {boolean} + */ +const shouldFit = (options, fitRatio) => options && options.autoFit && fitRatio < 1; + +/** + * @param {CoreLabelOptions} options + * @param {number} fitRatio + * @returns {FontSpec[]} + */ +function toFonts(options, fitRatio) { + const optFont = options.font; + const fonts = helpers.isArray(optFont) ? optFont : [optFont]; + if (shouldFit(options, fitRatio)) { + return fonts.map(function(f) { + const font = helpers.toFont(f); + font.size = Math.floor(f.size * fitRatio); + font.lineHeight = f.lineHeight; + return helpers.toFont(font); + }); + } + return fonts.map(f => helpers.toFont(f)); +} + +/** + * @param {AnnotationPointCoordinates} options + * @returns {boolean} + */ +function isBoundToPoint(options) { + return options && (helpers.defined(options.xValue) || helpers.defined(options.yValue)); +} + +function calculateLabelPosition$1(start, size, adjust = 0, position) { + return start - getRelativePosition(size, position) + adjust; +} + +/** + * @param {Chart} chart + * @param {AnnotationBoxModel} properties + * @param {CoreAnnotationOptions} options + * @returns {AnnotationElement} + */ +function initAnimationProperties(chart, properties, options) { + const initAnim = options.init; + if (!initAnim) { + return; + } else if (initAnim === true) { + return applyDefault(properties, options); + } + return execCallback(chart, properties, options); +} + +/** + * @param {Object} options + * @param {Array} hooks + * @param {Object} hooksContainer + * @returns {boolean} + */ +function loadHooks(options, hooks, hooksContainer) { + let activated = false; + hooks.forEach(hook => { + if (helpers.isFunction(options[hook])) { + activated = true; + hooksContainer[hook] = options[hook]; + } else if (helpers.defined(hooksContainer[hook])) { + delete hooksContainer[hook]; + } + }); + return activated; +} + +function applyDefault(properties, options) { + const type = options.type || 'line'; + return defaultInitAnimation[type](properties); +} + +function execCallback(chart, properties, options) { + const result = helpers.callback(options.init, [{chart, properties, options}]); + if (result === true) { + return applyDefault(properties, options); + } else if (helpers.isObject(result)) { + return result; + } +} + +const widthCache = new Map(); +const notRadius = (radius) => isNaN(radius) || radius <= 0; +const fontsKey = (fonts) => fonts.reduce(function(prev, item) { + prev += item.string; + return prev; +}, ''); + +/** + * @typedef { import('chart.js').Point } Point + * @typedef { import('../../types/label').CoreLabelOptions } CoreLabelOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + */ + +/** + * Determine if content is an image or a canvas. + * @param {*} content + * @returns boolean|undefined + * @todo move this function to chart.js helpers + */ +function isImageOrCanvas(content) { + if (content && typeof content === 'object') { + const type = content.toString(); + return (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]'); + } +} + +/** + * Set the translation on the canvas if the rotation must be applied. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Point} point - the point of translation + * @param {number} rotation - rotation (in degrees) to apply + */ +function translate(ctx, {x, y}, rotation) { + if (rotation) { + ctx.translate(x, y); + ctx.rotate(helpers.toRadians(rotation)); + ctx.translate(-x, -y); + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {Object} options + * @returns {boolean|undefined} + */ +function setBorderStyle(ctx, options) { + if (options && options.borderWidth) { + ctx.lineCap = options.borderCapStyle || 'butt'; + ctx.setLineDash(options.borderDash); + ctx.lineDashOffset = options.borderDashOffset; + ctx.lineJoin = options.borderJoinStyle || 'miter'; + ctx.lineWidth = options.borderWidth; + ctx.strokeStyle = options.borderColor; + return true; + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {Object} options + */ +function setShadowStyle(ctx, options) { + ctx.shadowColor = options.backgroundShadowColor; + ctx.shadowBlur = options.shadowBlur; + ctx.shadowOffsetX = options.shadowOffsetX; + ctx.shadowOffsetY = options.shadowOffsetY; +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {CoreLabelOptions} options + * @returns {{width: number, height: number}} + */ +function measureLabelSize(ctx, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + const size = { + width: getSize(content.width, options.width), + height: getSize(content.height, options.height) + }; + return size; + } + const fonts = toFonts(options); + const strokeWidth = options.textStrokeWidth; + const lines = helpers.isArray(content) ? content : [content]; + const mapKey = lines.join() + fontsKey(fonts) + strokeWidth + (ctx._measureText ? '-spriting' : ''); + if (!widthCache.has(mapKey)) { + widthCache.set(mapKey, calculateLabelSize(ctx, lines, fonts, strokeWidth)); + } + return widthCache.get(mapKey); +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{x: number, y: number, width: number, height: number}} rect + * @param {Object} options + */ +function drawBox(ctx, rect, options) { + const {x, y, width, height} = rect; + ctx.save(); + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + helpers.addRoundedRectPath(ctx, { + x, y, w: width, h: height, + radius: clampAll(helpers.toTRBLCorners(options.borderRadius), 0, Math.min(width, height) / 2) + }); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{x: number, y: number, width: number, height: number}} rect + * @param {CoreLabelOptions} options + * @param {number} fitRatio + */ +function drawLabel(ctx, rect, options, fitRatio) { + const content = options.content; + if (isImageOrCanvas(content)) { + ctx.save(); + ctx.globalAlpha = getOpacity(options.opacity, content.style.opacity); + ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height); + ctx.restore(); + return; + } + const labels = helpers.isArray(content) ? content : [content]; + const fonts = toFonts(options, fitRatio); + const optColor = options.color; + const colors = helpers.isArray(optColor) ? optColor : [optColor]; + const x = calculateTextAlignment(rect, options); + const y = rect.y + options.textStrokeWidth / 2; + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = options.textAlign; + if (setTextStrokeStyle(ctx, options)) { + applyLabelDecoration(ctx, {x, y}, labels, fonts); + } + applyLabelContent(ctx, {x, y}, labels, {fonts, colors}); + ctx.restore(); +} + +function setTextStrokeStyle(ctx, options) { + if (options.textStrokeWidth > 0) { + // https://stackoverflow.com/questions/13627111/drawing-text-with-an-outer-stroke-with-html5s-canvas + ctx.lineJoin = 'round'; + ctx.miterLimit = 2; + ctx.lineWidth = options.textStrokeWidth; + ctx.strokeStyle = options.textStrokeColor; + return true; + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{radius: number, options: PointAnnotationOptions}} element + * @param {number} x + * @param {number} y + */ +function drawPoint(ctx, element, x, y) { + const {radius, options} = element; + const style = options.pointStyle; + const rotation = options.rotation; + let rad = (rotation || 0) * helpers.RAD_PER_DEG; + + if (isImageOrCanvas(style)) { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + if (notRadius(radius)) { + return; + } + drawPointStyle(ctx, {x, y, radius, rotation, style, rad}); +} + +function drawPointStyle(ctx, {x, y, radius, rotation, style, rad}) { + let xOffset, yOffset, size, cornerRadius; + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, helpers.TAU); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += helpers.TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += helpers.TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + helpers.QUARTER_PI) * size; + yOffset = Math.sin(rad + helpers.QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - helpers.PI, rad - helpers.HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - helpers.HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + helpers.HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + helpers.HALF_PI, rad + helpers.PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += helpers.QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += helpers.QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += helpers.QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); +} + +function calculateLabelSize(ctx, lines, fonts, strokeWidth) { + ctx.save(); + const count = lines.length; + let width = 0; + let height = strokeWidth; + for (let i = 0; i < count; i++) { + const font = fonts[Math.min(i, fonts.length - 1)]; + ctx.font = font.string; + const text = lines[i]; + width = Math.max(width, ctx.measureText(text).width + strokeWidth); + height += font.lineHeight; + } + ctx.restore(); + return {width, height}; +} + +function applyLabelDecoration(ctx, {x, y}, labels, fonts) { + ctx.beginPath(); + let lhs = 0; + labels.forEach(function(l, i) { + const f = fonts[Math.min(i, fonts.length - 1)]; + const lh = f.lineHeight; + ctx.font = f.string; + ctx.strokeText(l, x, y + lh / 2 + lhs); + lhs += lh; + }); + ctx.stroke(); +} + +function applyLabelContent(ctx, {x, y}, labels, {fonts, colors}) { + let lhs = 0; + labels.forEach(function(l, i) { + const c = colors[Math.min(i, colors.length - 1)]; + const f = fonts[Math.min(i, fonts.length - 1)]; + const lh = f.lineHeight; + ctx.beginPath(); + ctx.font = f.string; + ctx.fillStyle = c; + ctx.fillText(l, x, y + lh / 2 + lhs); + lhs += lh; + ctx.fill(); + }); +} + +function getOpacity(value, elementValue) { + const opacity = helpers.isNumber(value) ? value : elementValue; + return helpers.isNumber(opacity) ? clamp(opacity, 0, 1) : 1; +} + +const positions = ['left', 'bottom', 'top', 'right']; + +/** + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +/** + * Drawa the callout component for labels. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {AnnotationElement} element - the label element + */ +function drawCallout(ctx, element) { + const {pointX, pointY, options} = element; + const callout = options.callout; + const calloutPosition = callout && callout.display && resolveCalloutPosition(element, callout); + if (!calloutPosition || isPointInRange(element, callout, calloutPosition)) { + return; + } + + ctx.save(); + ctx.beginPath(); + const stroke = setBorderStyle(ctx, callout); + if (!stroke) { + return ctx.restore(); + } + const {separatorStart, separatorEnd} = getCalloutSeparatorCoord(element, calloutPosition); + const {sideStart, sideEnd} = getCalloutSideCoord(element, calloutPosition, separatorStart); + if (callout.margin > 0 || options.borderWidth === 0) { + ctx.moveTo(separatorStart.x, separatorStart.y); + ctx.lineTo(separatorEnd.x, separatorEnd.y); + } + ctx.moveTo(sideStart.x, sideStart.y); + ctx.lineTo(sideEnd.x, sideEnd.y); + const rotatedPoint = rotated({x: pointX, y: pointY}, element.getCenterPoint(), helpers.toRadians(-element.rotation)); + ctx.lineTo(rotatedPoint.x, rotatedPoint.y); + ctx.stroke(); + ctx.restore(); +} + +function getCalloutSeparatorCoord(element, position) { + const {x, y, x2, y2} = element; + const adjust = getCalloutSeparatorAdjust(element, position); + let separatorStart, separatorEnd; + if (position === 'left' || position === 'right') { + separatorStart = {x: x + adjust, y}; + separatorEnd = {x: separatorStart.x, y: y2}; + } else { + // position 'top' or 'bottom' + separatorStart = {x, y: y + adjust}; + separatorEnd = {x: x2, y: separatorStart.y}; + } + return {separatorStart, separatorEnd}; +} + +function getCalloutSeparatorAdjust(element, position) { + const {width, height, options} = element; + const adjust = options.callout.margin + options.borderWidth / 2; + if (position === 'right') { + return width + adjust; + } else if (position === 'bottom') { + return height + adjust; + } + return -adjust; +} + +function getCalloutSideCoord(element, position, separatorStart) { + const {y, width, height, options} = element; + const start = options.callout.start; + const side = getCalloutSideAdjust(position, options.callout); + let sideStart, sideEnd; + if (position === 'left' || position === 'right') { + sideStart = {x: separatorStart.x, y: y + getSize(height, start)}; + sideEnd = {x: sideStart.x + side, y: sideStart.y}; + } else { + // position 'top' or 'bottom' + sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y}; + sideEnd = {x: sideStart.x, y: sideStart.y + side}; + } + return {sideStart, sideEnd}; +} + +function getCalloutSideAdjust(position, options) { + const side = options.side; + if (position === 'left' || position === 'top') { + return -side; + } + return side; +} + +function resolveCalloutPosition(element, options) { + const position = options.position; + if (positions.includes(position)) { + return position; + } + return resolveCalloutAutoPosition(element, options); +} + +function resolveCalloutAutoPosition(element, options) { + const {x, y, x2, y2, width, height, pointX, pointY, centerX, centerY, rotation} = element; + const center = {x: centerX, y: centerY}; + const start = options.start; + const xAdjust = getSize(width, start); + const yAdjust = getSize(height, start); + const xPoints = [x, x + xAdjust, x + xAdjust, x2]; + const yPoints = [y + yAdjust, y2, y, y2]; + const result = []; + for (let index = 0; index < 4; index++) { + const rotatedPoint = rotated({x: xPoints[index], y: yPoints[index]}, center, helpers.toRadians(rotation)); + result.push({ + position: positions[index], + distance: helpers.distanceBetweenPoints(rotatedPoint, {x: pointX, y: pointY}) + }); + } + return result.sort((a, b) => a.distance - b.distance)[0].position; +} + +function isPointInRange(element, callout, position) { + const {pointX, pointY} = element; + const margin = callout.margin; + let x = pointX; + let y = pointY; + if (position === 'left') { + x += margin; + } else if (position === 'right') { + x -= margin; + } else if (position === 'top') { + y += margin; + } else if (position === 'bottom') { + y -= margin; + } + return element.inRange(x, y); +} + +const limitedLineScale = { + xScaleID: {min: 'xMin', max: 'xMax', start: 'left', end: 'right', startProp: 'x', endProp: 'x2'}, + yScaleID: {min: 'yMin', max: 'yMax', start: 'bottom', end: 'top', startProp: 'y', endProp: 'y2'} +}; + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import("chart.js").Point } Point + * @typedef { import('../../types/element').AnnotationBoxModel } AnnotationBoxModel + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + * @typedef { import('../../types/options').LineAnnotationOptions } LineAnnotationOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + * @typedef { import('../../types/options').PolygonAnnotationOptions } PolygonAnnotationOptions + */ + +/** + * @param {Scale} scale + * @param {number|string} value + * @param {number} fallback + * @returns {number} + */ +function scaleValue(scale, value, fallback) { + value = typeof value === 'number' ? value : scale.parse(value); + return helpers.isFinite(value) ? scale.getPixelForValue(value) : fallback; +} + +/** + * Search the scale defined in chartjs by the axis related to the annotation options key. + * @param {{ [key: string]: Scale }} scales + * @param {CoreAnnotationOptions} options + * @param {string} key + * @returns {string} + */ +function retrieveScaleID(scales, options, key) { + const scaleID = options[key]; + if (scaleID || key === 'scaleID') { + return scaleID; + } + const axis = key.charAt(0); + const axes = Object.values(scales).filter((scale) => scale.axis && scale.axis === axis); + if (axes.length) { + return axes[0].id; + } + return axis; +} + +/** + * @param {Scale} scale + * @param {{min: number, max: number, start: number, end: number}} options + * @returns {{start: number, end: number}|undefined} + */ +function getDimensionByScale(scale, options) { + if (scale) { + const reverse = scale.options.reverse; + const start = scaleValue(scale, options.min, reverse ? options.end : options.start); + const end = scaleValue(scale, options.max, reverse ? options.start : options.end); + return { + start, + end + }; + } +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {Point} + */ +function getChartPoint(chart, options) { + const {chartArea, scales} = chart; + const xScale = scales[retrieveScaleID(scales, options, 'xScaleID')]; + const yScale = scales[retrieveScaleID(scales, options, 'yScaleID')]; + let x = chartArea.width / 2; + let y = chartArea.height / 2; + + if (xScale) { + x = scaleValue(xScale, options.xValue, xScale.left + xScale.width / 2); + } + + if (yScale) { + y = scaleValue(yScale, options.yValue, yScale.top + yScale.height / 2); + } + return {x, y}; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolveBoxProperties(chart, options) { + const scales = chart.scales; + const xScale = scales[retrieveScaleID(scales, options, 'xScaleID')]; + const yScale = scales[retrieveScaleID(scales, options, 'yScaleID')]; + + if (!xScale && !yScale) { + return {}; + } + + let {left: x, right: x2} = xScale || chart.chartArea; + let {top: y, bottom: y2} = yScale || chart.chartArea; + const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2}); + x = xDim.start; + x2 = xDim.end; + const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y2, end: y}); + y = yDim.start; + y2 = yDim.end; + + return { + x, + y, + x2, + y2, + width: x2 - x, + height: y2 - y, + centerX: x + (x2 - x) / 2, + centerY: y + (y2 - y) / 2 + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions|PolygonAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolvePointProperties(chart, options) { + if (!isBoundToPoint(options)) { + const box = resolveBoxProperties(chart, options); + let radius = options.radius; + if (!radius || isNaN(radius)) { + radius = Math.min(box.width, box.height) / 2; + options.radius = radius; + } + const size = radius * 2; + const adjustCenterX = box.centerX + options.xAdjust; + const adjustCenterY = box.centerY + options.yAdjust; + return { + x: adjustCenterX - radius, + y: adjustCenterY - radius, + x2: adjustCenterX + radius, + y2: adjustCenterY + radius, + centerX: adjustCenterX, + centerY: adjustCenterY, + width: size, + height: size, + radius + }; + } + return getChartCircle(chart, options); +} +/** + * @param {Chart} chart + * @param {LineAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolveLineProperties(chart, options) { + const {scales, chartArea} = chart; + const scale = scales[options.scaleID]; + const area = {x: chartArea.left, y: chartArea.top, x2: chartArea.right, y2: chartArea.bottom}; + + if (scale) { + resolveFullLineProperties(scale, area, options); + } else { + resolveLimitedLineProperties(scales, area, options); + } + return area; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @param {boolean} [centerBased=false] + * @returns {AnnotationBoxModel} + */ +function resolveBoxAndLabelProperties(chart, options) { + const properties = resolveBoxProperties(chart, options); + properties.initProperties = initAnimationProperties(chart, properties, options); + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: resolveLabelElementProperties$1(chart, properties, options), + initProperties: properties.initProperties + }]; + return properties; +} + +function getChartCircle(chart, options) { + const point = getChartPoint(chart, options); + const size = options.radius * 2; + return { + x: point.x - options.radius + options.xAdjust, + y: point.y - options.radius + options.yAdjust, + x2: point.x + options.radius + options.xAdjust, + y2: point.y + options.radius + options.yAdjust, + centerX: point.x + options.xAdjust, + centerY: point.y + options.yAdjust, + radius: options.radius, + width: size, + height: size + }; +} + +function getChartDimensionByScale(scale, options) { + const result = getDimensionByScale(scale, options) || options; + return { + start: Math.min(result.start, result.end), + end: Math.max(result.start, result.end) + }; +} + +function resolveFullLineProperties(scale, area, options) { + const min = scaleValue(scale, options.value, NaN); + const max = scaleValue(scale, options.endValue, min); + if (scale.isHorizontal()) { + area.x = min; + area.x2 = max; + } else { + area.y = min; + area.y2 = max; + } +} + +function resolveLimitedLineProperties(scales, area, options) { + for (const scaleId of Object.keys(limitedLineScale)) { + const scale = scales[retrieveScaleID(scales, options, scaleId)]; + if (scale) { + const {min, max, start, end, startProp, endProp} = limitedLineScale[scaleId]; + const dim = getDimensionByScale(scale, {min: options[min], max: options[max], start: scale[start], end: scale[end]}); + area[startProp] = dim.start; + area[endProp] = dim.end; + } + } +} + +function calculateX({properties, options}, labelSize, position, padding) { + const {x: start, x2: end, width: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust: options.label.xAdjust, + size: labelSize.width + }); +} + +function calculateY({properties, options}, labelSize, position, padding) { + const {y: start, y2: end, height: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust: options.label.yAdjust, + size: labelSize.height + }); +} + +function calculatePosition(boxOpts, labelOpts) { + const {start, end, borderWidth} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position); +} + +function resolveLabelElementProperties$1(chart, properties, options) { + const label = options.label; + label.backgroundColor = 'transparent'; + label.callout.display = false; + const position = toPosition(label.position); + const padding = helpers.toPadding(label.padding); + const labelSize = measureLabelSize(chart.ctx, label); + const x = calculateX({properties, options}, labelSize, position, padding); + const y = calculateY({properties, options}, labelSize, position, padding); + const width = labelSize.width + padding.width; + const height = labelSize.height + padding.height; + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2, + rotation: label.rotation + }; + +} + +const moveHooks = ['enter', 'leave']; + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + */ + +const eventHooks = moveHooks.concat('click'); + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + */ +function updateListeners(chart, state, options) { + state.listened = loadHooks(options, eventHooks, state.listeners); + state.moveListened = false; + + moveHooks.forEach(hook => { + if (helpers.isFunction(options[hook])) { + state.moveListened = true; + } + }); + + if (!state.listened || !state.moveListened) { + state.annotations.forEach(scope => { + if (!state.listened && helpers.isFunction(scope.click)) { + state.listened = true; + } + if (!state.moveListened) { + moveHooks.forEach(hook => { + if (helpers.isFunction(scope[hook])) { + state.listened = true; + state.moveListened = true; + } + }); + } + }); + } +} + +/** + * @param {Object} state + * @param {ChartEvent} event + * @param {AnnotationPluginOptions} options + * @return {boolean|undefined} + */ +function handleEvent(state, event, options) { + if (state.listened) { + switch (event.type) { + case 'mousemove': + case 'mouseout': + return handleMoveEvents(state, event, options); + case 'click': + return handleClickEvents(state, event, options); + } + } +} + +function handleMoveEvents(state, event, options) { + if (!state.moveListened) { + return; + } + + let elements; + + if (event.type === 'mousemove') { + elements = getElements(state.visibleElements, event, options.interaction); + } else { + elements = []; + } + + const previous = state.hovered; + state.hovered = elements; + + const context = {state, event}; + let changed = dispatchMoveEvents(context, 'leave', previous, elements); + return dispatchMoveEvents(context, 'enter', elements, previous) || changed; +} + +function dispatchMoveEvents({state, event}, hook, elements, checkElements) { + let changed; + for (const element of elements) { + if (checkElements.indexOf(element) < 0) { + changed = dispatchEvent(element.options[hook] || state.listeners[hook], element, event) || changed; + } + } + return changed; +} + +function handleClickEvents(state, event, options) { + const listeners = state.listeners; + const elements = getElements(state.visibleElements, event, options.interaction); + let changed; + for (const element of elements) { + changed = dispatchEvent(element.options.click || listeners.click, element, event) || changed; + } + return changed; +} + +function dispatchEvent(handler, element, event) { + return helpers.callback(handler, [element.$context, event]) === true; +} + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const elementHooks = ['afterDraw', 'beforeDraw']; + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + */ +function updateHooks(chart, state, options) { + const visibleElements = state.visibleElements; + state.hooked = loadHooks(options, elementHooks, state.hooks); + + if (!state.hooked) { + visibleElements.forEach(scope => { + if (!state.hooked) { + elementHooks.forEach(hook => { + if (helpers.isFunction(scope.options[hook])) { + state.hooked = true; + } + }); + } + }); + } +} + +/** + * @param {Object} state + * @param {AnnotationElement} element + * @param {string} hook + */ +function invokeHook(state, element, hook) { + if (state.hooked) { + const callbackHook = element.options[hook] || state.hooks[hook]; + return helpers.callback(callbackHook, [element.$context]); + } +} + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + */ + +/** + * @param {Chart} chart + * @param {Scale} scale + * @param {CoreAnnotationOptions[]} annotations + */ +function adjustScaleRange(chart, scale, annotations) { + const range = getScaleLimits(chart.scales, scale, annotations); + let changed = changeScaleLimit(scale, range, 'min', 'suggestedMin'); + changed = changeScaleLimit(scale, range, 'max', 'suggestedMax') || changed; + if (changed && helpers.isFunction(scale.handleTickRangeOptions)) { + scale.handleTickRangeOptions(); + } +} + +/** + * @param {CoreAnnotationOptions[]} annotations + * @param {{ [key: string]: Scale }} scales + */ +function verifyScaleOptions(annotations, scales) { + for (const annotation of annotations) { + verifyScaleIDs(annotation, scales); + } +} + +function changeScaleLimit(scale, range, limit, suggestedLimit) { + if (helpers.isFinite(range[limit]) && !scaleLimitDefined(scale.options, limit, suggestedLimit)) { + const changed = scale[limit] !== range[limit]; + scale[limit] = range[limit]; + return changed; + } +} + +function scaleLimitDefined(scaleOptions, limit, suggestedLimit) { + return helpers.defined(scaleOptions[limit]) || helpers.defined(scaleOptions[suggestedLimit]); +} + +function verifyScaleIDs(annotation, scales) { + for (const key of ['scaleID', 'xScaleID', 'yScaleID']) { + const scaleID = retrieveScaleID(scales, annotation, key); + if (scaleID && !scales[scaleID] && verifyProperties(annotation, key)) { + console.warn(`No scale found with id '${scaleID}' for annotation '${annotation.id}'`); + } + } +} + +function verifyProperties(annotation, key) { + if (key === 'scaleID') { + return true; + } + const axis = key.charAt(0); + for (const prop of ['Min', 'Max', 'Value']) { + if (helpers.defined(annotation[axis + prop])) { + return true; + } + } + return false; +} + +function getScaleLimits(scales, scale, annotations) { + const axis = scale.axis; + const scaleID = scale.id; + const scaleIDOption = axis + 'ScaleID'; + const limits = { + min: helpers.valueOrDefault(scale.min, Number.NEGATIVE_INFINITY), + max: helpers.valueOrDefault(scale.max, Number.POSITIVE_INFINITY) + }; + for (const annotation of annotations) { + if (annotation.scaleID === scaleID) { + updateLimits(annotation, scale, ['value', 'endValue'], limits); + } else if (retrieveScaleID(scales, annotation, scaleIDOption) === scaleID) { + updateLimits(annotation, scale, [axis + 'Min', axis + 'Max', axis + 'Value'], limits); + } + } + return limits; +} + +function updateLimits(annotation, scale, props, limits) { + for (const prop of props) { + const raw = annotation[prop]; + if (helpers.defined(raw)) { + const value = scale.parse(raw); + limits.min = Math.min(limits.min, value); + limits.max = Math.max(limits.max, value); + } + } +} + +class BoxAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const {x, y} = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), helpers.toRadians(-this.options.rotation)); + return inBoxRange({x, y}, this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis, this.options); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + ctx.save(); + translate(ctx, this.getCenterPoint(), this.options.rotation); + drawBox(ctx, this, this.options); + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + return resolveBoxAndLabelProperties(chart, options); + } +} + +BoxAnnotation.id = 'boxAnnotation'; + +BoxAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + init: undefined, + hitTolerance: 0, + label: { + backgroundColor: 'transparent', + borderWidth: 0, + callout: { + display: false + }, + color: 'black', + content: null, + display: false, + drawTime: undefined, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + hitTolerance: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: undefined, + textAlign: 'start', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0, + z: undefined + }, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +BoxAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +BoxAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +class DoughnutLabelAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + return inLabelRange( + {x: mouseX, y: mouseY}, + {rect: this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), center: this.getCenterPoint(useFinalPosition)}, + axis, + {rotation: this.rotation, borderWidth: 0, hitTolerance: this.options.hitTolerance} + ); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + if (!options.display || !options.content) { + return; + } + drawBackground(ctx, this); + ctx.save(); + translate(ctx, this.getCenterPoint(), this.rotation); + drawLabel(ctx, this, options, this._fitRatio); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const meta = getDatasetMeta(chart, options); + if (!meta) { + return {}; + } + const {controllerMeta, point, radius} = getControllerMeta(chart, options, meta); + let labelSize = measureLabelSize(chart.ctx, options); + const _fitRatio = getFitRatio(labelSize, radius); + if (shouldFit(options, _fitRatio)) { + labelSize = {width: labelSize.width * _fitRatio, height: labelSize.height * _fitRatio}; + } + const {position, xAdjust, yAdjust} = options; + const boxSize = measureLabelRectangle(point, labelSize, {borderWidth: 0, position, xAdjust, yAdjust}); + return { + initProperties: initAnimationProperties(chart, boxSize, options), + ...boxSize, + ...controllerMeta, + rotation: options.rotation, + _fitRatio + }; + } +} + +DoughnutLabelAnnotation.id = 'doughnutLabelAnnotation'; + +DoughnutLabelAnnotation.defaults = { + autoFit: true, + autoHide: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 0, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + hitTolerance: 0, + init: undefined, + opacity: undefined, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + spacing: 1, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0 +}; + +DoughnutLabelAnnotation.defaultRoutes = { +}; + +function getDatasetMeta(chart, options) { + return chart.getSortedVisibleDatasetMetas().reduce(function(result, value) { + const controller = value.controller; + if (controller instanceof chart_js.DoughnutController && + isControllerVisible(chart, options, value.data) && + (!result || controller.innerRadius < result.controller.innerRadius) && + controller.options.circumference >= 90) { + return value; + } + return result; + }, undefined); +} + +function isControllerVisible(chart, options, elements) { + if (!options.autoHide) { + return true; + } + for (let i = 0; i < elements.length; i++) { + if (!elements[i].hidden && chart.getDataVisibility(i)) { + return true; + } + } +} + +function getControllerMeta({chartArea}, options, meta) { + const {left, top, right, bottom} = chartArea; + const {innerRadius, offsetX, offsetY} = meta.controller; + const x = (left + right) / 2 + offsetX; + const y = (top + bottom) / 2 + offsetY; + const square = { + left: Math.max(x - innerRadius, left), + right: Math.min(x + innerRadius, right), + top: Math.max(y - innerRadius, top), + bottom: Math.min(y + innerRadius, bottom) + }; + const point = { + x: (square.left + square.right) / 2, + y: (square.top + square.bottom) / 2 + }; + const space = options.spacing + options.borderWidth / 2; + const _radius = innerRadius - space; + const _counterclockwise = point.y > y; + const side = _counterclockwise ? top + space : bottom - space; + const angles = getAngles(side, x, y, _radius); + const controllerMeta = { + _centerX: x, + _centerY: y, + _radius, + _counterclockwise, + ...angles + }; + return { + controllerMeta, + point, + radius: Math.min(innerRadius, Math.min(square.right - square.left, square.bottom - square.top) / 2) + }; +} + +function getFitRatio({width, height}, radius) { + const hypo = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + return (radius * 2) / hypo; +} + +function getAngles(y, centerX, centerY, radius) { + const yk2 = Math.pow(centerY - y, 2); + const r2 = Math.pow(radius, 2); + const b = centerX * -2; + const c = Math.pow(centerX, 2) + yk2 - r2; + const delta = Math.pow(b, 2) - (4 * c); + if (delta <= 0) { + return { + _startAngle: 0, + _endAngle: helpers.TAU + }; + } + const start = (-b - Math.sqrt(delta)) / 2; + const end = (-b + Math.sqrt(delta)) / 2; + return { + _startAngle: helpers.getAngleFromPoint({x: centerX, y: centerY}, {x: start, y}).angle, + _endAngle: helpers.getAngleFromPoint({x: centerX, y: centerY}, {x: end, y}).angle + }; +} + +function drawBackground(ctx, element) { + const {_centerX, _centerY, _radius, _startAngle, _endAngle, _counterclockwise, options} = element; + ctx.save(); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + ctx.arc(_centerX, _centerY, _radius, _startAngle, _endAngle, _counterclockwise); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.stroke(); + } + ctx.restore(); +} + +class LabelAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + return inLabelRange( + {x: mouseX, y: mouseY}, + {rect: this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), center: this.getCenterPoint(useFinalPosition)}, + axis, + {rotation: this.rotation, borderWidth: this.options.borderWidth, hitTolerance: this.options.hitTolerance} + ); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const visible = !helpers.defined(this._visible) || this._visible; + if (!options.display || !options.content || !visible) { + return; + } + ctx.save(); + translate(ctx, this.getCenterPoint(), this.rotation); + drawCallout(ctx, this); + drawBox(ctx, this, options); + drawLabel(ctx, getLabelSize(this), options); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + let point; + if (!isBoundToPoint(options)) { + const {centerX, centerY} = resolveBoxProperties(chart, options); + point = {x: centerX, y: centerY}; + } else { + point = getChartPoint(chart, options); + } + const padding = helpers.toPadding(options.padding); + const labelSize = measureLabelSize(chart.ctx, options); + const boxSize = measureLabelRectangle(point, labelSize, options, padding); + return { + initProperties: initAnimationProperties(chart, boxSize, options), + pointX: point.x, + pointY: point.y, + ...boxSize, + rotation: options.rotation + }; + } +} + +LabelAnnotation.id = 'labelAnnotation'; + +LabelAnnotation.defaults = { + adjustScaleRange: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: { + borderCapStyle: 'butt', + borderColor: undefined, + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 1, + display: false, + margin: 5, + position: 'auto', + side: 5, + start: '50%', + }, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + hitTolerance: 0, + init: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +LabelAnnotation.defaultRoutes = { + borderColor: 'color' +}; + +function getLabelSize({x, y, width, height, options}) { + const hBorderWidth = options.borderWidth / 2; + const padding = helpers.toPadding(options.padding); + return { + x: x + padding.left + hBorderWidth, + y: y + padding.top + hBorderWidth, + width: width - padding.left - padding.right - options.borderWidth, + height: height - padding.top - padding.bottom - options.borderWidth + }; +} + +const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)}); +const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x; +const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y; +const sqr = v => v * v; +const rangeLimit = (mouseX, mouseY, {x, y, x2, y2}, axis) => axis === 'y' ? {start: Math.min(y, y2), end: Math.max(y, y2), value: mouseY} : {start: Math.min(x, x2), end: Math.max(x, x2), value: mouseX}; +// http://www.independent-software.com/determining-coordinates-on-a-html-canvas-bezier-curve.html +const coordInCurve = (start, cp, end, t) => (1 - t) * (1 - t) * start + 2 * (1 - t) * t * cp + t * t * end; +const pointInCurve = (start, cp, end, t) => ({x: coordInCurve(start.x, cp.x, end.x, t), y: coordInCurve(start.y, cp.y, end.y, t)}); +const coordAngleInCurve = (start, cp, end, t) => 2 * (1 - t) * (cp - start) + 2 * t * (end - cp); +const angleInCurve = (start, cp, end, t) => -Math.atan2(coordAngleInCurve(start.x, cp.x, end.x, t), coordAngleInCurve(start.y, cp.y, end.y, t)) + 0.5 * helpers.PI; + +class LineAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + const point = {mouseX, mouseY}; + const {path, ctx} = this; + if (path) { + setBorderStyle(ctx, this.options); + ctx.lineWidth += this.options.hitTolerance; + const {chart} = this.$context; + const mx = mouseX * chart.currentDevicePixelRatio; + const my = mouseY * chart.currentDevicePixelRatio; + const result = ctx.isPointInStroke(path, mx, my) || isOnLabel(this, point, useFinalPosition); + ctx.restore(); + return result; + } + const epsilon = sqr(hitSize); + return intersects(this, point, epsilon, useFinalPosition) || isOnLabel(this, point, useFinalPosition); + } + return inAxisRange(this, {mouseX, mouseY}, axis, {hitSize, useFinalPosition}); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {x, y, x2, y2, cp, options} = this; + + ctx.save(); + if (!setBorderStyle(ctx, options)) { + // no border width, then line is not drawn + return ctx.restore(); + } + setShadowStyle(ctx, options); + + const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)); + if (options.curve && cp) { + drawCurve(ctx, this, cp, length); + return ctx.restore(); + } + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this); + const angle = Math.atan2(y2 - y, x2 - x); + ctx.translate(x, y); + ctx.rotate(angle); + ctx.beginPath(); + ctx.moveTo(0 + startAdjust, 0); + ctx.lineTo(length - endAdjust, 0); + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + drawArrowHead(ctx, 0, startAdjust, startOpts); + drawArrowHead(ctx, length, -endAdjust, endOpts); + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + const area = resolveLineProperties(chart, options); + const {x, y, x2, y2} = area; + const inside = isLineInArea(area, chart.chartArea); + const properties = inside + ? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea) + : {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; + properties.centerX = (x2 + x) / 2; + properties.centerY = (y2 + y) / 2; + properties.initProperties = initAnimationProperties(chart, properties, options); + if (options.curve) { + const p1 = {x: properties.x, y: properties.y}; + const p2 = {x: properties.x2, y: properties.y2}; + properties.cp = getControlPoint(properties, options, helpers.distanceBetweenPoints(p1, p2)); + } + const labelProperties = resolveLabelElementProperties(chart, properties, options.label); + // additonal prop to manage zoom/pan + labelProperties._visible = inside; + + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: labelProperties, + initProperties: properties.initProperties + }]; + return properties; + } +} + +LineAnnotation.id = 'lineAnnotation'; + +const arrowHeadsDefaults = { + backgroundColor: undefined, + backgroundShadowColor: undefined, + borderColor: undefined, + borderDash: undefined, + borderDashOffset: undefined, + borderShadowColor: undefined, + borderWidth: undefined, + display: undefined, + fill: undefined, + length: undefined, + shadowBlur: undefined, + shadowOffsetX: undefined, + shadowOffsetY: undefined, + width: undefined +}; + +LineAnnotation.defaults = { + adjustScaleRange: true, + arrowHeads: { + display: false, + end: Object.assign({}, arrowHeadsDefaults), + fill: false, + length: 12, + start: Object.assign({}, arrowHeadsDefaults), + width: 6 + }, + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 2, + curve: false, + controlPoint: { + y: '-50%' + }, + display: true, + endValue: undefined, + init: undefined, + hitTolerance: 0, + label: { + backgroundColor: 'rgba(0,0,0,0.8)', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderColor: 'black', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 6, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: Object.assign({}, LabelAnnotation.defaults.callout), + color: '#fff', + content: null, + display: false, + drawTime: undefined, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + hitTolerance: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0, + z: undefined + }, + scaleID: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + value: undefined, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +LineAnnotation.descriptors = { + arrowHeads: { + start: { + _fallback: true + }, + end: { + _fallback: true + }, + _fallback: true + } +}; + +LineAnnotation.defaultRoutes = { + borderColor: 'color' +}; + +function inAxisRange(element, {mouseX, mouseY}, axis, {hitSize, useFinalPosition}) { + const limit = rangeLimit(mouseX, mouseY, element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis); + return inLimit(limit, hitSize) || isOnLabel(element, {mouseX, mouseY}, useFinalPosition, axis); +} + +function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { + return !( + (x < left && x2 < left) || + (x > right && x2 > right) || + (y < top && y2 < top) || + (y > bottom && y2 > bottom) + ); +} + +function limitPointToArea({x, y}, p2, {top, right, bottom, left}) { + if (x < left) { + y = interpolateY(left, {x, y}, p2); + x = left; + } + if (x > right) { + y = interpolateY(right, {x, y}, p2); + x = right; + } + if (y < top) { + x = interpolateX(top, {x, y}, p2); + y = top; + } + if (y > bottom) { + x = interpolateX(bottom, {x, y}, p2); + y = bottom; + } + return {x, y}; +} + +function limitLineToArea(p1, p2, area) { + const {x, y} = limitPointToArea(p1, p2, area); + const {x: x2, y: y2} = limitPointToArea(p2, p1, area); + return {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; +} + +function intersects(element, {mouseX, mouseY}, epsilon = EPSILON, useFinalPosition) { + // Adapted from https://stackoverflow.com/a/6853926/25507 + const {x: x1, y: y1, x2, y2} = element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); + const dx = x2 - x1; + const dy = y2 - y1; + const lenSq = sqr(dx) + sqr(dy); + const t = lenSq === 0 ? -1 : ((mouseX - x1) * dx + (mouseY - y1) * dy) / lenSq; + + let xx, yy; + if (t < 0) { + xx = x1; + yy = y1; + } else if (t > 1) { + xx = x2; + yy = y2; + } else { + xx = x1 + t * dx; + yy = y1 + t * dy; + } + return (sqr(mouseX - xx) + sqr(mouseY - yy)) <= epsilon; +} + +function isOnLabel(element, {mouseX, mouseY}, useFinalPosition, axis) { + const label = element.label; + return label.options.display && label.inRange(mouseX, mouseY, axis, useFinalPosition); +} + +function resolveLabelElementProperties(chart, properties, options) { + const borderWidth = options.borderWidth; + const padding = helpers.toPadding(options.padding); + const textSize = measureLabelSize(chart.ctx, options); + const width = textSize.width + padding.width + borderWidth; + const height = textSize.height + padding.height + borderWidth; + return calculateLabelPosition(properties, options, {width, height, padding}, chart.chartArea); +} + +function calculateAutoRotation(properties) { + const {x, y, x2, y2} = properties; + const rotation = Math.atan2(y2 - y, x2 - x); + // Flip the rotation if it goes > PI/2 or < -PI/2, so label stays upright + return rotation > helpers.PI / 2 ? rotation - helpers.PI : rotation < helpers.PI / -2 ? rotation + helpers.PI : rotation; +} + +function calculateLabelPosition(properties, label, sizes, chartArea) { + const {width, height, padding} = sizes; + const {xAdjust, yAdjust} = label; + const p1 = {x: properties.x, y: properties.y}; + const p2 = {x: properties.x2, y: properties.y2}; + const rotation = label.rotation === 'auto' ? calculateAutoRotation(properties) : helpers.toRadians(label.rotation); + const size = rotatedSize(width, height, rotation); + const t = calculateT(properties, label, {labelSize: size, padding}, chartArea); + const pt = properties.cp ? pointInCurve(p1, properties.cp, p2, t) : pointInLine(p1, p2, t); + const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left}; + const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top}; + const centerX = adjustLabelCoordinate(pt.x, xCoordinateSizes) + xAdjust; + const centerY = adjustLabelCoordinate(pt.y, yCoordinateSizes) + yAdjust; + return { + x: centerX - (width / 2), + y: centerY - (height / 2), + x2: centerX + (width / 2), + y2: centerY + (height / 2), + centerX, + centerY, + pointX: pt.x, + pointY: pt.y, + width, + height, + rotation: helpers.toDegrees(rotation) + }; +} + +function rotatedSize(width, height, rotation) { + const cos = Math.cos(rotation); + const sin = Math.sin(rotation); + return { + w: Math.abs(width * cos) + Math.abs(height * sin), + h: Math.abs(width * sin) + Math.abs(height * cos) + }; +} + +function calculateT(properties, label, sizes, chartArea) { + let t; + const space = spaceAround(properties, chartArea); + if (label.position === 'start') { + t = calculateTAdjust({w: properties.x2 - properties.x, h: properties.y2 - properties.y}, sizes, label, space); + } else if (label.position === 'end') { + t = 1 - calculateTAdjust({w: properties.x - properties.x2, h: properties.y - properties.y2}, sizes, label, space); + } else { + t = getRelativePosition(1, label.position); + } + return t; +} + +function calculateTAdjust(lineSize, sizes, label, space) { + const {labelSize, padding} = sizes; + const lineW = lineSize.w * space.dx; + const lineH = lineSize.h * space.dy; + const x = (lineW > 0) && ((labelSize.w / 2 + padding.left - space.x) / lineW); + const y = (lineH > 0) && ((labelSize.h / 2 + padding.top - space.y) / lineH); + return clamp(Math.max(x, y), 0, 0.25); +} + +function spaceAround(properties, chartArea) { + const {x, x2, y, y2} = properties; + const t = Math.min(y, y2) - chartArea.top; + const l = Math.min(x, x2) - chartArea.left; + const b = chartArea.bottom - Math.max(y, y2); + const r = chartArea.right - Math.max(x, x2); + return { + x: Math.min(l, r), + y: Math.min(t, b), + dx: l <= r ? 1 : -1, + dy: t <= b ? 1 : -1 + }; +} + +function adjustLabelCoordinate(coordinate, labelSizes) { + const {size, min, max, padding} = labelSizes; + const halfSize = size / 2; + if (size > max - min) { + // if it does not fit, display as much as possible + return (max + min) / 2; + } + if (min >= (coordinate - padding - halfSize)) { + coordinate = min + padding + halfSize; + } + if (max <= (coordinate + padding + halfSize)) { + coordinate = max - padding - halfSize; + } + return coordinate; +} + +function getArrowHeads(line) { + const options = line.options; + const arrowStartOpts = options.arrowHeads && options.arrowHeads.start; + const arrowEndOpts = options.arrowHeads && options.arrowHeads.end; + return { + startOpts: arrowStartOpts, + endOpts: arrowEndOpts, + startAdjust: getLineAdjust(line, arrowStartOpts), + endAdjust: getLineAdjust(line, arrowEndOpts) + }; +} + +function getLineAdjust(line, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return 0; + } + const {length, width} = arrowOpts; + const adjust = line.options.borderWidth / 2; + const p1 = {x: length, y: width + adjust}; + const p2 = {x: 0, y: adjust}; + return Math.abs(interpolateX(0, p1, p2)); +} + +function drawArrowHead(ctx, offset, adjust, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return; + } + const {length, width, fill, backgroundColor, borderColor} = arrowOpts; + const arrowOffsetX = Math.abs(offset - length) + adjust; + ctx.beginPath(); + setShadowStyle(ctx, arrowOpts); + setBorderStyle(ctx, arrowOpts); + ctx.moveTo(arrowOffsetX, -width); + ctx.lineTo(offset + adjust, 0); + ctx.lineTo(arrowOffsetX, width); + if (fill === true) { + ctx.fillStyle = backgroundColor || borderColor; + ctx.closePath(); + ctx.fill(); + ctx.shadowColor = 'transparent'; + } else { + ctx.shadowColor = arrowOpts.borderShadowColor; + } + ctx.stroke(); +} + +function getControlPoint(properties, options, distance) { + const {x, y, x2, y2, centerX, centerY} = properties; + const angle = Math.atan2(y2 - y, x2 - x); + const cp = toPosition(options.controlPoint, 0); + const point = { + x: centerX + getSize(distance, cp.x, false), + y: centerY + getSize(distance, cp.y, false) + }; + return rotated(point, {x: centerX, y: centerY}, angle); +} + +function drawArrowHeadOnCurve(ctx, {x, y}, {angle, adjust}, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return; + } + ctx.save(); + ctx.translate(x, y); + ctx.rotate(angle); + drawArrowHead(ctx, 0, -adjust, arrowOpts); + ctx.restore(); +} + +function drawCurve(ctx, element, cp, length) { + const {x, y, x2, y2, options} = element; + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(element); + const p1 = {x, y}; + const p2 = {x: x2, y: y2}; + const startAngle = angleInCurve(p1, cp, p2, 0); + const endAngle = angleInCurve(p1, cp, p2, 1) - helpers.PI; + const ps = pointInCurve(p1, cp, p2, startAdjust / length); + const pe = pointInCurve(p1, cp, p2, 1 - endAdjust / length); + + const path = new Path2D(); + ctx.beginPath(); + path.moveTo(ps.x, ps.y); + path.quadraticCurveTo(cp.x, cp.y, pe.x, pe.y); + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(path); + element.path = path; + element.ctx = ctx; + drawArrowHeadOnCurve(ctx, ps, {angle: startAngle, adjust: startAdjust}, startOpts); + drawArrowHeadOnCurve(ctx, pe, {angle: endAngle, adjust: endAdjust}, endOpts); +} + +class EllipseAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const rotation = this.options.rotation; + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['width', 'height', 'centerX', 'centerY'], useFinalPosition), rotation, hitSize); + } + const {x, y, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); + const limit = axis === 'y' ? {start: y, end: y2} : {start: x, end: x2}; + const rotatedPoint = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), helpers.toRadians(-rotation)); + return rotatedPoint[axis] >= limit.start - hitSize - EPSILON && rotatedPoint[axis] <= limit.end + hitSize + EPSILON; + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {width, height, centerX, centerY, options} = this; + ctx.save(); + translate(ctx, this.getCenterPoint(), options.rotation); + setShadowStyle(ctx, this.options); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + const stroke = setBorderStyle(ctx, options); + ctx.ellipse(centerX, centerY, height / 2, width / 2, helpers.PI / 2, 0, 2 * helpers.PI); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + return resolveBoxAndLabelProperties(chart, options); + } + +} + +EllipseAnnotation.id = 'ellipseAnnotation'; + +EllipseAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + label: Object.assign({}, BoxAnnotation.defaults.label), + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +EllipseAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +EllipseAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +function pointInEllipse(p, ellipse, rotation, hitSize) { + const {width, height, centerX, centerY} = ellipse; + const xRadius = width / 2; + const yRadius = height / 2; + + if (xRadius <= 0 || yRadius <= 0) { + return false; + } + // https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm + const angle = helpers.toRadians(rotation || 0); + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + const a = Math.pow(cosAngle * (p.x - centerX) + sinAngle * (p.y - centerY), 2); + const b = Math.pow(sinAngle * (p.x - centerX) - cosAngle * (p.y - centerY), 2); + return (a / Math.pow(xRadius + hitSize, 2)) + (b / Math.pow(yRadius + hitSize, 2)) <= 1.0001; +} + +class PointAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const {x, y, x2, y2, width} = this.getProps(['x', 'y', 'x2', 'y2', 'width'], useFinalPosition); + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2, hitSize); + } + const limit = axis === 'y' ? {start: y, end: y2, value: mouseY} : {start: x, end: x2, value: mouseX}; + return inLimit(limit, hitSize); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const borderWidth = options.borderWidth; + if (options.radius < 0.1) { + return; + } + ctx.save(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + drawPoint(ctx, this, this.centerX, this.centerY); + if (stroke && !isImageOrCanvas(options.pointStyle)) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + options.borderWidth = borderWidth; + } + + resolveElementProperties(chart, options) { + const properties = resolvePointProperties(chart, options); + properties.initProperties = initAnimationProperties(chart, properties, options); + return properties; + } +} + +PointAnnotation.id = 'pointAnnotation'; + +PointAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + pointStyle: 'circle', + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +PointAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +class PolygonAnnotation extends chart_js.Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + if (axis !== 'x' && axis !== 'y') { + return this.options.radius >= 0.1 && this.elements.length > 1 && pointIsInPolygon(this.elements, mouseX, mouseY, useFinalPosition); + } + const rotatedPoint = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), helpers.toRadians(-this.options.rotation)); + const axisPoints = this.elements.map((point) => axis === 'y' ? point.bY : point.bX); + const start = Math.min(...axisPoints); + const end = Math.max(...axisPoints); + return rotatedPoint[axis] >= start && rotatedPoint[axis] <= end; + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {elements, options} = this; + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + let first = true; + for (const el of elements) { + if (first) { + ctx.moveTo(el.x, el.y); + first = false; + } else { + ctx.lineTo(el.x, el.y); + } + } + ctx.closePath(); + ctx.fill(); + // If no border, don't draw it + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const properties = resolvePointProperties(chart, options); + const {sides, rotation} = options; + const elements = []; + const angle = (2 * helpers.PI) / sides; + let rad = rotation * helpers.RAD_PER_DEG; + for (let i = 0; i < sides; i++, rad += angle) { + const elProps = buildPointElement(properties, options, rad); + elProps.initProperties = initAnimationProperties(chart, properties, options); + elements.push(elProps); + } + properties.elements = elements; + return properties; + } +} + +PolygonAnnotation.id = 'polygonAnnotation'; + +PolygonAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + point: { + radius: 0 + }, + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + sides: 3, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +PolygonAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +function buildPointElement({centerX, centerY}, {radius, borderWidth, hitTolerance}, rad) { + const hitSize = (borderWidth + hitTolerance) / 2; + const sin = Math.sin(rad); + const cos = Math.cos(rad); + const point = {x: centerX + sin * radius, y: centerY - cos * radius}; + return { + type: 'point', + optionScope: 'point', + properties: { + x: point.x, + y: point.y, + centerX: point.x, + centerY: point.y, + bX: centerX + sin * (radius + hitSize), + bY: centerY - cos * (radius + hitSize) + } + }; +} + +function pointIsInPolygon(points, x, y, useFinalPosition) { + let isInside = false; + let A = points[points.length - 1].getProps(['bX', 'bY'], useFinalPosition); + for (const point of points) { + const B = point.getProps(['bX', 'bY'], useFinalPosition); + if ((B.bY > y) !== (A.bY > y) && x < (A.bX - B.bX) * (y - B.bY) / (A.bY - B.bY) + B.bX) { + isInside = !isInside; + } + A = B; + } + return isInside; +} + +const annotationTypes = { + box: BoxAnnotation, + doughnutLabel: DoughnutLabelAnnotation, + ellipse: EllipseAnnotation, + label: LabelAnnotation, + line: LineAnnotation, + point: PointAnnotation, + polygon: PolygonAnnotation +}; + +/** + * Register fallback for annotation elements + * For example lineAnnotation options would be looked through: + * - the annotation object (options.plugins.annotation.annotations[id]) + * - element options (options.elements.lineAnnotation) + * - element defaults (defaults.elements.lineAnnotation) + * - annotation plugin defaults (defaults.plugins.annotation, this is what we are registering here) + */ +Object.keys(annotationTypes).forEach(key => { + chart_js.defaults.describe(`elements.${annotationTypes[key].id}`, { + _fallback: 'plugins.annotation.common' + }); +}); + +const directUpdater = { + update: Object.assign +}; + +const hooks$1 = eventHooks.concat(elementHooks); +const resolve = (value, optDefs) => helpers.isObject(optDefs) ? resolveObj(value, optDefs) : value; + + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").UpdateMode } UpdateMode + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + */ + +/** + * @param {string} prop + * @returns {boolean} + */ +const isIndexable = (prop) => prop === 'color' || prop === 'font'; + +/** + * Resolve the annotation type, checking if is supported. + * @param {string} [type=line] - annotation type + * @returns {string} resolved annotation type + */ +function resolveType(type = 'line') { + if (annotationTypes[type]) { + return type; + } + console.warn(`Unknown annotation type: '${type}', defaulting to 'line'`); + return 'line'; +} + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + * @param {UpdateMode} mode + */ +function updateElements(chart, state, options, mode) { + const animations = resolveAnimations(chart, options.animations, mode); + + const annotations = state.annotations; + const elements = resyncElements(state.elements, annotations); + + for (let i = 0; i < annotations.length; i++) { + const annotationOptions = annotations[i]; + const element = getOrCreateElement(elements, i, annotationOptions.type); + const resolver = annotationOptions.setContext(getContext(chart, element, elements, annotationOptions)); + const properties = element.resolveElementProperties(chart, resolver); + + properties.skip = toSkip(properties); + + if ('elements' in properties) { + updateSubElements(element, properties.elements, resolver, animations); + // Remove the sub-element definitions from properties, so the actual elements + // are not overwritten by their definitions + delete properties.elements; + } + + if (!helpers.defined(element.x)) { + // If the element is newly created, assing the properties directly - to + // make them readily awailable to any scriptable options. If we do not do this, + // the properties retruned by `resolveElementProperties` are available only + // after options resolution. + Object.assign(element, properties); + } + + Object.assign(element, properties.initProperties); + properties.options = resolveAnnotationOptions(resolver); + + animations.update(element, properties); + } +} + +function toSkip(properties) { + return isNaN(properties.x) || isNaN(properties.y); +} + +function resolveAnimations(chart, animOpts, mode) { + if (mode === 'reset' || mode === 'none' || mode === 'resize') { + return directUpdater; + } + return new chart_js.Animations(chart, animOpts); +} + +function updateSubElements(mainElement, elements, resolver, animations) { + const subElements = mainElement.elements || (mainElement.elements = []); + subElements.length = elements.length; + for (let i = 0; i < elements.length; i++) { + const definition = elements[i]; + const properties = definition.properties; + const subElement = getOrCreateElement(subElements, i, definition.type, definition.initProperties); + const subResolver = resolver[definition.optionScope].override(definition); + properties.options = resolveAnnotationOptions(subResolver); + animations.update(subElement, properties); + } +} + +function getOrCreateElement(elements, index, type, initProperties) { + const elementClass = annotationTypes[resolveType(type)]; + let element = elements[index]; + if (!element || !(element instanceof elementClass)) { + element = elements[index] = new elementClass(); + Object.assign(element, initProperties); + } + return element; +} + +function resolveAnnotationOptions(resolver) { + const elementClass = annotationTypes[resolveType(resolver.type)]; + const result = {}; + result.id = resolver.id; + result.type = resolver.type; + result.drawTime = resolver.drawTime; + Object.assign(result, + resolveObj(resolver, elementClass.defaults), + resolveObj(resolver, elementClass.defaultRoutes)); + for (const hook of hooks$1) { + result[hook] = resolver[hook]; + } + return result; +} + +function resolveObj(resolver, defs) { + const result = {}; + for (const prop of Object.keys(defs)) { + const optDefs = defs[prop]; + const value = resolver[prop]; + if (isIndexable(prop) && helpers.isArray(value)) { + result[prop] = value.map((item) => resolve(item, optDefs)); + } else { + result[prop] = resolve(value, optDefs); + } + } + return result; +} + +function getContext(chart, element, elements, annotation) { + return element.$context || (element.$context = Object.assign(Object.create(chart.getContext()), { + element, + get elements() { + return elements.filter((el) => el && el.options); + }, + id: annotation.id, + type: 'annotation' + })); +} + +function resyncElements(elements, annotations) { + const count = annotations.length; + const start = elements.length; + + if (start < count) { + const add = count - start; + elements.splice(start, 0, ...new Array(add)); + } else if (start > count) { + elements.splice(count, start - count); + } + return elements; +} + +var version = "3.1.0"; + +const chartStates = new Map(); +const isNotDoughnutLabel = annotation => annotation.type !== 'doughnutLabel'; +const hooks = eventHooks.concat(elementHooks); + +var Annotation = { + id: 'annotation', + + version, + + beforeRegister() { + requireVersion('chart.js', '4.0', chart_js.Chart.version); + }, + + afterRegister() { + chart_js.Chart.register(annotationTypes); + }, + + afterUnregister() { + chart_js.Chart.unregister(annotationTypes); + }, + + beforeInit(chart) { + chartStates.set(chart, { + annotations: [], + elements: [], + visibleElements: [], + listeners: {}, + listened: false, + moveListened: false, + hooks: {}, + hooked: false, + hovered: [] + }); + }, + + beforeUpdate(chart, args, options) { + const state = chartStates.get(chart); + const annotations = state.annotations = []; + + let annotationOptions = options.annotations; + if (helpers.isObject(annotationOptions)) { + Object.keys(annotationOptions).forEach(key => { + const value = annotationOptions[key]; + if (helpers.isObject(value)) { + value.id = key; + annotations.push(value); + } + }); + } else if (helpers.isArray(annotationOptions)) { + annotations.push(...annotationOptions); + } + verifyScaleOptions(annotations.filter(isNotDoughnutLabel), chart.scales); + }, + + afterDataLimits(chart, args) { + const state = chartStates.get(chart); + adjustScaleRange(chart, args.scale, state.annotations.filter(isNotDoughnutLabel).filter(a => a.display && a.adjustScaleRange)); + }, + + afterUpdate(chart, args, options) { + const state = chartStates.get(chart); + updateListeners(chart, state, options); + updateElements(chart, state, options, args.mode); + state.visibleElements = state.elements.filter(el => !el.skip && el.options.display); + updateHooks(chart, state, options); + }, + + beforeDatasetsDraw(chart, _args, options) { + draw(chart, 'beforeDatasetsDraw', options.clip); + }, + + afterDatasetsDraw(chart, _args, options) { + draw(chart, 'afterDatasetsDraw', options.clip); + }, + + beforeDatasetDraw(chart, _args, options) { + draw(chart, _args.index, options.clip); + }, + + beforeDraw(chart, _args, options) { + draw(chart, 'beforeDraw', options.clip); + }, + + afterDraw(chart, _args, options) { + draw(chart, 'afterDraw', options.clip); + }, + + beforeEvent(chart, args, options) { + const state = chartStates.get(chart); + if (handleEvent(state, args.event, options)) { + args.changed = true; + } + }, + + afterDestroy(chart) { + chartStates.delete(chart); + }, + + getAnnotations(chart) { + const state = chartStates.get(chart); + return state ? state.elements : []; + }, + + // only for testing + _getAnnotationElementsAtEventForMode(visibleElements, event, options) { + return getElements(visibleElements, event, options); + }, + + defaults: { + animations: { + numbers: { + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'centerX', 'centerY', 'pointX', 'pointY', 'radius'], + type: 'number' + }, + colors: { + properties: ['backgroundColor', 'borderColor'], + type: 'color' + } + }, + clip: true, + interaction: { + mode: undefined, + axis: undefined, + intersect: undefined + }, + common: { + drawTime: 'afterDatasetsDraw', + init: false, + label: { + } + } + }, + + descriptors: { + _indexable: false, + _scriptable: (prop) => !hooks.includes(prop) && prop !== 'init', + annotations: { + _allKeys: false, + _fallback: (prop, opts) => `elements.${annotationTypes[resolveType(opts.type)].id}` + }, + interaction: { + _fallback: true + }, + common: { + label: { + _indexable: isIndexable, + _fallback: true + }, + _indexable: isIndexable + } + }, + + additionalOptionScopes: [''] +}; + +function draw(chart, caller, clip) { + const {ctx, chartArea} = chart; + const state = chartStates.get(chart); + + if (clip) { + helpers.clipArea(ctx, chartArea); + } + + const drawableElements = getDrawableElements(state.visibleElements, caller).sort((a, b) => a.element.options.z - b.element.options.z); + for (const item of drawableElements) { + drawElement(ctx, chartArea, state, item); + } + + if (clip) { + helpers.unclipArea(ctx); + } +} + +function getDrawableElements(elements, caller) { + const drawableElements = []; + for (const el of elements) { + if (el.options.drawTime === caller) { + drawableElements.push({element: el, main: true}); + } + if (el.elements && el.elements.length) { + for (const sub of el.elements) { + if (sub.options.display && sub.options.drawTime === caller) { + drawableElements.push({element: sub}); + } + } + } + } + return drawableElements; +} + +function drawElement(ctx, chartArea, state, item) { + const el = item.element; + if (item.main) { + invokeHook(state, el, 'beforeDraw'); + el.draw(ctx, chartArea); + invokeHook(state, el, 'afterDraw'); + } else { + el.draw(ctx, chartArea); + } +} + +chart_js.Chart.register(Annotation); + +return Annotation; + +})); diff --git a/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.esm.js b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.esm.js new file mode 100644 index 0000000..4d6b1f2 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.esm.js @@ -0,0 +1,3033 @@ +/*! +* chartjs-plugin-annotation v3.1.0 +* https://www.chartjs.org/chartjs-plugin-annotation/index + * (c) 2024 chartjs-plugin-annotation Contributors + * Released under the MIT License + */ +import { Element, DoughnutController, defaults, Animations, Chart } from 'chart.js'; +import { distanceBetweenPoints, toRadians, isObject, valueOrDefault, defined, isFunction, callback, isArray, toFont, addRoundedRectPath, toTRBLCorners, QUARTER_PI, PI, HALF_PI, TWO_THIRDS_PI, TAU, isNumber, RAD_PER_DEG, toPadding, isFinite, getAngleFromPoint, toDegrees, clipArea, unclipArea } from 'chart.js/helpers'; + +/** + * @typedef { import("chart.js").ChartEvent } ChartEvent + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const interaction = { + modes: { + /** + * Point mode returns all elements that hit test based on the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @return {AnnotationElement[]} - elements that are found + */ + point(visibleElements, event) { + return filterElements(visibleElements, event, {intersect: true}); + }, + + /** + * Nearest mode returns the element closest to the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found (only 1 element) + */ + nearest(visibleElements, event, options) { + return getNearestItem(visibleElements, event, options); + }, + /** + * x mode returns the elements that hit-test at the current x coordinate + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ + x(visibleElements, event, options) { + return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'x'}); + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ + y(visibleElements, event, options) { + return filterElements(visibleElements, event, {intersect: options.intersect, axis: 'y'}); + } + } +}; + +/** + * Returns all elements that hit test based on the event position + * @param {AnnotationElement[]} visibleElements - annotation elements which are visible + * @param {ChartEvent} event - the event we are find things at + * @param {Object} options - interaction options to use + * @return {AnnotationElement[]} - elements that are found + */ +function getElements(visibleElements, event, options) { + const mode = interaction.modes[options.mode] || interaction.modes.nearest; + return mode(visibleElements, event, options); +} + +function inRangeByAxis(element, event, axis) { + if (axis !== 'x' && axis !== 'y') { + return element.inRange(event.x, event.y, 'x', true) || element.inRange(event.x, event.y, 'y', true); + } + return element.inRange(event.x, event.y, axis, true); +} + +function getPointByAxis(event, center, axis) { + if (axis === 'x') { + return {x: event.x, y: center.y}; + } else if (axis === 'y') { + return {x: center.x, y: event.y}; + } + return center; +} + +function filterElements(visibleElements, event, options) { + return visibleElements.filter((element) => options.intersect ? element.inRange(event.x, event.y) : inRangeByAxis(element, event, options.axis)); +} + +function getNearestItem(visibleElements, event, options) { + let minDistance = Number.POSITIVE_INFINITY; + + return filterElements(visibleElements, event, options) + .reduce((nearestItems, element) => { + const center = element.getCenterPoint(); + const evenPoint = getPointByAxis(event, center, options.axis); + const distance = distanceBetweenPoints(event, evenPoint); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + + return nearestItems; + }, []) + .sort((a, b) => a._index - b._index) + .slice(0, 1); // return only the top item; +} + +/** + * @typedef {import('chart.js').Point} Point + */ + +/** + * Rotate a `point` relative to `center` point by `angle` + * @param {Point} point - the point to rotate + * @param {Point} center - center point for rotation + * @param {number} angle - angle for rotation, in radians + * @returns {Point} rotated point + */ +function rotated(point, center, angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const cx = center.x; + const cy = center.y; + + return { + x: cx + cos * (point.x - cx) - sin * (point.y - cy), + y: cy + sin * (point.x - cx) + cos * (point.y - cy) + }; +} + +const isOlderPart = (act, req) => req > act || (act.length > req.length && act.slice(0, req.length) === req); + +/** + * @typedef { import('chart.js').Point } Point + * @typedef { import('chart.js').InteractionAxis } InteractionAxis + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const EPSILON = 0.001; +const clamp = (x, from, to) => Math.min(to, Math.max(from, x)); + +/** + * @param {{value: number, start: number, end: number}} limit + * @param {number} hitSize + * @returns {boolean} + */ +const inLimit = (limit, hitSize) => limit.value >= limit.start - hitSize && limit.value <= limit.end + hitSize; + +/** + * @param {Object} obj + * @param {number} from + * @param {number} to + * @returns {Object} + */ +function clampAll(obj, from, to) { + for (const key of Object.keys(obj)) { + obj[key] = clamp(obj[key], from, to); + } + return obj; +} + +/** + * @param {Point} point + * @param {Point} center + * @param {number} radius + * @param {number} hitSize + * @returns {boolean} + */ +function inPointRange(point, center, radius, hitSize) { + if (!point || !center || radius <= 0) { + return false; + } + return (Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)) <= Math.pow(radius + hitSize, 2); +} + +/** + * @param {Point} point + * @param {{x: number, y: number, x2: number, y2: number}} rect + * @param {InteractionAxis} axis + * @param {{borderWidth: number, hitTolerance: number}} hitsize + * @returns {boolean} + */ +function inBoxRange(point, {x, y, x2, y2}, axis, {borderWidth, hitTolerance}) { + const hitSize = (borderWidth + hitTolerance) / 2; + const inRangeX = point.x >= x - hitSize - EPSILON && point.x <= x2 + hitSize + EPSILON; + const inRangeY = point.y >= y - hitSize - EPSILON && point.y <= y2 + hitSize + EPSILON; + if (axis === 'x') { + return inRangeX; + } else if (axis === 'y') { + return inRangeY; + } + return inRangeX && inRangeY; +} + +/** + * @param {Point} point + * @param {rect: {x: number, y: number, x2: number, y2: number}, center: {x: number, y: number}} element + * @param {InteractionAxis} axis + * @param {{rotation: number, borderWidth: number, hitTolerance: number}} + * @returns {boolean} + */ +function inLabelRange(point, {rect, center}, axis, {rotation, borderWidth, hitTolerance}) { + const rotPoint = rotated(point, center, toRadians(-rotation)); + return inBoxRange(rotPoint, rect, axis, {borderWidth, hitTolerance}); +} + +/** + * @param {AnnotationElement} element + * @param {boolean} useFinalPosition + * @returns {Point} + */ +function getElementCenterPoint(element, useFinalPosition) { + const {centerX, centerY} = element.getProps(['centerX', 'centerY'], useFinalPosition); + return {x: centerX, y: centerY}; +} + +/** + * @param {string} pkg + * @param {string} min + * @param {string} ver + * @param {boolean} [strict=true] + * @returns {boolean} + */ +function requireVersion(pkg, min, ver, strict = true) { + const parts = ver.split('.'); + let i = 0; + for (const req of min.split('.')) { + const act = parts[i++]; + if (parseInt(req, 10) < parseInt(act, 10)) { + break; + } + if (isOlderPart(act, req)) { + if (strict) { + throw new Error(`${pkg} v${ver} is not supported. v${min} or newer is required.`); + } else { + return false; + } + } + } + return true; +} + +const isPercentString = (s) => typeof s === 'string' && s.endsWith('%'); +const toPercent = (s) => parseFloat(s) / 100; +const toPositivePercent = (s) => clamp(toPercent(s), 0, 1); + +const boxAppering = (x, y) => ({x, y, x2: x, y2: y, width: 0, height: 0}); +const defaultInitAnimation = { + box: (properties) => boxAppering(properties.centerX, properties.centerY), + doughnutLabel: (properties) => boxAppering(properties.centerX, properties.centerY), + ellipse: (properties) => ({centerX: properties.centerX, centerY: properties.centerX, radius: 0, width: 0, height: 0}), + label: (properties) => boxAppering(properties.centerX, properties.centerY), + line: (properties) => boxAppering(properties.x, properties.y), + point: (properties) => ({centerX: properties.centerX, centerY: properties.centerY, radius: 0, width: 0, height: 0}), + polygon: (properties) => boxAppering(properties.centerX, properties.centerY) +}; + +/** + * @typedef { import('chart.js').FontSpec } FontSpec + * @typedef { import('chart.js').Point } Point + * @typedef { import('chart.js').Padding } Padding + * @typedef { import('../../types/element').AnnotationBoxModel } AnnotationBoxModel + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + * @typedef { import('../../types/options').AnnotationPointCoordinates } AnnotationPointCoordinates + * @typedef { import('../../types/label').CoreLabelOptions } CoreLabelOptions + * @typedef { import('../../types/label').LabelPositionObject } LabelPositionObject + */ + +/** + * @param {number} size + * @param {number|string} position + * @returns {number} + */ +function getRelativePosition(size, position) { + if (position === 'start') { + return 0; + } + if (position === 'end') { + return size; + } + if (isPercentString(position)) { + return toPositivePercent(position) * size; + } + return size / 2; +} + +/** + * @param {number} size + * @param {number|string} value + * @param {boolean} [positivePercent=true] + * @returns {number} + */ +function getSize(size, value, positivePercent = true) { + if (typeof value === 'number') { + return value; + } else if (isPercentString(value)) { + return (positivePercent ? toPositivePercent(value) : toPercent(value)) * size; + } + return size; +} + +/** + * @param {{x: number, width: number}} size + * @param {CoreLabelOptions} options + * @returns {number} + */ +function calculateTextAlignment(size, options) { + const {x, width} = size; + const textAlign = options.textAlign; + if (textAlign === 'center') { + return x + width / 2; + } else if (textAlign === 'end' || textAlign === 'right') { + return x + width; + } + return x; +} + +/** + * @param {Point} point + * @param {{height: number, width: number}} labelSize + * @param {{borderWidth: number, position: {LabelPositionObject|string}, xAdjust: number, yAdjust: number}} options + * @param {Padding|undefined} padding + * @returns {{x: number, y: number, x2: number, y2: number, height: number, width: number, centerX: number, centerY: number}} + */ +function measureLabelRectangle(point, labelSize, {borderWidth, position, xAdjust, yAdjust}, padding) { + const hasPadding = isObject(padding); + const width = labelSize.width + (hasPadding ? padding.width : 0) + borderWidth; + const height = labelSize.height + (hasPadding ? padding.height : 0) + borderWidth; + const positionObj = toPosition(position); + const x = calculateLabelPosition$1(point.x, width, xAdjust, positionObj.x); + const y = calculateLabelPosition$1(point.y, height, yAdjust, positionObj.y); + + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2 + }; +} + +/** + * @param {LabelPositionObject|string} value + * @param {string|number} defaultValue + * @returns {LabelPositionObject} + */ +function toPosition(value, defaultValue = 'center') { + if (isObject(value)) { + return { + x: valueOrDefault(value.x, defaultValue), + y: valueOrDefault(value.y, defaultValue), + }; + } + value = valueOrDefault(value, defaultValue); + return { + x: value, + y: value + }; +} + +/** + * @param {CoreLabelOptions} options + * @param {number} fitRatio + * @returns {boolean} + */ +const shouldFit = (options, fitRatio) => options && options.autoFit && fitRatio < 1; + +/** + * @param {CoreLabelOptions} options + * @param {number} fitRatio + * @returns {FontSpec[]} + */ +function toFonts(options, fitRatio) { + const optFont = options.font; + const fonts = isArray(optFont) ? optFont : [optFont]; + if (shouldFit(options, fitRatio)) { + return fonts.map(function(f) { + const font = toFont(f); + font.size = Math.floor(f.size * fitRatio); + font.lineHeight = f.lineHeight; + return toFont(font); + }); + } + return fonts.map(f => toFont(f)); +} + +/** + * @param {AnnotationPointCoordinates} options + * @returns {boolean} + */ +function isBoundToPoint(options) { + return options && (defined(options.xValue) || defined(options.yValue)); +} + +function calculateLabelPosition$1(start, size, adjust = 0, position) { + return start - getRelativePosition(size, position) + adjust; +} + +/** + * @param {Chart} chart + * @param {AnnotationBoxModel} properties + * @param {CoreAnnotationOptions} options + * @returns {AnnotationElement} + */ +function initAnimationProperties(chart, properties, options) { + const initAnim = options.init; + if (!initAnim) { + return; + } else if (initAnim === true) { + return applyDefault(properties, options); + } + return execCallback(chart, properties, options); +} + +/** + * @param {Object} options + * @param {Array} hooks + * @param {Object} hooksContainer + * @returns {boolean} + */ +function loadHooks(options, hooks, hooksContainer) { + let activated = false; + hooks.forEach(hook => { + if (isFunction(options[hook])) { + activated = true; + hooksContainer[hook] = options[hook]; + } else if (defined(hooksContainer[hook])) { + delete hooksContainer[hook]; + } + }); + return activated; +} + +function applyDefault(properties, options) { + const type = options.type || 'line'; + return defaultInitAnimation[type](properties); +} + +function execCallback(chart, properties, options) { + const result = callback(options.init, [{chart, properties, options}]); + if (result === true) { + return applyDefault(properties, options); + } else if (isObject(result)) { + return result; + } +} + +const widthCache = new Map(); +const notRadius = (radius) => isNaN(radius) || radius <= 0; +const fontsKey = (fonts) => fonts.reduce(function(prev, item) { + prev += item.string; + return prev; +}, ''); + +/** + * @typedef { import('chart.js').Point } Point + * @typedef { import('../../types/label').CoreLabelOptions } CoreLabelOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + */ + +/** + * Determine if content is an image or a canvas. + * @param {*} content + * @returns boolean|undefined + * @todo move this function to chart.js helpers + */ +function isImageOrCanvas(content) { + if (content && typeof content === 'object') { + const type = content.toString(); + return (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]'); + } +} + +/** + * Set the translation on the canvas if the rotation must be applied. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {Point} point - the point of translation + * @param {number} rotation - rotation (in degrees) to apply + */ +function translate(ctx, {x, y}, rotation) { + if (rotation) { + ctx.translate(x, y); + ctx.rotate(toRadians(rotation)); + ctx.translate(-x, -y); + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {Object} options + * @returns {boolean|undefined} + */ +function setBorderStyle(ctx, options) { + if (options && options.borderWidth) { + ctx.lineCap = options.borderCapStyle || 'butt'; + ctx.setLineDash(options.borderDash); + ctx.lineDashOffset = options.borderDashOffset; + ctx.lineJoin = options.borderJoinStyle || 'miter'; + ctx.lineWidth = options.borderWidth; + ctx.strokeStyle = options.borderColor; + return true; + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {Object} options + */ +function setShadowStyle(ctx, options) { + ctx.shadowColor = options.backgroundShadowColor; + ctx.shadowBlur = options.shadowBlur; + ctx.shadowOffsetX = options.shadowOffsetX; + ctx.shadowOffsetY = options.shadowOffsetY; +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {CoreLabelOptions} options + * @returns {{width: number, height: number}} + */ +function measureLabelSize(ctx, options) { + const content = options.content; + if (isImageOrCanvas(content)) { + const size = { + width: getSize(content.width, options.width), + height: getSize(content.height, options.height) + }; + return size; + } + const fonts = toFonts(options); + const strokeWidth = options.textStrokeWidth; + const lines = isArray(content) ? content : [content]; + const mapKey = lines.join() + fontsKey(fonts) + strokeWidth + (ctx._measureText ? '-spriting' : ''); + if (!widthCache.has(mapKey)) { + widthCache.set(mapKey, calculateLabelSize(ctx, lines, fonts, strokeWidth)); + } + return widthCache.get(mapKey); +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{x: number, y: number, width: number, height: number}} rect + * @param {Object} options + */ +function drawBox(ctx, rect, options) { + const {x, y, width, height} = rect; + ctx.save(); + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + addRoundedRectPath(ctx, { + x, y, w: width, h: height, + radius: clampAll(toTRBLCorners(options.borderRadius), 0, Math.min(width, height) / 2) + }); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{x: number, y: number, width: number, height: number}} rect + * @param {CoreLabelOptions} options + * @param {number} fitRatio + */ +function drawLabel(ctx, rect, options, fitRatio) { + const content = options.content; + if (isImageOrCanvas(content)) { + ctx.save(); + ctx.globalAlpha = getOpacity(options.opacity, content.style.opacity); + ctx.drawImage(content, rect.x, rect.y, rect.width, rect.height); + ctx.restore(); + return; + } + const labels = isArray(content) ? content : [content]; + const fonts = toFonts(options, fitRatio); + const optColor = options.color; + const colors = isArray(optColor) ? optColor : [optColor]; + const x = calculateTextAlignment(rect, options); + const y = rect.y + options.textStrokeWidth / 2; + ctx.save(); + ctx.textBaseline = 'middle'; + ctx.textAlign = options.textAlign; + if (setTextStrokeStyle(ctx, options)) { + applyLabelDecoration(ctx, {x, y}, labels, fonts); + } + applyLabelContent(ctx, {x, y}, labels, {fonts, colors}); + ctx.restore(); +} + +function setTextStrokeStyle(ctx, options) { + if (options.textStrokeWidth > 0) { + // https://stackoverflow.com/questions/13627111/drawing-text-with-an-outer-stroke-with-html5s-canvas + ctx.lineJoin = 'round'; + ctx.miterLimit = 2; + ctx.lineWidth = options.textStrokeWidth; + ctx.strokeStyle = options.textStrokeColor; + return true; + } +} + +/** + * @param {CanvasRenderingContext2D} ctx + * @param {{radius: number, options: PointAnnotationOptions}} element + * @param {number} x + * @param {number} y + */ +function drawPoint(ctx, element, x, y) { + const {radius, options} = element; + const style = options.pointStyle; + const rotation = options.rotation; + let rad = (rotation || 0) * RAD_PER_DEG; + + if (isImageOrCanvas(style)) { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + if (notRadius(radius)) { + return; + } + drawPointStyle(ctx, {x, y, radius, rotation, style, rad}); +} + +function drawPointStyle(ctx, {x, y, radius, rotation, style, rad}) { + let xOffset, yOffset, size, cornerRadius; + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, TAU); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); +} + +function calculateLabelSize(ctx, lines, fonts, strokeWidth) { + ctx.save(); + const count = lines.length; + let width = 0; + let height = strokeWidth; + for (let i = 0; i < count; i++) { + const font = fonts[Math.min(i, fonts.length - 1)]; + ctx.font = font.string; + const text = lines[i]; + width = Math.max(width, ctx.measureText(text).width + strokeWidth); + height += font.lineHeight; + } + ctx.restore(); + return {width, height}; +} + +function applyLabelDecoration(ctx, {x, y}, labels, fonts) { + ctx.beginPath(); + let lhs = 0; + labels.forEach(function(l, i) { + const f = fonts[Math.min(i, fonts.length - 1)]; + const lh = f.lineHeight; + ctx.font = f.string; + ctx.strokeText(l, x, y + lh / 2 + lhs); + lhs += lh; + }); + ctx.stroke(); +} + +function applyLabelContent(ctx, {x, y}, labels, {fonts, colors}) { + let lhs = 0; + labels.forEach(function(l, i) { + const c = colors[Math.min(i, colors.length - 1)]; + const f = fonts[Math.min(i, fonts.length - 1)]; + const lh = f.lineHeight; + ctx.beginPath(); + ctx.font = f.string; + ctx.fillStyle = c; + ctx.fillText(l, x, y + lh / 2 + lhs); + lhs += lh; + ctx.fill(); + }); +} + +function getOpacity(value, elementValue) { + const opacity = isNumber(value) ? value : elementValue; + return isNumber(opacity) ? clamp(opacity, 0, 1) : 1; +} + +const positions = ['left', 'bottom', 'top', 'right']; + +/** + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +/** + * Drawa the callout component for labels. + * @param {CanvasRenderingContext2D} ctx - chart canvas context + * @param {AnnotationElement} element - the label element + */ +function drawCallout(ctx, element) { + const {pointX, pointY, options} = element; + const callout = options.callout; + const calloutPosition = callout && callout.display && resolveCalloutPosition(element, callout); + if (!calloutPosition || isPointInRange(element, callout, calloutPosition)) { + return; + } + + ctx.save(); + ctx.beginPath(); + const stroke = setBorderStyle(ctx, callout); + if (!stroke) { + return ctx.restore(); + } + const {separatorStart, separatorEnd} = getCalloutSeparatorCoord(element, calloutPosition); + const {sideStart, sideEnd} = getCalloutSideCoord(element, calloutPosition, separatorStart); + if (callout.margin > 0 || options.borderWidth === 0) { + ctx.moveTo(separatorStart.x, separatorStart.y); + ctx.lineTo(separatorEnd.x, separatorEnd.y); + } + ctx.moveTo(sideStart.x, sideStart.y); + ctx.lineTo(sideEnd.x, sideEnd.y); + const rotatedPoint = rotated({x: pointX, y: pointY}, element.getCenterPoint(), toRadians(-element.rotation)); + ctx.lineTo(rotatedPoint.x, rotatedPoint.y); + ctx.stroke(); + ctx.restore(); +} + +function getCalloutSeparatorCoord(element, position) { + const {x, y, x2, y2} = element; + const adjust = getCalloutSeparatorAdjust(element, position); + let separatorStart, separatorEnd; + if (position === 'left' || position === 'right') { + separatorStart = {x: x + adjust, y}; + separatorEnd = {x: separatorStart.x, y: y2}; + } else { + // position 'top' or 'bottom' + separatorStart = {x, y: y + adjust}; + separatorEnd = {x: x2, y: separatorStart.y}; + } + return {separatorStart, separatorEnd}; +} + +function getCalloutSeparatorAdjust(element, position) { + const {width, height, options} = element; + const adjust = options.callout.margin + options.borderWidth / 2; + if (position === 'right') { + return width + adjust; + } else if (position === 'bottom') { + return height + adjust; + } + return -adjust; +} + +function getCalloutSideCoord(element, position, separatorStart) { + const {y, width, height, options} = element; + const start = options.callout.start; + const side = getCalloutSideAdjust(position, options.callout); + let sideStart, sideEnd; + if (position === 'left' || position === 'right') { + sideStart = {x: separatorStart.x, y: y + getSize(height, start)}; + sideEnd = {x: sideStart.x + side, y: sideStart.y}; + } else { + // position 'top' or 'bottom' + sideStart = {x: separatorStart.x + getSize(width, start), y: separatorStart.y}; + sideEnd = {x: sideStart.x, y: sideStart.y + side}; + } + return {sideStart, sideEnd}; +} + +function getCalloutSideAdjust(position, options) { + const side = options.side; + if (position === 'left' || position === 'top') { + return -side; + } + return side; +} + +function resolveCalloutPosition(element, options) { + const position = options.position; + if (positions.includes(position)) { + return position; + } + return resolveCalloutAutoPosition(element, options); +} + +function resolveCalloutAutoPosition(element, options) { + const {x, y, x2, y2, width, height, pointX, pointY, centerX, centerY, rotation} = element; + const center = {x: centerX, y: centerY}; + const start = options.start; + const xAdjust = getSize(width, start); + const yAdjust = getSize(height, start); + const xPoints = [x, x + xAdjust, x + xAdjust, x2]; + const yPoints = [y + yAdjust, y2, y, y2]; + const result = []; + for (let index = 0; index < 4; index++) { + const rotatedPoint = rotated({x: xPoints[index], y: yPoints[index]}, center, toRadians(rotation)); + result.push({ + position: positions[index], + distance: distanceBetweenPoints(rotatedPoint, {x: pointX, y: pointY}) + }); + } + return result.sort((a, b) => a.distance - b.distance)[0].position; +} + +function isPointInRange(element, callout, position) { + const {pointX, pointY} = element; + const margin = callout.margin; + let x = pointX; + let y = pointY; + if (position === 'left') { + x += margin; + } else if (position === 'right') { + x -= margin; + } else if (position === 'top') { + y += margin; + } else if (position === 'bottom') { + y -= margin; + } + return element.inRange(x, y); +} + +const limitedLineScale = { + xScaleID: {min: 'xMin', max: 'xMax', start: 'left', end: 'right', startProp: 'x', endProp: 'x2'}, + yScaleID: {min: 'yMin', max: 'yMax', start: 'bottom', end: 'top', startProp: 'y', endProp: 'y2'} +}; + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import("chart.js").Point } Point + * @typedef { import('../../types/element').AnnotationBoxModel } AnnotationBoxModel + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + * @typedef { import('../../types/options').LineAnnotationOptions } LineAnnotationOptions + * @typedef { import('../../types/options').PointAnnotationOptions } PointAnnotationOptions + * @typedef { import('../../types/options').PolygonAnnotationOptions } PolygonAnnotationOptions + */ + +/** + * @param {Scale} scale + * @param {number|string} value + * @param {number} fallback + * @returns {number} + */ +function scaleValue(scale, value, fallback) { + value = typeof value === 'number' ? value : scale.parse(value); + return isFinite(value) ? scale.getPixelForValue(value) : fallback; +} + +/** + * Search the scale defined in chartjs by the axis related to the annotation options key. + * @param {{ [key: string]: Scale }} scales + * @param {CoreAnnotationOptions} options + * @param {string} key + * @returns {string} + */ +function retrieveScaleID(scales, options, key) { + const scaleID = options[key]; + if (scaleID || key === 'scaleID') { + return scaleID; + } + const axis = key.charAt(0); + const axes = Object.values(scales).filter((scale) => scale.axis && scale.axis === axis); + if (axes.length) { + return axes[0].id; + } + return axis; +} + +/** + * @param {Scale} scale + * @param {{min: number, max: number, start: number, end: number}} options + * @returns {{start: number, end: number}|undefined} + */ +function getDimensionByScale(scale, options) { + if (scale) { + const reverse = scale.options.reverse; + const start = scaleValue(scale, options.min, reverse ? options.end : options.start); + const end = scaleValue(scale, options.max, reverse ? options.start : options.end); + return { + start, + end + }; + } +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {Point} + */ +function getChartPoint(chart, options) { + const {chartArea, scales} = chart; + const xScale = scales[retrieveScaleID(scales, options, 'xScaleID')]; + const yScale = scales[retrieveScaleID(scales, options, 'yScaleID')]; + let x = chartArea.width / 2; + let y = chartArea.height / 2; + + if (xScale) { + x = scaleValue(xScale, options.xValue, xScale.left + xScale.width / 2); + } + + if (yScale) { + y = scaleValue(yScale, options.yValue, yScale.top + yScale.height / 2); + } + return {x, y}; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolveBoxProperties(chart, options) { + const scales = chart.scales; + const xScale = scales[retrieveScaleID(scales, options, 'xScaleID')]; + const yScale = scales[retrieveScaleID(scales, options, 'yScaleID')]; + + if (!xScale && !yScale) { + return {}; + } + + let {left: x, right: x2} = xScale || chart.chartArea; + let {top: y, bottom: y2} = yScale || chart.chartArea; + const xDim = getChartDimensionByScale(xScale, {min: options.xMin, max: options.xMax, start: x, end: x2}); + x = xDim.start; + x2 = xDim.end; + const yDim = getChartDimensionByScale(yScale, {min: options.yMin, max: options.yMax, start: y2, end: y}); + y = yDim.start; + y2 = yDim.end; + + return { + x, + y, + x2, + y2, + width: x2 - x, + height: y2 - y, + centerX: x + (x2 - x) / 2, + centerY: y + (y2 - y) / 2 + }; +} + +/** + * @param {Chart} chart + * @param {PointAnnotationOptions|PolygonAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolvePointProperties(chart, options) { + if (!isBoundToPoint(options)) { + const box = resolveBoxProperties(chart, options); + let radius = options.radius; + if (!radius || isNaN(radius)) { + radius = Math.min(box.width, box.height) / 2; + options.radius = radius; + } + const size = radius * 2; + const adjustCenterX = box.centerX + options.xAdjust; + const adjustCenterY = box.centerY + options.yAdjust; + return { + x: adjustCenterX - radius, + y: adjustCenterY - radius, + x2: adjustCenterX + radius, + y2: adjustCenterY + radius, + centerX: adjustCenterX, + centerY: adjustCenterY, + width: size, + height: size, + radius + }; + } + return getChartCircle(chart, options); +} +/** + * @param {Chart} chart + * @param {LineAnnotationOptions} options + * @returns {AnnotationBoxModel} + */ +function resolveLineProperties(chart, options) { + const {scales, chartArea} = chart; + const scale = scales[options.scaleID]; + const area = {x: chartArea.left, y: chartArea.top, x2: chartArea.right, y2: chartArea.bottom}; + + if (scale) { + resolveFullLineProperties(scale, area, options); + } else { + resolveLimitedLineProperties(scales, area, options); + } + return area; +} + +/** + * @param {Chart} chart + * @param {CoreAnnotationOptions} options + * @param {boolean} [centerBased=false] + * @returns {AnnotationBoxModel} + */ +function resolveBoxAndLabelProperties(chart, options) { + const properties = resolveBoxProperties(chart, options); + properties.initProperties = initAnimationProperties(chart, properties, options); + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: resolveLabelElementProperties$1(chart, properties, options), + initProperties: properties.initProperties + }]; + return properties; +} + +function getChartCircle(chart, options) { + const point = getChartPoint(chart, options); + const size = options.radius * 2; + return { + x: point.x - options.radius + options.xAdjust, + y: point.y - options.radius + options.yAdjust, + x2: point.x + options.radius + options.xAdjust, + y2: point.y + options.radius + options.yAdjust, + centerX: point.x + options.xAdjust, + centerY: point.y + options.yAdjust, + radius: options.radius, + width: size, + height: size + }; +} + +function getChartDimensionByScale(scale, options) { + const result = getDimensionByScale(scale, options) || options; + return { + start: Math.min(result.start, result.end), + end: Math.max(result.start, result.end) + }; +} + +function resolveFullLineProperties(scale, area, options) { + const min = scaleValue(scale, options.value, NaN); + const max = scaleValue(scale, options.endValue, min); + if (scale.isHorizontal()) { + area.x = min; + area.x2 = max; + } else { + area.y = min; + area.y2 = max; + } +} + +function resolveLimitedLineProperties(scales, area, options) { + for (const scaleId of Object.keys(limitedLineScale)) { + const scale = scales[retrieveScaleID(scales, options, scaleId)]; + if (scale) { + const {min, max, start, end, startProp, endProp} = limitedLineScale[scaleId]; + const dim = getDimensionByScale(scale, {min: options[min], max: options[max], start: scale[start], end: scale[end]}); + area[startProp] = dim.start; + area[endProp] = dim.end; + } + } +} + +function calculateX({properties, options}, labelSize, position, padding) { + const {x: start, x2: end, width: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.x, + padding: {start: padding.left, end: padding.right}, + adjust: options.label.xAdjust, + size: labelSize.width + }); +} + +function calculateY({properties, options}, labelSize, position, padding) { + const {y: start, y2: end, height: size} = properties; + return calculatePosition({start, end, size, borderWidth: options.borderWidth}, { + position: position.y, + padding: {start: padding.top, end: padding.bottom}, + adjust: options.label.yAdjust, + size: labelSize.height + }); +} + +function calculatePosition(boxOpts, labelOpts) { + const {start, end, borderWidth} = boxOpts; + const {position, padding: {start: padStart, end: padEnd}, adjust} = labelOpts; + const availableSize = end - borderWidth - start - padStart - padEnd - labelOpts.size; + return start + borderWidth / 2 + adjust + getRelativePosition(availableSize, position); +} + +function resolveLabelElementProperties$1(chart, properties, options) { + const label = options.label; + label.backgroundColor = 'transparent'; + label.callout.display = false; + const position = toPosition(label.position); + const padding = toPadding(label.padding); + const labelSize = measureLabelSize(chart.ctx, label); + const x = calculateX({properties, options}, labelSize, position, padding); + const y = calculateY({properties, options}, labelSize, position, padding); + const width = labelSize.width + padding.width; + const height = labelSize.height + padding.height; + return { + x, + y, + x2: x + width, + y2: y + height, + width, + height, + centerX: x + width / 2, + centerY: y + height / 2, + rotation: label.rotation + }; + +} + +const moveHooks = ['enter', 'leave']; + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + */ + +const eventHooks = moveHooks.concat('click'); + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + */ +function updateListeners(chart, state, options) { + state.listened = loadHooks(options, eventHooks, state.listeners); + state.moveListened = false; + + moveHooks.forEach(hook => { + if (isFunction(options[hook])) { + state.moveListened = true; + } + }); + + if (!state.listened || !state.moveListened) { + state.annotations.forEach(scope => { + if (!state.listened && isFunction(scope.click)) { + state.listened = true; + } + if (!state.moveListened) { + moveHooks.forEach(hook => { + if (isFunction(scope[hook])) { + state.listened = true; + state.moveListened = true; + } + }); + } + }); + } +} + +/** + * @param {Object} state + * @param {ChartEvent} event + * @param {AnnotationPluginOptions} options + * @return {boolean|undefined} + */ +function handleEvent(state, event, options) { + if (state.listened) { + switch (event.type) { + case 'mousemove': + case 'mouseout': + return handleMoveEvents(state, event, options); + case 'click': + return handleClickEvents(state, event, options); + } + } +} + +function handleMoveEvents(state, event, options) { + if (!state.moveListened) { + return; + } + + let elements; + + if (event.type === 'mousemove') { + elements = getElements(state.visibleElements, event, options.interaction); + } else { + elements = []; + } + + const previous = state.hovered; + state.hovered = elements; + + const context = {state, event}; + let changed = dispatchMoveEvents(context, 'leave', previous, elements); + return dispatchMoveEvents(context, 'enter', elements, previous) || changed; +} + +function dispatchMoveEvents({state, event}, hook, elements, checkElements) { + let changed; + for (const element of elements) { + if (checkElements.indexOf(element) < 0) { + changed = dispatchEvent(element.options[hook] || state.listeners[hook], element, event) || changed; + } + } + return changed; +} + +function handleClickEvents(state, event, options) { + const listeners = state.listeners; + const elements = getElements(state.visibleElements, event, options.interaction); + let changed; + for (const element of elements) { + changed = dispatchEvent(element.options.click || listeners.click, element, event) || changed; + } + return changed; +} + +function dispatchEvent(handler, element, event) { + return callback(handler, [element.$context, event]) === true; +} + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + * @typedef { import('../../types/element').AnnotationElement } AnnotationElement + */ + +const elementHooks = ['afterDraw', 'beforeDraw']; + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + */ +function updateHooks(chart, state, options) { + const visibleElements = state.visibleElements; + state.hooked = loadHooks(options, elementHooks, state.hooks); + + if (!state.hooked) { + visibleElements.forEach(scope => { + if (!state.hooked) { + elementHooks.forEach(hook => { + if (isFunction(scope.options[hook])) { + state.hooked = true; + } + }); + } + }); + } +} + +/** + * @param {Object} state + * @param {AnnotationElement} element + * @param {string} hook + */ +function invokeHook(state, element, hook) { + if (state.hooked) { + const callbackHook = element.options[hook] || state.hooks[hook]; + return callback(callbackHook, [element.$context]); + } +} + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").Scale } Scale + * @typedef { import('../../types/options').CoreAnnotationOptions } CoreAnnotationOptions + */ + +/** + * @param {Chart} chart + * @param {Scale} scale + * @param {CoreAnnotationOptions[]} annotations + */ +function adjustScaleRange(chart, scale, annotations) { + const range = getScaleLimits(chart.scales, scale, annotations); + let changed = changeScaleLimit(scale, range, 'min', 'suggestedMin'); + changed = changeScaleLimit(scale, range, 'max', 'suggestedMax') || changed; + if (changed && isFunction(scale.handleTickRangeOptions)) { + scale.handleTickRangeOptions(); + } +} + +/** + * @param {CoreAnnotationOptions[]} annotations + * @param {{ [key: string]: Scale }} scales + */ +function verifyScaleOptions(annotations, scales) { + for (const annotation of annotations) { + verifyScaleIDs(annotation, scales); + } +} + +function changeScaleLimit(scale, range, limit, suggestedLimit) { + if (isFinite(range[limit]) && !scaleLimitDefined(scale.options, limit, suggestedLimit)) { + const changed = scale[limit] !== range[limit]; + scale[limit] = range[limit]; + return changed; + } +} + +function scaleLimitDefined(scaleOptions, limit, suggestedLimit) { + return defined(scaleOptions[limit]) || defined(scaleOptions[suggestedLimit]); +} + +function verifyScaleIDs(annotation, scales) { + for (const key of ['scaleID', 'xScaleID', 'yScaleID']) { + const scaleID = retrieveScaleID(scales, annotation, key); + if (scaleID && !scales[scaleID] && verifyProperties(annotation, key)) { + console.warn(`No scale found with id '${scaleID}' for annotation '${annotation.id}'`); + } + } +} + +function verifyProperties(annotation, key) { + if (key === 'scaleID') { + return true; + } + const axis = key.charAt(0); + for (const prop of ['Min', 'Max', 'Value']) { + if (defined(annotation[axis + prop])) { + return true; + } + } + return false; +} + +function getScaleLimits(scales, scale, annotations) { + const axis = scale.axis; + const scaleID = scale.id; + const scaleIDOption = axis + 'ScaleID'; + const limits = { + min: valueOrDefault(scale.min, Number.NEGATIVE_INFINITY), + max: valueOrDefault(scale.max, Number.POSITIVE_INFINITY) + }; + for (const annotation of annotations) { + if (annotation.scaleID === scaleID) { + updateLimits(annotation, scale, ['value', 'endValue'], limits); + } else if (retrieveScaleID(scales, annotation, scaleIDOption) === scaleID) { + updateLimits(annotation, scale, [axis + 'Min', axis + 'Max', axis + 'Value'], limits); + } + } + return limits; +} + +function updateLimits(annotation, scale, props, limits) { + for (const prop of props) { + const raw = annotation[prop]; + if (defined(raw)) { + const value = scale.parse(raw); + limits.min = Math.min(limits.min, value); + limits.max = Math.max(limits.max, value); + } + } +} + +class BoxAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const {x, y} = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), toRadians(-this.options.rotation)); + return inBoxRange({x, y}, this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis, this.options); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + ctx.save(); + translate(ctx, this.getCenterPoint(), this.options.rotation); + drawBox(ctx, this, this.options); + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + return resolveBoxAndLabelProperties(chart, options); + } +} + +BoxAnnotation.id = 'boxAnnotation'; + +BoxAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + init: undefined, + hitTolerance: 0, + label: { + backgroundColor: 'transparent', + borderWidth: 0, + callout: { + display: false + }, + color: 'black', + content: null, + display: false, + drawTime: undefined, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + hitTolerance: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: undefined, + textAlign: 'start', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0, + z: undefined + }, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +BoxAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +BoxAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +class DoughnutLabelAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + return inLabelRange( + {x: mouseX, y: mouseY}, + {rect: this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), center: this.getCenterPoint(useFinalPosition)}, + axis, + {rotation: this.rotation, borderWidth: 0, hitTolerance: this.options.hitTolerance} + ); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + if (!options.display || !options.content) { + return; + } + drawBackground(ctx, this); + ctx.save(); + translate(ctx, this.getCenterPoint(), this.rotation); + drawLabel(ctx, this, options, this._fitRatio); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const meta = getDatasetMeta(chart, options); + if (!meta) { + return {}; + } + const {controllerMeta, point, radius} = getControllerMeta(chart, options, meta); + let labelSize = measureLabelSize(chart.ctx, options); + const _fitRatio = getFitRatio(labelSize, radius); + if (shouldFit(options, _fitRatio)) { + labelSize = {width: labelSize.width * _fitRatio, height: labelSize.height * _fitRatio}; + } + const {position, xAdjust, yAdjust} = options; + const boxSize = measureLabelRectangle(point, labelSize, {borderWidth: 0, position, xAdjust, yAdjust}); + return { + initProperties: initAnimationProperties(chart, boxSize, options), + ...boxSize, + ...controllerMeta, + rotation: options.rotation, + _fitRatio + }; + } +} + +DoughnutLabelAnnotation.id = 'doughnutLabelAnnotation'; + +DoughnutLabelAnnotation.defaults = { + autoFit: true, + autoHide: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 0, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + hitTolerance: 0, + init: undefined, + opacity: undefined, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + spacing: 1, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0 +}; + +DoughnutLabelAnnotation.defaultRoutes = { +}; + +function getDatasetMeta(chart, options) { + return chart.getSortedVisibleDatasetMetas().reduce(function(result, value) { + const controller = value.controller; + if (controller instanceof DoughnutController && + isControllerVisible(chart, options, value.data) && + (!result || controller.innerRadius < result.controller.innerRadius) && + controller.options.circumference >= 90) { + return value; + } + return result; + }, undefined); +} + +function isControllerVisible(chart, options, elements) { + if (!options.autoHide) { + return true; + } + for (let i = 0; i < elements.length; i++) { + if (!elements[i].hidden && chart.getDataVisibility(i)) { + return true; + } + } +} + +function getControllerMeta({chartArea}, options, meta) { + const {left, top, right, bottom} = chartArea; + const {innerRadius, offsetX, offsetY} = meta.controller; + const x = (left + right) / 2 + offsetX; + const y = (top + bottom) / 2 + offsetY; + const square = { + left: Math.max(x - innerRadius, left), + right: Math.min(x + innerRadius, right), + top: Math.max(y - innerRadius, top), + bottom: Math.min(y + innerRadius, bottom) + }; + const point = { + x: (square.left + square.right) / 2, + y: (square.top + square.bottom) / 2 + }; + const space = options.spacing + options.borderWidth / 2; + const _radius = innerRadius - space; + const _counterclockwise = point.y > y; + const side = _counterclockwise ? top + space : bottom - space; + const angles = getAngles(side, x, y, _radius); + const controllerMeta = { + _centerX: x, + _centerY: y, + _radius, + _counterclockwise, + ...angles + }; + return { + controllerMeta, + point, + radius: Math.min(innerRadius, Math.min(square.right - square.left, square.bottom - square.top) / 2) + }; +} + +function getFitRatio({width, height}, radius) { + const hypo = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + return (radius * 2) / hypo; +} + +function getAngles(y, centerX, centerY, radius) { + const yk2 = Math.pow(centerY - y, 2); + const r2 = Math.pow(radius, 2); + const b = centerX * -2; + const c = Math.pow(centerX, 2) + yk2 - r2; + const delta = Math.pow(b, 2) - (4 * c); + if (delta <= 0) { + return { + _startAngle: 0, + _endAngle: TAU + }; + } + const start = (-b - Math.sqrt(delta)) / 2; + const end = (-b + Math.sqrt(delta)) / 2; + return { + _startAngle: getAngleFromPoint({x: centerX, y: centerY}, {x: start, y}).angle, + _endAngle: getAngleFromPoint({x: centerX, y: centerY}, {x: end, y}).angle + }; +} + +function drawBackground(ctx, element) { + const {_centerX, _centerY, _radius, _startAngle, _endAngle, _counterclockwise, options} = element; + ctx.save(); + const stroke = setBorderStyle(ctx, options); + ctx.fillStyle = options.backgroundColor; + ctx.beginPath(); + ctx.arc(_centerX, _centerY, _radius, _startAngle, _endAngle, _counterclockwise); + ctx.closePath(); + ctx.fill(); + if (stroke) { + ctx.stroke(); + } + ctx.restore(); +} + +class LabelAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + return inLabelRange( + {x: mouseX, y: mouseY}, + {rect: this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), center: this.getCenterPoint(useFinalPosition)}, + axis, + {rotation: this.rotation, borderWidth: this.options.borderWidth, hitTolerance: this.options.hitTolerance} + ); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const visible = !defined(this._visible) || this._visible; + if (!options.display || !options.content || !visible) { + return; + } + ctx.save(); + translate(ctx, this.getCenterPoint(), this.rotation); + drawCallout(ctx, this); + drawBox(ctx, this, options); + drawLabel(ctx, getLabelSize(this), options); + ctx.restore(); + } + + resolveElementProperties(chart, options) { + let point; + if (!isBoundToPoint(options)) { + const {centerX, centerY} = resolveBoxProperties(chart, options); + point = {x: centerX, y: centerY}; + } else { + point = getChartPoint(chart, options); + } + const padding = toPadding(options.padding); + const labelSize = measureLabelSize(chart.ctx, options); + const boxSize = measureLabelRectangle(point, labelSize, options, padding); + return { + initProperties: initAnimationProperties(chart, boxSize, options), + pointX: point.x, + pointY: point.y, + ...boxSize, + rotation: options.rotation + }; + } +} + +LabelAnnotation.id = 'labelAnnotation'; + +LabelAnnotation.defaults = { + adjustScaleRange: true, + backgroundColor: 'transparent', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 0, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: { + borderCapStyle: 'butt', + borderColor: undefined, + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 1, + display: false, + margin: 5, + position: 'auto', + side: 5, + start: '50%', + }, + color: 'black', + content: null, + display: true, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: undefined + }, + height: undefined, + hitTolerance: 0, + init: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +LabelAnnotation.defaultRoutes = { + borderColor: 'color' +}; + +function getLabelSize({x, y, width, height, options}) { + const hBorderWidth = options.borderWidth / 2; + const padding = toPadding(options.padding); + return { + x: x + padding.left + hBorderWidth, + y: y + padding.top + hBorderWidth, + width: width - padding.left - padding.right - options.borderWidth, + height: height - padding.top - padding.bottom - options.borderWidth + }; +} + +const pointInLine = (p1, p2, t) => ({x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)}); +const interpolateX = (y, p1, p2) => pointInLine(p1, p2, Math.abs((y - p1.y) / (p2.y - p1.y))).x; +const interpolateY = (x, p1, p2) => pointInLine(p1, p2, Math.abs((x - p1.x) / (p2.x - p1.x))).y; +const sqr = v => v * v; +const rangeLimit = (mouseX, mouseY, {x, y, x2, y2}, axis) => axis === 'y' ? {start: Math.min(y, y2), end: Math.max(y, y2), value: mouseY} : {start: Math.min(x, x2), end: Math.max(x, x2), value: mouseX}; +// http://www.independent-software.com/determining-coordinates-on-a-html-canvas-bezier-curve.html +const coordInCurve = (start, cp, end, t) => (1 - t) * (1 - t) * start + 2 * (1 - t) * t * cp + t * t * end; +const pointInCurve = (start, cp, end, t) => ({x: coordInCurve(start.x, cp.x, end.x, t), y: coordInCurve(start.y, cp.y, end.y, t)}); +const coordAngleInCurve = (start, cp, end, t) => 2 * (1 - t) * (cp - start) + 2 * t * (end - cp); +const angleInCurve = (start, cp, end, t) => -Math.atan2(coordAngleInCurve(start.x, cp.x, end.x, t), coordAngleInCurve(start.y, cp.y, end.y, t)) + 0.5 * PI; + +class LineAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + const point = {mouseX, mouseY}; + const {path, ctx} = this; + if (path) { + setBorderStyle(ctx, this.options); + ctx.lineWidth += this.options.hitTolerance; + const {chart} = this.$context; + const mx = mouseX * chart.currentDevicePixelRatio; + const my = mouseY * chart.currentDevicePixelRatio; + const result = ctx.isPointInStroke(path, mx, my) || isOnLabel(this, point, useFinalPosition); + ctx.restore(); + return result; + } + const epsilon = sqr(hitSize); + return intersects(this, point, epsilon, useFinalPosition) || isOnLabel(this, point, useFinalPosition); + } + return inAxisRange(this, {mouseX, mouseY}, axis, {hitSize, useFinalPosition}); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {x, y, x2, y2, cp, options} = this; + + ctx.save(); + if (!setBorderStyle(ctx, options)) { + // no border width, then line is not drawn + return ctx.restore(); + } + setShadowStyle(ctx, options); + + const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)); + if (options.curve && cp) { + drawCurve(ctx, this, cp, length); + return ctx.restore(); + } + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this); + const angle = Math.atan2(y2 - y, x2 - x); + ctx.translate(x, y); + ctx.rotate(angle); + ctx.beginPath(); + ctx.moveTo(0 + startAdjust, 0); + ctx.lineTo(length - endAdjust, 0); + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + drawArrowHead(ctx, 0, startAdjust, startOpts); + drawArrowHead(ctx, length, -endAdjust, endOpts); + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + const area = resolveLineProperties(chart, options); + const {x, y, x2, y2} = area; + const inside = isLineInArea(area, chart.chartArea); + const properties = inside + ? limitLineToArea({x, y}, {x: x2, y: y2}, chart.chartArea) + : {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; + properties.centerX = (x2 + x) / 2; + properties.centerY = (y2 + y) / 2; + properties.initProperties = initAnimationProperties(chart, properties, options); + if (options.curve) { + const p1 = {x: properties.x, y: properties.y}; + const p2 = {x: properties.x2, y: properties.y2}; + properties.cp = getControlPoint(properties, options, distanceBetweenPoints(p1, p2)); + } + const labelProperties = resolveLabelElementProperties(chart, properties, options.label); + // additonal prop to manage zoom/pan + labelProperties._visible = inside; + + properties.elements = [{ + type: 'label', + optionScope: 'label', + properties: labelProperties, + initProperties: properties.initProperties + }]; + return properties; + } +} + +LineAnnotation.id = 'lineAnnotation'; + +const arrowHeadsDefaults = { + backgroundColor: undefined, + backgroundShadowColor: undefined, + borderColor: undefined, + borderDash: undefined, + borderDashOffset: undefined, + borderShadowColor: undefined, + borderWidth: undefined, + display: undefined, + fill: undefined, + length: undefined, + shadowBlur: undefined, + shadowOffsetX: undefined, + shadowOffsetY: undefined, + width: undefined +}; + +LineAnnotation.defaults = { + adjustScaleRange: true, + arrowHeads: { + display: false, + end: Object.assign({}, arrowHeadsDefaults), + fill: false, + length: 12, + start: Object.assign({}, arrowHeadsDefaults), + width: 6 + }, + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 2, + curve: false, + controlPoint: { + y: '-50%' + }, + display: true, + endValue: undefined, + init: undefined, + hitTolerance: 0, + label: { + backgroundColor: 'rgba(0,0,0,0.8)', + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderColor: 'black', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderRadius: 6, + borderShadowColor: 'transparent', + borderWidth: 0, + callout: Object.assign({}, LabelAnnotation.defaults.callout), + color: '#fff', + content: null, + display: false, + drawTime: undefined, + font: { + family: undefined, + lineHeight: undefined, + size: undefined, + style: undefined, + weight: 'bold' + }, + height: undefined, + hitTolerance: undefined, + opacity: undefined, + padding: 6, + position: 'center', + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + textAlign: 'center', + textStrokeColor: undefined, + textStrokeWidth: 0, + width: undefined, + xAdjust: 0, + yAdjust: 0, + z: undefined + }, + scaleID: undefined, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + value: undefined, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +LineAnnotation.descriptors = { + arrowHeads: { + start: { + _fallback: true + }, + end: { + _fallback: true + }, + _fallback: true + } +}; + +LineAnnotation.defaultRoutes = { + borderColor: 'color' +}; + +function inAxisRange(element, {mouseX, mouseY}, axis, {hitSize, useFinalPosition}) { + const limit = rangeLimit(mouseX, mouseY, element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition), axis); + return inLimit(limit, hitSize) || isOnLabel(element, {mouseX, mouseY}, useFinalPosition, axis); +} + +function isLineInArea({x, y, x2, y2}, {top, right, bottom, left}) { + return !( + (x < left && x2 < left) || + (x > right && x2 > right) || + (y < top && y2 < top) || + (y > bottom && y2 > bottom) + ); +} + +function limitPointToArea({x, y}, p2, {top, right, bottom, left}) { + if (x < left) { + y = interpolateY(left, {x, y}, p2); + x = left; + } + if (x > right) { + y = interpolateY(right, {x, y}, p2); + x = right; + } + if (y < top) { + x = interpolateX(top, {x, y}, p2); + y = top; + } + if (y > bottom) { + x = interpolateX(bottom, {x, y}, p2); + y = bottom; + } + return {x, y}; +} + +function limitLineToArea(p1, p2, area) { + const {x, y} = limitPointToArea(p1, p2, area); + const {x: x2, y: y2} = limitPointToArea(p2, p1, area); + return {x, y, x2, y2, width: Math.abs(x2 - x), height: Math.abs(y2 - y)}; +} + +function intersects(element, {mouseX, mouseY}, epsilon = EPSILON, useFinalPosition) { + // Adapted from https://stackoverflow.com/a/6853926/25507 + const {x: x1, y: y1, x2, y2} = element.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); + const dx = x2 - x1; + const dy = y2 - y1; + const lenSq = sqr(dx) + sqr(dy); + const t = lenSq === 0 ? -1 : ((mouseX - x1) * dx + (mouseY - y1) * dy) / lenSq; + + let xx, yy; + if (t < 0) { + xx = x1; + yy = y1; + } else if (t > 1) { + xx = x2; + yy = y2; + } else { + xx = x1 + t * dx; + yy = y1 + t * dy; + } + return (sqr(mouseX - xx) + sqr(mouseY - yy)) <= epsilon; +} + +function isOnLabel(element, {mouseX, mouseY}, useFinalPosition, axis) { + const label = element.label; + return label.options.display && label.inRange(mouseX, mouseY, axis, useFinalPosition); +} + +function resolveLabelElementProperties(chart, properties, options) { + const borderWidth = options.borderWidth; + const padding = toPadding(options.padding); + const textSize = measureLabelSize(chart.ctx, options); + const width = textSize.width + padding.width + borderWidth; + const height = textSize.height + padding.height + borderWidth; + return calculateLabelPosition(properties, options, {width, height, padding}, chart.chartArea); +} + +function calculateAutoRotation(properties) { + const {x, y, x2, y2} = properties; + const rotation = Math.atan2(y2 - y, x2 - x); + // Flip the rotation if it goes > PI/2 or < -PI/2, so label stays upright + return rotation > PI / 2 ? rotation - PI : rotation < PI / -2 ? rotation + PI : rotation; +} + +function calculateLabelPosition(properties, label, sizes, chartArea) { + const {width, height, padding} = sizes; + const {xAdjust, yAdjust} = label; + const p1 = {x: properties.x, y: properties.y}; + const p2 = {x: properties.x2, y: properties.y2}; + const rotation = label.rotation === 'auto' ? calculateAutoRotation(properties) : toRadians(label.rotation); + const size = rotatedSize(width, height, rotation); + const t = calculateT(properties, label, {labelSize: size, padding}, chartArea); + const pt = properties.cp ? pointInCurve(p1, properties.cp, p2, t) : pointInLine(p1, p2, t); + const xCoordinateSizes = {size: size.w, min: chartArea.left, max: chartArea.right, padding: padding.left}; + const yCoordinateSizes = {size: size.h, min: chartArea.top, max: chartArea.bottom, padding: padding.top}; + const centerX = adjustLabelCoordinate(pt.x, xCoordinateSizes) + xAdjust; + const centerY = adjustLabelCoordinate(pt.y, yCoordinateSizes) + yAdjust; + return { + x: centerX - (width / 2), + y: centerY - (height / 2), + x2: centerX + (width / 2), + y2: centerY + (height / 2), + centerX, + centerY, + pointX: pt.x, + pointY: pt.y, + width, + height, + rotation: toDegrees(rotation) + }; +} + +function rotatedSize(width, height, rotation) { + const cos = Math.cos(rotation); + const sin = Math.sin(rotation); + return { + w: Math.abs(width * cos) + Math.abs(height * sin), + h: Math.abs(width * sin) + Math.abs(height * cos) + }; +} + +function calculateT(properties, label, sizes, chartArea) { + let t; + const space = spaceAround(properties, chartArea); + if (label.position === 'start') { + t = calculateTAdjust({w: properties.x2 - properties.x, h: properties.y2 - properties.y}, sizes, label, space); + } else if (label.position === 'end') { + t = 1 - calculateTAdjust({w: properties.x - properties.x2, h: properties.y - properties.y2}, sizes, label, space); + } else { + t = getRelativePosition(1, label.position); + } + return t; +} + +function calculateTAdjust(lineSize, sizes, label, space) { + const {labelSize, padding} = sizes; + const lineW = lineSize.w * space.dx; + const lineH = lineSize.h * space.dy; + const x = (lineW > 0) && ((labelSize.w / 2 + padding.left - space.x) / lineW); + const y = (lineH > 0) && ((labelSize.h / 2 + padding.top - space.y) / lineH); + return clamp(Math.max(x, y), 0, 0.25); +} + +function spaceAround(properties, chartArea) { + const {x, x2, y, y2} = properties; + const t = Math.min(y, y2) - chartArea.top; + const l = Math.min(x, x2) - chartArea.left; + const b = chartArea.bottom - Math.max(y, y2); + const r = chartArea.right - Math.max(x, x2); + return { + x: Math.min(l, r), + y: Math.min(t, b), + dx: l <= r ? 1 : -1, + dy: t <= b ? 1 : -1 + }; +} + +function adjustLabelCoordinate(coordinate, labelSizes) { + const {size, min, max, padding} = labelSizes; + const halfSize = size / 2; + if (size > max - min) { + // if it does not fit, display as much as possible + return (max + min) / 2; + } + if (min >= (coordinate - padding - halfSize)) { + coordinate = min + padding + halfSize; + } + if (max <= (coordinate + padding + halfSize)) { + coordinate = max - padding - halfSize; + } + return coordinate; +} + +function getArrowHeads(line) { + const options = line.options; + const arrowStartOpts = options.arrowHeads && options.arrowHeads.start; + const arrowEndOpts = options.arrowHeads && options.arrowHeads.end; + return { + startOpts: arrowStartOpts, + endOpts: arrowEndOpts, + startAdjust: getLineAdjust(line, arrowStartOpts), + endAdjust: getLineAdjust(line, arrowEndOpts) + }; +} + +function getLineAdjust(line, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return 0; + } + const {length, width} = arrowOpts; + const adjust = line.options.borderWidth / 2; + const p1 = {x: length, y: width + adjust}; + const p2 = {x: 0, y: adjust}; + return Math.abs(interpolateX(0, p1, p2)); +} + +function drawArrowHead(ctx, offset, adjust, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return; + } + const {length, width, fill, backgroundColor, borderColor} = arrowOpts; + const arrowOffsetX = Math.abs(offset - length) + adjust; + ctx.beginPath(); + setShadowStyle(ctx, arrowOpts); + setBorderStyle(ctx, arrowOpts); + ctx.moveTo(arrowOffsetX, -width); + ctx.lineTo(offset + adjust, 0); + ctx.lineTo(arrowOffsetX, width); + if (fill === true) { + ctx.fillStyle = backgroundColor || borderColor; + ctx.closePath(); + ctx.fill(); + ctx.shadowColor = 'transparent'; + } else { + ctx.shadowColor = arrowOpts.borderShadowColor; + } + ctx.stroke(); +} + +function getControlPoint(properties, options, distance) { + const {x, y, x2, y2, centerX, centerY} = properties; + const angle = Math.atan2(y2 - y, x2 - x); + const cp = toPosition(options.controlPoint, 0); + const point = { + x: centerX + getSize(distance, cp.x, false), + y: centerY + getSize(distance, cp.y, false) + }; + return rotated(point, {x: centerX, y: centerY}, angle); +} + +function drawArrowHeadOnCurve(ctx, {x, y}, {angle, adjust}, arrowOpts) { + if (!arrowOpts || !arrowOpts.display) { + return; + } + ctx.save(); + ctx.translate(x, y); + ctx.rotate(angle); + drawArrowHead(ctx, 0, -adjust, arrowOpts); + ctx.restore(); +} + +function drawCurve(ctx, element, cp, length) { + const {x, y, x2, y2, options} = element; + const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(element); + const p1 = {x, y}; + const p2 = {x: x2, y: y2}; + const startAngle = angleInCurve(p1, cp, p2, 0); + const endAngle = angleInCurve(p1, cp, p2, 1) - PI; + const ps = pointInCurve(p1, cp, p2, startAdjust / length); + const pe = pointInCurve(p1, cp, p2, 1 - endAdjust / length); + + const path = new Path2D(); + ctx.beginPath(); + path.moveTo(ps.x, ps.y); + path.quadraticCurveTo(cp.x, cp.y, pe.x, pe.y); + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(path); + element.path = path; + element.ctx = ctx; + drawArrowHeadOnCurve(ctx, ps, {angle: startAngle, adjust: startAdjust}, startOpts); + drawArrowHeadOnCurve(ctx, pe, {angle: endAngle, adjust: endAdjust}, endOpts); +} + +class EllipseAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const rotation = this.options.rotation; + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + return pointInEllipse({x: mouseX, y: mouseY}, this.getProps(['width', 'height', 'centerX', 'centerY'], useFinalPosition), rotation, hitSize); + } + const {x, y, x2, y2} = this.getProps(['x', 'y', 'x2', 'y2'], useFinalPosition); + const limit = axis === 'y' ? {start: y, end: y2} : {start: x, end: x2}; + const rotatedPoint = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), toRadians(-rotation)); + return rotatedPoint[axis] >= limit.start - hitSize - EPSILON && rotatedPoint[axis] <= limit.end + hitSize + EPSILON; + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {width, height, centerX, centerY, options} = this; + ctx.save(); + translate(ctx, this.getCenterPoint(), options.rotation); + setShadowStyle(ctx, this.options); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + const stroke = setBorderStyle(ctx, options); + ctx.ellipse(centerX, centerY, height / 2, width / 2, PI / 2, 0, 2 * PI); + ctx.fill(); + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + get label() { + return this.elements && this.elements[0]; + } + + resolveElementProperties(chart, options) { + return resolveBoxAndLabelProperties(chart, options); + } + +} + +EllipseAnnotation.id = 'ellipseAnnotation'; + +EllipseAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + label: Object.assign({}, BoxAnnotation.defaults.label), + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + z: 0 +}; + +EllipseAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +EllipseAnnotation.descriptors = { + label: { + _fallback: true + } +}; + +function pointInEllipse(p, ellipse, rotation, hitSize) { + const {width, height, centerX, centerY} = ellipse; + const xRadius = width / 2; + const yRadius = height / 2; + + if (xRadius <= 0 || yRadius <= 0) { + return false; + } + // https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm + const angle = toRadians(rotation || 0); + const cosAngle = Math.cos(angle); + const sinAngle = Math.sin(angle); + const a = Math.pow(cosAngle * (p.x - centerX) + sinAngle * (p.y - centerY), 2); + const b = Math.pow(sinAngle * (p.x - centerX) - cosAngle * (p.y - centerY), 2); + return (a / Math.pow(xRadius + hitSize, 2)) + (b / Math.pow(yRadius + hitSize, 2)) <= 1.0001; +} + +class PointAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + const {x, y, x2, y2, width} = this.getProps(['x', 'y', 'x2', 'y2', 'width'], useFinalPosition); + const hitSize = (this.options.borderWidth + this.options.hitTolerance) / 2; + if (axis !== 'x' && axis !== 'y') { + return inPointRange({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), width / 2, hitSize); + } + const limit = axis === 'y' ? {start: y, end: y2, value: mouseY} : {start: x, end: x2, value: mouseX}; + return inLimit(limit, hitSize); + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const options = this.options; + const borderWidth = options.borderWidth; + if (options.radius < 0.1) { + return; + } + ctx.save(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + drawPoint(ctx, this, this.centerX, this.centerY); + if (stroke && !isImageOrCanvas(options.pointStyle)) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + options.borderWidth = borderWidth; + } + + resolveElementProperties(chart, options) { + const properties = resolvePointProperties(chart, options); + properties.initProperties = initAnimationProperties(chart, properties, options); + return properties; + } +} + +PointAnnotation.id = 'pointAnnotation'; + +PointAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderDash: [], + borderDashOffset: 0, + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + pointStyle: 'circle', + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +PointAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +class PolygonAnnotation extends Element { + + inRange(mouseX, mouseY, axis, useFinalPosition) { + if (axis !== 'x' && axis !== 'y') { + return this.options.radius >= 0.1 && this.elements.length > 1 && pointIsInPolygon(this.elements, mouseX, mouseY, useFinalPosition); + } + const rotatedPoint = rotated({x: mouseX, y: mouseY}, this.getCenterPoint(useFinalPosition), toRadians(-this.options.rotation)); + const axisPoints = this.elements.map((point) => axis === 'y' ? point.bY : point.bX); + const start = Math.min(...axisPoints); + const end = Math.max(...axisPoints); + return rotatedPoint[axis] >= start && rotatedPoint[axis] <= end; + } + + getCenterPoint(useFinalPosition) { + return getElementCenterPoint(this, useFinalPosition); + } + + draw(ctx) { + const {elements, options} = this; + ctx.save(); + ctx.beginPath(); + ctx.fillStyle = options.backgroundColor; + setShadowStyle(ctx, options); + const stroke = setBorderStyle(ctx, options); + let first = true; + for (const el of elements) { + if (first) { + ctx.moveTo(el.x, el.y); + first = false; + } else { + ctx.lineTo(el.x, el.y); + } + } + ctx.closePath(); + ctx.fill(); + // If no border, don't draw it + if (stroke) { + ctx.shadowColor = options.borderShadowColor; + ctx.stroke(); + } + ctx.restore(); + } + + resolveElementProperties(chart, options) { + const properties = resolvePointProperties(chart, options); + const {sides, rotation} = options; + const elements = []; + const angle = (2 * PI) / sides; + let rad = rotation * RAD_PER_DEG; + for (let i = 0; i < sides; i++, rad += angle) { + const elProps = buildPointElement(properties, options, rad); + elProps.initProperties = initAnimationProperties(chart, properties, options); + elements.push(elProps); + } + properties.elements = elements; + return properties; + } +} + +PolygonAnnotation.id = 'polygonAnnotation'; + +PolygonAnnotation.defaults = { + adjustScaleRange: true, + backgroundShadowColor: 'transparent', + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderShadowColor: 'transparent', + borderWidth: 1, + display: true, + hitTolerance: 0, + init: undefined, + point: { + radius: 0 + }, + radius: 10, + rotation: 0, + shadowBlur: 0, + shadowOffsetX: 0, + shadowOffsetY: 0, + sides: 3, + xAdjust: 0, + xMax: undefined, + xMin: undefined, + xScaleID: undefined, + xValue: undefined, + yAdjust: 0, + yMax: undefined, + yMin: undefined, + yScaleID: undefined, + yValue: undefined, + z: 0 +}; + +PolygonAnnotation.defaultRoutes = { + borderColor: 'color', + backgroundColor: 'color' +}; + +function buildPointElement({centerX, centerY}, {radius, borderWidth, hitTolerance}, rad) { + const hitSize = (borderWidth + hitTolerance) / 2; + const sin = Math.sin(rad); + const cos = Math.cos(rad); + const point = {x: centerX + sin * radius, y: centerY - cos * radius}; + return { + type: 'point', + optionScope: 'point', + properties: { + x: point.x, + y: point.y, + centerX: point.x, + centerY: point.y, + bX: centerX + sin * (radius + hitSize), + bY: centerY - cos * (radius + hitSize) + } + }; +} + +function pointIsInPolygon(points, x, y, useFinalPosition) { + let isInside = false; + let A = points[points.length - 1].getProps(['bX', 'bY'], useFinalPosition); + for (const point of points) { + const B = point.getProps(['bX', 'bY'], useFinalPosition); + if ((B.bY > y) !== (A.bY > y) && x < (A.bX - B.bX) * (y - B.bY) / (A.bY - B.bY) + B.bX) { + isInside = !isInside; + } + A = B; + } + return isInside; +} + +const annotationTypes = { + box: BoxAnnotation, + doughnutLabel: DoughnutLabelAnnotation, + ellipse: EllipseAnnotation, + label: LabelAnnotation, + line: LineAnnotation, + point: PointAnnotation, + polygon: PolygonAnnotation +}; + +/** + * Register fallback for annotation elements + * For example lineAnnotation options would be looked through: + * - the annotation object (options.plugins.annotation.annotations[id]) + * - element options (options.elements.lineAnnotation) + * - element defaults (defaults.elements.lineAnnotation) + * - annotation plugin defaults (defaults.plugins.annotation, this is what we are registering here) + */ +Object.keys(annotationTypes).forEach(key => { + defaults.describe(`elements.${annotationTypes[key].id}`, { + _fallback: 'plugins.annotation.common' + }); +}); + +const directUpdater = { + update: Object.assign +}; + +const hooks$1 = eventHooks.concat(elementHooks); +const resolve = (value, optDefs) => isObject(optDefs) ? resolveObj(value, optDefs) : value; + + +/** + * @typedef { import("chart.js").Chart } Chart + * @typedef { import("chart.js").UpdateMode } UpdateMode + * @typedef { import('../../types/options').AnnotationPluginOptions } AnnotationPluginOptions + */ + +/** + * @param {string} prop + * @returns {boolean} + */ +const isIndexable = (prop) => prop === 'color' || prop === 'font'; + +/** + * Resolve the annotation type, checking if is supported. + * @param {string} [type=line] - annotation type + * @returns {string} resolved annotation type + */ +function resolveType(type = 'line') { + if (annotationTypes[type]) { + return type; + } + console.warn(`Unknown annotation type: '${type}', defaulting to 'line'`); + return 'line'; +} + +/** + * @param {Chart} chart + * @param {Object} state + * @param {AnnotationPluginOptions} options + * @param {UpdateMode} mode + */ +function updateElements(chart, state, options, mode) { + const animations = resolveAnimations(chart, options.animations, mode); + + const annotations = state.annotations; + const elements = resyncElements(state.elements, annotations); + + for (let i = 0; i < annotations.length; i++) { + const annotationOptions = annotations[i]; + const element = getOrCreateElement(elements, i, annotationOptions.type); + const resolver = annotationOptions.setContext(getContext(chart, element, elements, annotationOptions)); + const properties = element.resolveElementProperties(chart, resolver); + + properties.skip = toSkip(properties); + + if ('elements' in properties) { + updateSubElements(element, properties.elements, resolver, animations); + // Remove the sub-element definitions from properties, so the actual elements + // are not overwritten by their definitions + delete properties.elements; + } + + if (!defined(element.x)) { + // If the element is newly created, assing the properties directly - to + // make them readily awailable to any scriptable options. If we do not do this, + // the properties retruned by `resolveElementProperties` are available only + // after options resolution. + Object.assign(element, properties); + } + + Object.assign(element, properties.initProperties); + properties.options = resolveAnnotationOptions(resolver); + + animations.update(element, properties); + } +} + +function toSkip(properties) { + return isNaN(properties.x) || isNaN(properties.y); +} + +function resolveAnimations(chart, animOpts, mode) { + if (mode === 'reset' || mode === 'none' || mode === 'resize') { + return directUpdater; + } + return new Animations(chart, animOpts); +} + +function updateSubElements(mainElement, elements, resolver, animations) { + const subElements = mainElement.elements || (mainElement.elements = []); + subElements.length = elements.length; + for (let i = 0; i < elements.length; i++) { + const definition = elements[i]; + const properties = definition.properties; + const subElement = getOrCreateElement(subElements, i, definition.type, definition.initProperties); + const subResolver = resolver[definition.optionScope].override(definition); + properties.options = resolveAnnotationOptions(subResolver); + animations.update(subElement, properties); + } +} + +function getOrCreateElement(elements, index, type, initProperties) { + const elementClass = annotationTypes[resolveType(type)]; + let element = elements[index]; + if (!element || !(element instanceof elementClass)) { + element = elements[index] = new elementClass(); + Object.assign(element, initProperties); + } + return element; +} + +function resolveAnnotationOptions(resolver) { + const elementClass = annotationTypes[resolveType(resolver.type)]; + const result = {}; + result.id = resolver.id; + result.type = resolver.type; + result.drawTime = resolver.drawTime; + Object.assign(result, + resolveObj(resolver, elementClass.defaults), + resolveObj(resolver, elementClass.defaultRoutes)); + for (const hook of hooks$1) { + result[hook] = resolver[hook]; + } + return result; +} + +function resolveObj(resolver, defs) { + const result = {}; + for (const prop of Object.keys(defs)) { + const optDefs = defs[prop]; + const value = resolver[prop]; + if (isIndexable(prop) && isArray(value)) { + result[prop] = value.map((item) => resolve(item, optDefs)); + } else { + result[prop] = resolve(value, optDefs); + } + } + return result; +} + +function getContext(chart, element, elements, annotation) { + return element.$context || (element.$context = Object.assign(Object.create(chart.getContext()), { + element, + get elements() { + return elements.filter((el) => el && el.options); + }, + id: annotation.id, + type: 'annotation' + })); +} + +function resyncElements(elements, annotations) { + const count = annotations.length; + const start = elements.length; + + if (start < count) { + const add = count - start; + elements.splice(start, 0, ...new Array(add)); + } else if (start > count) { + elements.splice(count, start - count); + } + return elements; +} + +var version = "3.1.0"; + +const chartStates = new Map(); +const isNotDoughnutLabel = annotation => annotation.type !== 'doughnutLabel'; +const hooks = eventHooks.concat(elementHooks); + +var annotation = { + id: 'annotation', + + version, + + beforeRegister() { + requireVersion('chart.js', '4.0', Chart.version); + }, + + afterRegister() { + Chart.register(annotationTypes); + }, + + afterUnregister() { + Chart.unregister(annotationTypes); + }, + + beforeInit(chart) { + chartStates.set(chart, { + annotations: [], + elements: [], + visibleElements: [], + listeners: {}, + listened: false, + moveListened: false, + hooks: {}, + hooked: false, + hovered: [] + }); + }, + + beforeUpdate(chart, args, options) { + const state = chartStates.get(chart); + const annotations = state.annotations = []; + + let annotationOptions = options.annotations; + if (isObject(annotationOptions)) { + Object.keys(annotationOptions).forEach(key => { + const value = annotationOptions[key]; + if (isObject(value)) { + value.id = key; + annotations.push(value); + } + }); + } else if (isArray(annotationOptions)) { + annotations.push(...annotationOptions); + } + verifyScaleOptions(annotations.filter(isNotDoughnutLabel), chart.scales); + }, + + afterDataLimits(chart, args) { + const state = chartStates.get(chart); + adjustScaleRange(chart, args.scale, state.annotations.filter(isNotDoughnutLabel).filter(a => a.display && a.adjustScaleRange)); + }, + + afterUpdate(chart, args, options) { + const state = chartStates.get(chart); + updateListeners(chart, state, options); + updateElements(chart, state, options, args.mode); + state.visibleElements = state.elements.filter(el => !el.skip && el.options.display); + updateHooks(chart, state, options); + }, + + beforeDatasetsDraw(chart, _args, options) { + draw(chart, 'beforeDatasetsDraw', options.clip); + }, + + afterDatasetsDraw(chart, _args, options) { + draw(chart, 'afterDatasetsDraw', options.clip); + }, + + beforeDatasetDraw(chart, _args, options) { + draw(chart, _args.index, options.clip); + }, + + beforeDraw(chart, _args, options) { + draw(chart, 'beforeDraw', options.clip); + }, + + afterDraw(chart, _args, options) { + draw(chart, 'afterDraw', options.clip); + }, + + beforeEvent(chart, args, options) { + const state = chartStates.get(chart); + if (handleEvent(state, args.event, options)) { + args.changed = true; + } + }, + + afterDestroy(chart) { + chartStates.delete(chart); + }, + + getAnnotations(chart) { + const state = chartStates.get(chart); + return state ? state.elements : []; + }, + + // only for testing + _getAnnotationElementsAtEventForMode(visibleElements, event, options) { + return getElements(visibleElements, event, options); + }, + + defaults: { + animations: { + numbers: { + properties: ['x', 'y', 'x2', 'y2', 'width', 'height', 'centerX', 'centerY', 'pointX', 'pointY', 'radius'], + type: 'number' + }, + colors: { + properties: ['backgroundColor', 'borderColor'], + type: 'color' + } + }, + clip: true, + interaction: { + mode: undefined, + axis: undefined, + intersect: undefined + }, + common: { + drawTime: 'afterDatasetsDraw', + init: false, + label: { + } + } + }, + + descriptors: { + _indexable: false, + _scriptable: (prop) => !hooks.includes(prop) && prop !== 'init', + annotations: { + _allKeys: false, + _fallback: (prop, opts) => `elements.${annotationTypes[resolveType(opts.type)].id}` + }, + interaction: { + _fallback: true + }, + common: { + label: { + _indexable: isIndexable, + _fallback: true + }, + _indexable: isIndexable + } + }, + + additionalOptionScopes: [''] +}; + +function draw(chart, caller, clip) { + const {ctx, chartArea} = chart; + const state = chartStates.get(chart); + + if (clip) { + clipArea(ctx, chartArea); + } + + const drawableElements = getDrawableElements(state.visibleElements, caller).sort((a, b) => a.element.options.z - b.element.options.z); + for (const item of drawableElements) { + drawElement(ctx, chartArea, state, item); + } + + if (clip) { + unclipArea(ctx); + } +} + +function getDrawableElements(elements, caller) { + const drawableElements = []; + for (const el of elements) { + if (el.options.drawTime === caller) { + drawableElements.push({element: el, main: true}); + } + if (el.elements && el.elements.length) { + for (const sub of el.elements) { + if (sub.options.display && sub.options.drawTime === caller) { + drawableElements.push({element: sub}); + } + } + } + } + return drawableElements; +} + +function drawElement(ctx, chartArea, state, item) { + const el = item.element; + if (item.main) { + invokeHook(state, el, 'beforeDraw'); + el.draw(ctx, chartArea); + invokeHook(state, el, 'afterDraw'); + } else { + el.draw(ctx, chartArea); + } +} + +export { annotation as default }; diff --git a/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js new file mode 100644 index 0000000..2ee2327 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/dist/chartjs-plugin-annotation.min.js @@ -0,0 +1,7 @@ +/*! +* chartjs-plugin-annotation v3.1.0 +* https://www.chartjs.org/chartjs-plugin-annotation/index + * (c) 2024 chartjs-plugin-annotation Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],e):(t="undefined"!=typeof globalThis?globalThis:t||self)["chartjs-plugin-annotation"]=e(t.Chart,t.Chart.helpers)}(this,(function(t,e){"use strict";const o={modes:{point:(t,e)=>i(t,e,{intersect:!0}),nearest:(t,o,n)=>function(t,o,n){let r=Number.POSITIVE_INFINITY;return i(t,o,n).reduce(((t,i)=>{const s=i.getCenterPoint(),a=function(t,e,o){if("x"===o)return{x:t.x,y:e.y};if("y"===o)return{x:e.x,y:t.y};return e}(o,s,n.axis),d=e.distanceBetweenPoints(o,a);return dt._index-e._index)).slice(0,1)}(t,o,n),x:(t,e,o)=>i(t,e,{intersect:o.intersect,axis:"x"}),y:(t,e,o)=>i(t,e,{intersect:o.intersect,axis:"y"})}};function n(t,e,n){return(o.modes[n.mode]||o.modes.nearest)(t,e,n)}function i(t,e,o){return t.filter((t=>o.intersect?t.inRange(e.x,e.y):function(t,e,o){return"x"!==o&&"y"!==o?t.inRange(e.x,e.y,"x",!0)||t.inRange(e.x,e.y,"y",!0):t.inRange(e.x,e.y,o,!0)}(t,e,o.axis)))}function r(t,e,o){const n=Math.cos(o),i=Math.sin(o),r=e.x,s=e.y;return{x:r+n*(t.x-r)-i*(t.y-s),y:s+i*(t.x-r)+n*(t.y-s)}}const s=(t,e)=>e>t||t.length>e.length&&t.slice(0,e.length)===e,a=.001,d=(t,e,o)=>Math.min(o,Math.max(e,t)),l=(t,e)=>t.value>=t.start-e&&t.value<=t.end+e;function c(t,e,o){for(const n of Object.keys(t))t[n]=d(t[n],e,o);return t}function h(t,{x:e,y:o,x2:n,y2:i},r,{borderWidth:s,hitTolerance:d}){const l=(s+d)/2,c=t.x>=e-l-a&&t.x<=n+l+a,h=t.y>=o-l-a&&t.y<=i+l+a;return"x"===r?c:("y"===r||c)&&h}function u(t,{rect:o,center:n},i,{rotation:s,borderWidth:a,hitTolerance:d}){return h(r(t,n,e.toRadians(-s)),o,i,{borderWidth:a,hitTolerance:d})}function f(t,e){const{centerX:o,centerY:n}=t.getProps(["centerX","centerY"],e);return{x:o,y:n}}const x=t=>"string"==typeof t&&t.endsWith("%"),y=t=>parseFloat(t)/100,p=t=>d(y(t),0,1),b=(t,e)=>({x:t,y:e,x2:t,y2:e,width:0,height:0}),g={box:t=>b(t.centerX,t.centerY),doughnutLabel:t=>b(t.centerX,t.centerY),ellipse:t=>({centerX:t.centerX,centerY:t.centerX,radius:0,width:0,height:0}),label:t=>b(t.centerX,t.centerY),line:t=>b(t.x,t.y),point:t=>({centerX:t.centerX,centerY:t.centerY,radius:0,width:0,height:0}),polygon:t=>b(t.centerX,t.centerY)};function m(t,e){return"start"===e?0:"end"===e?t:x(e)?p(e)*t:t/2}function v(t,e,o=!0){return"number"==typeof e?e:x(e)?(o?p(e):y(e))*t:t}function w(t,o,{borderWidth:n,position:i,xAdjust:r,yAdjust:s},a){const d=e.isObject(a),l=o.width+(d?a.width:0)+n,c=o.height+(d?a.height:0)+n,h=M(i),u=k(t.x,l,r,h.x),f=k(t.y,c,s,h.y);return{x:u,y:f,x2:u+l,y2:f+c,width:l,height:c,centerX:u+l/2,centerY:f+c/2}}function M(t,o="center"){return e.isObject(t)?{x:e.valueOrDefault(t.x,o),y:e.valueOrDefault(t.y,o)}:{x:t=e.valueOrDefault(t,o),y:t}}const P=(t,e)=>t&&t.autoFit&&e<1;function S(t,o){const n=t.font,i=e.isArray(n)?n:[n];return P(t,o)?i.map((function(t){const n=e.toFont(t);return n.size=Math.floor(t.size*o),n.lineHeight=t.lineHeight,e.toFont(n)})):i.map((t=>e.toFont(t)))}function C(t){return t&&(e.defined(t.xValue)||e.defined(t.yValue))}function k(t,e,o=0,n){return t-m(e,n)+o}function A(t,o,n){const i=n.init;if(i)return!0===i?D(o,n):function(t,o,n){const i=e.callback(n.init,[{chart:t,properties:o,options:n}]);if(!0===i)return D(o,n);if(e.isObject(i))return i}(t,o,n)}function T(t,o,n){let i=!1;return o.forEach((o=>{e.isFunction(t[o])?(i=!0,n[o]=t[o]):e.defined(n[o])&&delete n[o]})),i}function D(t,e){const o=e.type||"line";return g[o](t)}const j=new Map,O=t=>isNaN(t)||t<=0,R=t=>t.reduce((function(t,e){return t+=e.string}),"");function I(t){if(t&&"object"==typeof t){const e=t.toString();return"[object HTMLImageElement]"===e||"[object HTMLCanvasElement]"===e}}function Y(t,{x:o,y:n},i){i&&(t.translate(o,n),t.rotate(e.toRadians(i)),t.translate(-o,-n))}function X(t,e){if(e&&e.borderWidth)return t.lineCap=e.borderCapStyle||"butt",t.setLineDash(e.borderDash),t.lineDashOffset=e.borderDashOffset,t.lineJoin=e.borderJoinStyle||"miter",t.lineWidth=e.borderWidth,t.strokeStyle=e.borderColor,!0}function _(t,e){t.shadowColor=e.backgroundShadowColor,t.shadowBlur=e.shadowBlur,t.shadowOffsetX=e.shadowOffsetX,t.shadowOffsetY=e.shadowOffsetY}function E(t,o){const n=o.content;if(I(n)){return{width:v(n.width,o.width),height:v(n.height,o.height)}}const i=S(o),r=o.textStrokeWidth,s=e.isArray(n)?n:[n],a=s.join()+R(i)+r+(t._measureText?"-spriting":"");return j.has(a)||j.set(a,function(t,e,o,n){t.save();const i=e.length;let r=0,s=n;for(let a=0;a0)return t.lineJoin="round",t.miterLimit=2,t.lineWidth=e.textStrokeWidth,t.strokeStyle=e.textStrokeColor,!0}(t,n)&&function(t,{x:e,y:o},n,i){t.beginPath();let r=0;n.forEach((function(n,s){const a=i[Math.min(s,i.length-1)],d=a.lineHeight;t.font=a.string,t.strokeText(n,e,o+d/2+r),r+=d})),t.stroke()}(t,{x:h,y:u},s,a),function(t,{x:e,y:o},n,{fonts:i,colors:r}){let s=0;n.forEach((function(n,a){const d=r[Math.min(a,r.length-1)],l=i[Math.min(a,i.length-1)],c=l.lineHeight;t.beginPath(),t.font=l.string,t.fillStyle=d,t.fillText(n,e,o+c/2+s),s+=c,t.fill()}))}(t,{x:h,y:u},s,{fonts:a,colors:c}),t.restore()}function F(t,o,n,i){const{radius:r,options:s}=o,a=s.pointStyle,d=s.rotation;let l=(d||0)*e.RAD_PER_DEG;if(I(a))return t.save(),t.translate(n,i),t.rotate(l),t.drawImage(a,-a.width/2,-a.height/2,a.width,a.height),void t.restore();O(r)||function(t,{x:o,y:n,radius:i,rotation:r,style:s,rad:a}){let d,l,c,h;switch(t.beginPath(),s){default:t.arc(o,n,i,0,e.TAU),t.closePath();break;case"triangle":t.moveTo(o+Math.sin(a)*i,n-Math.cos(a)*i),a+=e.TWO_THIRDS_PI,t.lineTo(o+Math.sin(a)*i,n-Math.cos(a)*i),a+=e.TWO_THIRDS_PI,t.lineTo(o+Math.sin(a)*i,n-Math.cos(a)*i),t.closePath();break;case"rectRounded":h=.516*i,c=i-h,d=Math.cos(a+e.QUARTER_PI)*c,l=Math.sin(a+e.QUARTER_PI)*c,t.arc(o-d,n-l,h,a-e.PI,a-e.HALF_PI),t.arc(o+l,n-d,h,a-e.HALF_PI,a),t.arc(o+d,n+l,h,a,a+e.HALF_PI),t.arc(o-l,n+d,h,a+e.HALF_PI,a+e.PI),t.closePath();break;case"rect":if(!r){c=Math.SQRT1_2*i,t.rect(o-c,n-c,2*c,2*c);break}a+=e.QUARTER_PI;case"rectRot":d=Math.cos(a)*i,l=Math.sin(a)*i,t.moveTo(o-d,n-l),t.lineTo(o+l,n-d),t.lineTo(o+d,n+l),t.lineTo(o-l,n+d),t.closePath();break;case"crossRot":a+=e.QUARTER_PI;case"cross":d=Math.cos(a)*i,l=Math.sin(a)*i,t.moveTo(o-d,n-l),t.lineTo(o+d,n+l),t.moveTo(o+l,n-d),t.lineTo(o-l,n+d);break;case"star":d=Math.cos(a)*i,l=Math.sin(a)*i,t.moveTo(o-d,n-l),t.lineTo(o+d,n+l),t.moveTo(o+l,n-d),t.lineTo(o-l,n+d),a+=e.QUARTER_PI,d=Math.cos(a)*i,l=Math.sin(a)*i,t.moveTo(o-d,n-l),t.lineTo(o+d,n+l),t.moveTo(o+l,n-d),t.lineTo(o-l,n+d);break;case"line":d=Math.cos(a)*i,l=Math.sin(a)*i,t.moveTo(o-d,n-l),t.lineTo(o+d,n+l);break;case"dash":t.moveTo(o,n),t.lineTo(o+Math.cos(a)*i,n+Math.sin(a)*i)}t.fill()}(t,{x:n,y:i,radius:r,rotation:d,style:a,rad:l})}const H=["left","bottom","top","right"];function N(t,o){const{pointX:n,pointY:i,options:s}=o,a=s.callout,d=a&&a.display&&function(t,o){const n=o.position;if(H.includes(n))return n;return function(t,o){const{x:n,y:i,x2:s,y2:a,width:d,height:l,pointX:c,pointY:h,centerX:u,centerY:f,rotation:x}=t,y={x:u,y:f},p=o.start,b=v(d,p),g=v(l,p),m=[n,n+b,n+b,s],w=[i+g,a,i,a],M=[];for(let t=0;t<4;t++){const o=r({x:m[t],y:w[t]},y,e.toRadians(x));M.push({position:H[t],distance:e.distanceBetweenPoints(o,{x:c,y:h})})}return M.sort(((t,e)=>t.distance-e.distance))[0].position}(t,o)}(o,a);if(!d||function(t,e,o){const{pointX:n,pointY:i}=t,r=e.margin;let s=n,a=i;"left"===o?s+=r:"right"===o?s-=r:"top"===o?a+=r:"bottom"===o&&(a-=r);return t.inRange(s,a)}(o,a,d))return;t.save(),t.beginPath();if(!X(t,a))return t.restore();const{separatorStart:l,separatorEnd:c}=function(t,e){const{x:o,y:n,x2:i,y2:r}=t,s=function(t,e){const{width:o,height:n,options:i}=t,r=i.callout.margin+i.borderWidth/2;if("right"===e)return o+r;if("bottom"===e)return n+r;return-r}(t,e);let a,d;"left"===e||"right"===e?(a={x:o+s,y:n},d={x:a.x,y:r}):(a={x:o,y:n+s},d={x:i,y:a.y});return{separatorStart:a,separatorEnd:d}}(o,d),{sideStart:h,sideEnd:u}=function(t,e,o){const{y:n,width:i,height:r,options:s}=t,a=s.callout.start,d=function(t,e){const o=e.side;if("left"===t||"top"===t)return-o;return o}(e,s.callout);let l,c;"left"===e||"right"===e?(l={x:o.x,y:n+v(r,a)},c={x:l.x+d,y:l.y}):(l={x:o.x+v(i,a),y:o.y},c={x:l.x,y:l.y+d});return{sideStart:l,sideEnd:c}}(o,d,l);(a.margin>0||0===s.borderWidth)&&(t.moveTo(l.x,l.y),t.lineTo(c.x,c.y)),t.moveTo(h.x,h.y),t.lineTo(u.x,u.y);const f=r({x:n,y:i},o.getCenterPoint(),e.toRadians(-o.rotation));t.lineTo(f.x,f.y),t.stroke(),t.restore()}const L={xScaleID:{min:"xMin",max:"xMax",start:"left",end:"right",startProp:"x",endProp:"x2"},yScaleID:{min:"yMin",max:"yMax",start:"bottom",end:"top",startProp:"y",endProp:"y2"}};function V(t,o,n){return o="number"==typeof o?o:t.parse(o),e.isFinite(o)?t.getPixelForValue(o):n}function B(t,e,o){const n=e[o];if(n||"scaleID"===o)return n;const i=o.charAt(0),r=Object.values(t).filter((t=>t.axis&&t.axis===i));return r.length?r[0].id:i}function $(t,e){if(t){const o=t.options.reverse;return{start:V(t,e.min,o?e.end:e.start),end:V(t,e.max,o?e.start:e.end)}}}function U(t,e){const{chartArea:o,scales:n}=t,i=n[B(n,e,"xScaleID")],r=n[B(n,e,"yScaleID")];let s=o.width/2,a=o.height/2;return i&&(s=V(i,e.xValue,i.left+i.width/2)),r&&(a=V(r,e.yValue,r.top+r.height/2)),{x:s,y:a}}function J(t,e){const o=t.scales,n=o[B(o,e,"xScaleID")],i=o[B(o,e,"yScaleID")];if(!n&&!i)return{};let{left:r,right:s}=n||t.chartArea,{top:a,bottom:d}=i||t.chartArea;const l=K(n,{min:e.xMin,max:e.xMax,start:r,end:s});r=l.start,s=l.end;const c=K(i,{min:e.yMin,max:e.yMax,start:d,end:a});return a=c.start,d=c.end,{x:r,y:a,x2:s,y2:d,width:s-r,height:d-a,centerX:r+(s-r)/2,centerY:a+(d-a)/2}}function q(t,e){if(!C(e)){const o=J(t,e);let n=e.radius;n&&!isNaN(n)||(n=Math.min(o.width,o.height)/2,e.radius=n);const i=2*n,r=o.centerX+e.xAdjust,s=o.centerY+e.yAdjust;return{x:r-n,y:s-n,x2:r+n,y2:s+n,centerX:r,centerY:s,width:i,height:i,radius:n}}return function(t,e){const o=U(t,e),n=2*e.radius;return{x:o.x-e.radius+e.xAdjust,y:o.y-e.radius+e.yAdjust,x2:o.x+e.radius+e.xAdjust,y2:o.y+e.radius+e.yAdjust,centerX:o.x+e.xAdjust,centerY:o.y+e.yAdjust,radius:e.radius,width:n,height:n}}(t,e)}function Q(t,e){const{scales:o,chartArea:n}=t,i=o[e.scaleID],r={x:n.left,y:n.top,x2:n.right,y2:n.bottom};return i?function(t,e,o){const n=V(t,o.value,NaN),i=V(t,o.endValue,n);t.isHorizontal()?(e.x=n,e.x2=i):(e.y=n,e.y2=i)}(i,r,e):function(t,e,o){for(const n of Object.keys(L)){const i=t[B(t,o,n)];if(i){const{min:t,max:r,start:s,end:a,startProp:d,endProp:l}=L[n],c=$(i,{min:o[t],max:o[r],start:i[s],end:i[a]});e[d]=c.start,e[l]=c.end}}}(o,r,e),r}function G(t,e){const o=J(t,e);return o.initProperties=A(t,o,e),o.elements=[{type:"label",optionScope:"label",properties:tt(t,o,e),initProperties:o.initProperties}],o}function K(t,e){const o=$(t,e)||e;return{start:Math.min(o.start,o.end),end:Math.max(o.start,o.end)}}function Z(t,e){const{start:o,end:n,borderWidth:i}=t,{position:r,padding:{start:s,end:a},adjust:d}=e;return o+i/2+d+m(n-i-o-s-a-e.size,r)}function tt(t,o,n){const i=n.label;i.backgroundColor="transparent",i.callout.display=!1;const r=M(i.position),s=e.toPadding(i.padding),a=E(t.ctx,i),d=function({properties:t,options:e},o,n,i){const{x:r,x2:s,width:a}=t;return Z({start:r,end:s,size:a,borderWidth:e.borderWidth},{position:n.x,padding:{start:i.left,end:i.right},adjust:e.label.xAdjust,size:o.width})}({properties:o,options:n},a,r,s),l=function({properties:t,options:e},o,n,i){const{y:r,y2:s,height:a}=t;return Z({start:r,end:s,size:a,borderWidth:e.borderWidth},{position:n.y,padding:{start:i.top,end:i.bottom},adjust:e.label.yAdjust,size:o.height})}({properties:o,options:n},a,r,s),c=a.width+s.width,h=a.height+s.height;return{x:d,y:l,x2:d+c,y2:l+h,width:c,height:h,centerX:d+c/2,centerY:l+h/2,rotation:i.rotation}}const et=["enter","leave"],ot=et.concat("click");function nt(t,e,o){if(t.listened)switch(e.type){case"mousemove":case"mouseout":return function(t,e,o){if(!t.moveListened)return;let i;i="mousemove"===e.type?n(t.visibleElements,e,o.interaction):[];const r=t.hovered;t.hovered=i;const s={state:t,event:e};let a=it(s,"leave",r,i);return it(s,"enter",i,r)||a}(t,e,o);case"click":return function(t,e,o){const i=t.listeners,r=n(t.visibleElements,e,o.interaction);let s;for(const t of r)s=rt(t.options.click||i.click,t,e)||s;return s}(t,e,o)}}function it({state:t,event:e},o,n,i){let r;for(const s of n)i.indexOf(s)<0&&(r=rt(s.options[o]||t.listeners[o],s,e)||r);return r}function rt(t,o,n){return!0===e.callback(t,[o.$context,n])}const st=["afterDraw","beforeDraw"];function at(t,o,n){if(t.hooked){const i=o.options[n]||t.hooks[n];return e.callback(i,[o.$context])}}function dt(t,o,n){const i=function(t,o,n){const i=o.axis,r=o.id,s=i+"ScaleID",a={min:e.valueOrDefault(o.min,Number.NEGATIVE_INFINITY),max:e.valueOrDefault(o.max,Number.POSITIVE_INFINITY)};for(const e of n)e.scaleID===r?ut(e,o,["value","endValue"],a):B(t,e,s)===r&&ut(e,o,[i+"Min",i+"Max",i+"Value"],a);return a}(t.scales,o,n);let r=lt(o,i,"min","suggestedMin");r=lt(o,i,"max","suggestedMax")||r,r&&e.isFunction(o.handleTickRangeOptions)&&o.handleTickRangeOptions()}function lt(t,o,n,i){if(e.isFinite(o[n])&&!function(t,o,n){return e.defined(t[o])||e.defined(t[n])}(t.options,n,i)){const e=t[n]!==o[n];return t[n]=o[n],e}}function ct(t,e){for(const o of["scaleID","xScaleID","yScaleID"]){const n=B(e,t,o);n&&!e[n]&&ht(t,o)&&console.warn(`No scale found with id '${n}' for annotation '${t.id}'`)}}function ht(t,o){if("scaleID"===o)return!0;const n=o.charAt(0);for(const o of["Min","Max","Value"])if(e.defined(t[n+o]))return!0;return!1}function ut(t,o,n,i){for(const r of n){const n=t[r];if(e.defined(n)){const t=o.parse(n);i.min=Math.min(i.min,t),i.max=Math.max(i.max,t)}}}class ft extends t.Element{inRange(t,o,n,i){const{x:s,y:a}=r({x:t,y:o},this.getCenterPoint(i),e.toRadians(-this.options.rotation));return h({x:s,y:a},this.getProps(["x","y","x2","y2"],i),n,this.options)}getCenterPoint(t){return f(this,t)}draw(t){t.save(),Y(t,this.getCenterPoint(),this.options.rotation),W(t,this,this.options),t.restore()}get label(){return this.elements&&this.elements[0]}resolveElementProperties(t,e){return G(t,e)}}ft.id="boxAnnotation",ft.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:0,borderShadowColor:"transparent",borderWidth:1,display:!0,init:void 0,hitTolerance:0,label:{backgroundColor:"transparent",borderWidth:0,callout:{display:!1},color:"black",content:null,display:!1,drawTime:void 0,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:"bold"},height:void 0,hitTolerance:void 0,opacity:void 0,padding:6,position:"center",rotation:void 0,textAlign:"start",textStrokeColor:void 0,textStrokeWidth:0,width:void 0,xAdjust:0,yAdjust:0,z:void 0},rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xMax:void 0,xMin:void 0,xScaleID:void 0,yMax:void 0,yMin:void 0,yScaleID:void 0,z:0},ft.defaultRoutes={borderColor:"color",backgroundColor:"color"},ft.descriptors={label:{_fallback:!0}};class xt extends t.Element{inRange(t,e,o,n){return u({x:t,y:e},{rect:this.getProps(["x","y","x2","y2"],n),center:this.getCenterPoint(n)},o,{rotation:this.rotation,borderWidth:0,hitTolerance:this.options.hitTolerance})}getCenterPoint(t){return f(this,t)}draw(t){const e=this.options;e.display&&e.content&&(!function(t,e){const{_centerX:o,_centerY:n,_radius:i,_startAngle:r,_endAngle:s,_counterclockwise:a,options:d}=e;t.save();const l=X(t,d);t.fillStyle=d.backgroundColor,t.beginPath(),t.arc(o,n,i,r,s,a),t.closePath(),t.fill(),l&&t.stroke();t.restore()}(t,this),t.save(),Y(t,this.getCenterPoint(),this.rotation),z(t,this,e,this._fitRatio),t.restore())}resolveElementProperties(o,n){const i=function(e,o){return e.getSortedVisibleDatasetMetas().reduce((function(n,i){const r=i.controller;return r instanceof t.DoughnutController&&function(t,e,o){if(!e.autoHide)return!0;for(let e=0;e=90?i:n}),void 0)}(o,n);if(!i)return{};const{controllerMeta:r,point:s,radius:a}=function({chartArea:t},o,n){const{left:i,top:r,right:s,bottom:a}=t,{innerRadius:d,offsetX:l,offsetY:c}=n.controller,h=(i+s)/2+l,u=(r+a)/2+c,f={left:Math.max(h-d,i),right:Math.min(h+d,s),top:Math.max(u-d,r),bottom:Math.min(u+d,a)},x={x:(f.left+f.right)/2,y:(f.top+f.bottom)/2},y=o.spacing+o.borderWidth/2,p=d-y,b=x.y>u,g=function(t,o,n,i){const r=Math.pow(n-t,2),s=Math.pow(i,2),a=-2*o,d=Math.pow(o,2)+r-s,l=Math.pow(a,2)-4*d;if(l<=0)return{_startAngle:0,_endAngle:e.TAU};const c=(-a-Math.sqrt(l))/2,h=(-a+Math.sqrt(l))/2;return{_startAngle:e.getAngleFromPoint({x:o,y:n},{x:c,y:t}).angle,_endAngle:e.getAngleFromPoint({x:o,y:n},{x:h,y:t}).angle}}(b?r+y:a-y,h,u,p),m={_centerX:h,_centerY:u,_radius:p,_counterclockwise:b,...g};return{controllerMeta:m,point:x,radius:Math.min(d,Math.min(f.right-f.left,f.bottom-f.top)/2)}}(o,n,i);let d=E(o.ctx,n);const l=function({width:t,height:e},o){const n=Math.sqrt(Math.pow(t,2)+Math.pow(e,2));return 2*o/n}(d,a);P(n,l)&&(d={width:d.width*l,height:d.height*l});const{position:c,xAdjust:h,yAdjust:u}=n,f=w(s,d,{borderWidth:0,position:c,xAdjust:h,yAdjust:u});return{initProperties:A(o,f,n),...f,...r,rotation:n.rotation,_fitRatio:l}}}xt.id="doughnutLabelAnnotation",xt.defaults={autoFit:!0,autoHide:!0,backgroundColor:"transparent",backgroundShadowColor:"transparent",borderColor:"transparent",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderShadowColor:"transparent",borderWidth:0,color:"black",content:null,display:!0,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:void 0},height:void 0,hitTolerance:0,init:void 0,opacity:void 0,position:"center",rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,spacing:1,textAlign:"center",textStrokeColor:void 0,textStrokeWidth:0,width:void 0,xAdjust:0,yAdjust:0},xt.defaultRoutes={};class yt extends t.Element{inRange(t,e,o,n){return u({x:t,y:e},{rect:this.getProps(["x","y","x2","y2"],n),center:this.getCenterPoint(n)},o,{rotation:this.rotation,borderWidth:this.options.borderWidth,hitTolerance:this.options.hitTolerance})}getCenterPoint(t){return f(this,t)}draw(t){const o=this.options,n=!e.defined(this._visible)||this._visible;o.display&&o.content&&n&&(t.save(),Y(t,this.getCenterPoint(),this.rotation),N(t,this),W(t,this,o),z(t,function({x:t,y:o,width:n,height:i,options:r}){const s=r.borderWidth/2,a=e.toPadding(r.padding);return{x:t+a.left+s,y:o+a.top+s,width:n-a.left-a.right-r.borderWidth,height:i-a.top-a.bottom-r.borderWidth}}(this),o),t.restore())}resolveElementProperties(t,o){let n;if(C(o))n=U(t,o);else{const{centerX:e,centerY:i}=J(t,o);n={x:e,y:i}}const i=e.toPadding(o.padding),r=w(n,E(t.ctx,o),o,i);return{initProperties:A(t,r,o),pointX:n.x,pointY:n.y,...r,rotation:o.rotation}}}yt.id="labelAnnotation",yt.defaults={adjustScaleRange:!0,backgroundColor:"transparent",backgroundShadowColor:"transparent",borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:0,borderShadowColor:"transparent",borderWidth:0,callout:{borderCapStyle:"butt",borderColor:void 0,borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:1,display:!1,margin:5,position:"auto",side:5,start:"50%"},color:"black",content:null,display:!0,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:void 0},height:void 0,hitTolerance:0,init:void 0,opacity:void 0,padding:6,position:"center",rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,textAlign:"center",textStrokeColor:void 0,textStrokeWidth:0,width:void 0,xAdjust:0,xMax:void 0,xMin:void 0,xScaleID:void 0,xValue:void 0,yAdjust:0,yMax:void 0,yMin:void 0,yScaleID:void 0,yValue:void 0,z:0},yt.defaultRoutes={borderColor:"color"};const pt=(t,e,o)=>({x:t.x+o*(e.x-t.x),y:t.y+o*(e.y-t.y)}),bt=(t,e,o)=>pt(e,o,Math.abs((t-e.y)/(o.y-e.y))).x,gt=(t,e,o)=>pt(e,o,Math.abs((t-e.x)/(o.x-e.x))).y,mt=t=>t*t,vt=(t,e,{x:o,y:n,x2:i,y2:r},s)=>"y"===s?{start:Math.min(n,r),end:Math.max(n,r),value:e}:{start:Math.min(o,i),end:Math.max(o,i),value:t},wt=(t,e,o,n)=>(1-n)*(1-n)*t+2*(1-n)*n*e+n*n*o,Mt=(t,e,o,n)=>({x:wt(t.x,e.x,o.x,n),y:wt(t.y,e.y,o.y,n)}),Pt=(t,e,o,n)=>2*(1-n)*(e-t)+2*n*(o-e),St=(t,o,n,i)=>-Math.atan2(Pt(t.x,o.x,n.x,i),Pt(t.y,o.y,n.y,i))+.5*e.PI;class Ct extends t.Element{inRange(t,e,o,n){const i=(this.options.borderWidth+this.options.hitTolerance)/2;if("x"!==o&&"y"!==o){const o={mouseX:t,mouseY:e},{path:r,ctx:s}=this;if(r){X(s,this.options),s.lineWidth+=this.options.hitTolerance;const{chart:i}=this.$context,a=t*i.currentDevicePixelRatio,d=e*i.currentDevicePixelRatio,l=s.isPointInStroke(r,a,d)||Tt(this,o,n);return s.restore(),l}return function(t,{mouseX:e,mouseY:o},n=a,i){const{x:r,y:s,x2:d,y2:l}=t.getProps(["x","y","x2","y2"],i),c=d-r,h=l-s,u=mt(c)+mt(h),f=0===u?-1:((e-r)*c+(o-s)*h)/u;let x,y;f<0?(x=r,y=s):f>1?(x=d,y=l):(x=r+f*c,y=s+f*h);return mt(e-x)+mt(o-y)<=n}(this,o,mt(i),n)||Tt(this,o,n)}return function(t,{mouseX:e,mouseY:o},n,{hitSize:i,useFinalPosition:r}){const s=vt(e,o,t.getProps(["x","y","x2","y2"],r),n);return l(s,i)||Tt(t,{mouseX:e,mouseY:o},r,n)}(this,{mouseX:t,mouseY:e},o,{hitSize:i,useFinalPosition:n})}getCenterPoint(t){return f(this,t)}draw(t){const{x:o,y:n,x2:i,y2:r,cp:s,options:a}=this;if(t.save(),!X(t,a))return t.restore();_(t,a);const d=Math.sqrt(Math.pow(i-o,2)+Math.pow(r-n,2));if(a.curve&&s)return function(t,o,n,i){const{x:r,y:s,x2:a,y2:d,options:l}=o,{startOpts:c,endOpts:h,startAdjust:u,endAdjust:f}=Ot(o),x={x:r,y:s},y={x:a,y:d},p=St(x,n,y,0),b=St(x,n,y,1)-e.PI,g=Mt(x,n,y,u/i),m=Mt(x,n,y,1-f/i),v=new Path2D;t.beginPath(),v.moveTo(g.x,g.y),v.quadraticCurveTo(n.x,n.y,m.x,m.y),t.shadowColor=l.borderShadowColor,t.stroke(v),o.path=v,o.ctx=t,Yt(t,g,{angle:p,adjust:u},c),Yt(t,m,{angle:b,adjust:f},h)}(t,this,s,d),t.restore();const{startOpts:l,endOpts:c,startAdjust:h,endAdjust:u}=Ot(this),f=Math.atan2(r-n,i-o);t.translate(o,n),t.rotate(f),t.beginPath(),t.moveTo(0+h,0),t.lineTo(d-u,0),t.shadowColor=a.borderShadowColor,t.stroke(),It(t,0,h,l),It(t,d,-u,c),t.restore()}get label(){return this.elements&&this.elements[0]}resolveElementProperties(t,o){const n=Q(t,o),{x:i,y:s,x2:a,y2:d}=n,l=function({x:t,y:e,x2:o,y2:n},{top:i,right:r,bottom:s,left:a}){return!(tr&&o>r||es&&n>s)}(n,t.chartArea),c=l?function(t,e,o){const{x:n,y:i}=At(t,e,o),{x:r,y:s}=At(e,t,o);return{x:n,y:i,x2:r,y2:s,width:Math.abs(r-n),height:Math.abs(s-i)}}({x:i,y:s},{x:a,y:d},t.chartArea):{x:i,y:s,x2:a,y2:d,width:Math.abs(a-i),height:Math.abs(d-s)};if(c.centerX=(a+i)/2,c.centerY=(d+s)/2,c.initProperties=A(t,c,o),o.curve){const t={x:c.x,y:c.y},n={x:c.x2,y:c.y2};c.cp=function(t,e,o){const{x:n,y:i,x2:s,y2:a,centerX:d,centerY:l}=t,c=Math.atan2(a-i,s-n),h=M(e.controlPoint,0);return r({x:d+v(o,h.x,!1),y:l+v(o,h.y,!1)},{x:d,y:l},c)}(c,o,e.distanceBetweenPoints(t,n))}const h=function(t,o,n){const i=n.borderWidth,r=e.toPadding(n.padding),s=E(t.ctx,n),a=s.width+r.width+i,d=s.height+r.height+i;return function(t,o,n,i){const{width:r,height:s,padding:a}=n,{xAdjust:d,yAdjust:l}=o,c={x:t.x,y:t.y},h={x:t.x2,y:t.y2},u="auto"===o.rotation?function(t){const{x:o,y:n,x2:i,y2:r}=t,s=Math.atan2(r-n,i-o);return s>e.PI/2?s-e.PI:si&&(e=gt(i,{x:t,y:e},o),t=i),er&&(t=bt(r,{x:t,y:e},o),e=r),{x:t,y:e}}function Tt(t,{mouseX:e,mouseY:o},n,i){const r=t.label;return r.options.display&&r.inRange(e,o,i,n)}function Dt(t,e,o,n){const{labelSize:i,padding:r}=e,s=t.w*n.dx,a=t.h*n.dy,l=s>0&&(i.w/2+r.left-n.x)/s,c=a>0&&(i.h/2+r.top-n.y)/a;return d(Math.max(l,c),0,.25)}function jt(t,e){const{size:o,min:n,max:i,padding:r}=e,s=o/2;return o>i-n?(i+n)/2:(n>=t-r-s&&(t=n+r+s),i<=t+r+s&&(t=i-r-s),t)}function Ot(t){const e=t.options,o=e.arrowHeads&&e.arrowHeads.start,n=e.arrowHeads&&e.arrowHeads.end;return{startOpts:o,endOpts:n,startAdjust:Rt(t,o),endAdjust:Rt(t,n)}}function Rt(t,e){if(!e||!e.display)return 0;const{length:o,width:n}=e,i=t.options.borderWidth/2,r={x:o,y:n+i},s={x:0,y:i};return Math.abs(bt(0,r,s))}function It(t,e,o,n){if(!n||!n.display)return;const{length:i,width:r,fill:s,backgroundColor:a,borderColor:d}=n,l=Math.abs(e-i)+o;t.beginPath(),_(t,n),X(t,n),t.moveTo(l,-r),t.lineTo(e+o,0),t.lineTo(l,r),!0===s?(t.fillStyle=a||d,t.closePath(),t.fill(),t.shadowColor="transparent"):t.shadowColor=n.borderShadowColor,t.stroke()}function Yt(t,{x:e,y:o},{angle:n,adjust:i},r){r&&r.display&&(t.save(),t.translate(e,o),t.rotate(n),It(t,0,-i,r),t.restore())}Ct.defaults={adjustScaleRange:!0,arrowHeads:{display:!1,end:Object.assign({},kt),fill:!1,length:12,start:Object.assign({},kt),width:6},borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:2,curve:!1,controlPoint:{y:"-50%"},display:!0,endValue:void 0,init:void 0,hitTolerance:0,label:{backgroundColor:"rgba(0,0,0,0.8)",backgroundShadowColor:"transparent",borderCapStyle:"butt",borderColor:"black",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderRadius:6,borderShadowColor:"transparent",borderWidth:0,callout:Object.assign({},yt.defaults.callout),color:"#fff",content:null,display:!1,drawTime:void 0,font:{family:void 0,lineHeight:void 0,size:void 0,style:void 0,weight:"bold"},height:void 0,hitTolerance:void 0,opacity:void 0,padding:6,position:"center",rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,textAlign:"center",textStrokeColor:void 0,textStrokeWidth:0,width:void 0,xAdjust:0,yAdjust:0,z:void 0},scaleID:void 0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,value:void 0,xMax:void 0,xMin:void 0,xScaleID:void 0,yMax:void 0,yMin:void 0,yScaleID:void 0,z:0},Ct.descriptors={arrowHeads:{start:{_fallback:!0},end:{_fallback:!0},_fallback:!0}},Ct.defaultRoutes={borderColor:"color"};class Xt extends t.Element{inRange(t,o,n,i){const s=this.options.rotation,d=(this.options.borderWidth+this.options.hitTolerance)/2;if("x"!==n&&"y"!==n)return function(t,o,n,i){const{width:r,height:s,centerX:a,centerY:d}=o,l=r/2,c=s/2;if(l<=0||c<=0)return!1;const h=e.toRadians(n||0),u=Math.cos(h),f=Math.sin(h),x=Math.pow(u*(t.x-a)+f*(t.y-d),2),y=Math.pow(f*(t.x-a)-u*(t.y-d),2);return x/Math.pow(l+i,2)+y/Math.pow(c+i,2)<=1.0001}({x:t,y:o},this.getProps(["width","height","centerX","centerY"],i),s,d);const{x:l,y:c,x2:h,y2:u}=this.getProps(["x","y","x2","y2"],i),f="y"===n?{start:c,end:u}:{start:l,end:h},x=r({x:t,y:o},this.getCenterPoint(i),e.toRadians(-s));return x[n]>=f.start-d-a&&x[n]<=f.end+d+a}getCenterPoint(t){return f(this,t)}draw(t){const{width:o,height:n,centerX:i,centerY:r,options:s}=this;t.save(),Y(t,this.getCenterPoint(),s.rotation),_(t,this.options),t.beginPath(),t.fillStyle=s.backgroundColor;const a=X(t,s);t.ellipse(i,r,n/2,o/2,e.PI/2,0,2*e.PI),t.fill(),a&&(t.shadowColor=s.borderShadowColor,t.stroke()),t.restore()}get label(){return this.elements&&this.elements[0]}resolveElementProperties(t,e){return G(t,e)}}Xt.id="ellipseAnnotation",Xt.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:1,display:!0,hitTolerance:0,init:void 0,label:Object.assign({},ft.defaults.label),rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xMax:void 0,xMin:void 0,xScaleID:void 0,yMax:void 0,yMin:void 0,yScaleID:void 0,z:0},Xt.defaultRoutes={borderColor:"color",backgroundColor:"color"},Xt.descriptors={label:{_fallback:!0}};class _t extends t.Element{inRange(t,e,o,n){const{x:i,y:r,x2:s,y2:a,width:d}=this.getProps(["x","y","x2","y2","width"],n),c=(this.options.borderWidth+this.options.hitTolerance)/2;if("x"!==o&&"y"!==o)return function(t,e,o,n){return!(!t||!e||o<=0)&&Math.pow(t.x-e.x,2)+Math.pow(t.y-e.y,2)<=Math.pow(o+n,2)}({x:t,y:e},this.getCenterPoint(n),d/2,c);return l("y"===o?{start:r,end:a,value:e}:{start:i,end:s,value:t},c)}getCenterPoint(t){return f(this,t)}draw(t){const e=this.options,o=e.borderWidth;if(e.radius<.1)return;t.save(),t.fillStyle=e.backgroundColor,_(t,e);const n=X(t,e);F(t,this,this.centerX,this.centerY),n&&!I(e.pointStyle)&&(t.shadowColor=e.borderShadowColor,t.stroke()),t.restore(),e.borderWidth=o}resolveElementProperties(t,e){const o=q(t,e);return o.initProperties=A(t,o,e),o}}_t.id="pointAnnotation",_t.defaults={adjustScaleRange:!0,backgroundShadowColor:"transparent",borderDash:[],borderDashOffset:0,borderShadowColor:"transparent",borderWidth:1,display:!0,hitTolerance:0,init:void 0,pointStyle:"circle",radius:10,rotation:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,xAdjust:0,xMax:void 0,xMin:void 0,xScaleID:void 0,xValue:void 0,yAdjust:0,yMax:void 0,yMin:void 0,yScaleID:void 0,yValue:void 0,z:0},_t.defaultRoutes={borderColor:"color",backgroundColor:"color"};class Et extends t.Element{inRange(t,o,n,i){if("x"!==n&&"y"!==n)return this.options.radius>=.1&&this.elements.length>1&&function(t,e,o,n){let i=!1,r=t[t.length-1].getProps(["bX","bY"],n);for(const s of t){const t=s.getProps(["bX","bY"],n);t.bY>o!=r.bY>o&&e<(r.bX-t.bX)*(o-t.bY)/(r.bY-t.bY)+t.bX&&(i=!i),r=t}return i}(this.elements,t,o,i);const s=r({x:t,y:o},this.getCenterPoint(i),e.toRadians(-this.options.rotation)),a=this.elements.map((t=>"y"===n?t.bY:t.bX)),d=Math.min(...a),l=Math.max(...a);return s[n]>=d&&s[n]<=l}getCenterPoint(t){return f(this,t)}draw(t){const{elements:e,options:o}=this;t.save(),t.beginPath(),t.fillStyle=o.backgroundColor,_(t,o);const n=X(t,o);let i=!0;for(const o of e)i?(t.moveTo(o.x,o.y),i=!1):t.lineTo(o.x,o.y);t.closePath(),t.fill(),n&&(t.shadowColor=o.borderShadowColor,t.stroke()),t.restore()}resolveElementProperties(t,o){const n=q(t,o),{sides:i,rotation:r}=o,s=[],a=2*e.PI/i;let d=r*e.RAD_PER_DEG;for(let e=0;e{t.defaults.describe(`elements.${zt[e].id}`,{_fallback:"plugins.annotation.common"})}));const Ft={update:Object.assign},Ht=ot.concat(st),Nt=(t,o)=>e.isObject(o)?Qt(t,o):t,Lt=t=>"color"===t||"font"===t;function Vt(t="line"){return zt[t]?t:(console.warn(`Unknown annotation type: '${t}', defaulting to 'line'`),"line")}function Bt(o,n,i,r){const s=function(e,o,n){if("reset"===n||"none"===n||"resize"===n)return Ft;return new t.Animations(e,o)}(o,i.animations,r),a=n.annotations,d=function(t,e){const o=e.length,n=t.length;if(no&&t.splice(o,n-o);return t}(n.elements,a);for(let t=0;tNt(t,r))):n[i]=Nt(s,r)}return n}function Gt(t,e,o,n){return e.$context||(e.$context=Object.assign(Object.create(t.getContext()),{element:e,get elements(){return o.filter((t=>t&&t.options))},id:n.id,type:"annotation"}))}const Kt=new Map,Zt=t=>"doughnutLabel"!==t.type,te=ot.concat(st);var ee={id:"annotation",version:"3.1.0",beforeRegister(){!function(t,e,o,n=!0){const i=o.split(".");let r=0;for(const a of e.split(".")){const d=i[r++];if(parseInt(a,10){const o=r[t];e.isObject(o)&&(o.id=t,i.push(o))})):e.isArray(r)&&i.push(...r),function(t,e){for(const o of t)ct(o,e)}(i.filter(Zt),t.scales)},afterDataLimits(t,e){const o=Kt.get(t);dt(t,e.scale,o.annotations.filter(Zt).filter((t=>t.display&&t.adjustScaleRange)))},afterUpdate(t,o,n){const i=Kt.get(t);!function(t,o,n){o.listened=T(n,ot,o.listeners),o.moveListened=!1,et.forEach((t=>{e.isFunction(n[t])&&(o.moveListened=!0)})),o.listened&&o.moveListened||o.annotations.forEach((t=>{!o.listened&&e.isFunction(t.click)&&(o.listened=!0),o.moveListened||et.forEach((n=>{e.isFunction(t[n])&&(o.listened=!0,o.moveListened=!0)}))}))}(0,i,n),Bt(t,i,n,o.mode),i.visibleElements=i.elements.filter((t=>!t.skip&&t.options.display)),function(t,o,n){const i=o.visibleElements;o.hooked=T(n,st,o.hooks),o.hooked||i.forEach((t=>{o.hooked||st.forEach((n=>{e.isFunction(t.options[n])&&(o.hooked=!0)}))}))}(0,i,n)},beforeDatasetsDraw(t,e,o){oe(t,"beforeDatasetsDraw",o.clip)},afterDatasetsDraw(t,e,o){oe(t,"afterDatasetsDraw",o.clip)},beforeDatasetDraw(t,e,o){oe(t,e.index,o.clip)},beforeDraw(t,e,o){oe(t,"beforeDraw",o.clip)},afterDraw(t,e,o){oe(t,"afterDraw",o.clip)},beforeEvent(t,e,o){nt(Kt.get(t),e.event,o)&&(e.changed=!0)},afterDestroy(t){Kt.delete(t)},getAnnotations(t){const e=Kt.get(t);return e?e.elements:[]},_getAnnotationElementsAtEventForMode:(t,e,o)=>n(t,e,o),defaults:{animations:{numbers:{properties:["x","y","x2","y2","width","height","centerX","centerY","pointX","pointY","radius"],type:"number"},colors:{properties:["backgroundColor","borderColor"],type:"color"}},clip:!0,interaction:{mode:void 0,axis:void 0,intersect:void 0},common:{drawTime:"afterDatasetsDraw",init:!1,label:{}}},descriptors:{_indexable:!1,_scriptable:t=>!te.includes(t)&&"init"!==t,annotations:{_allKeys:!1,_fallback:(t,e)=>`elements.${zt[Vt(e.type)].id}`},interaction:{_fallback:!0},common:{label:{_indexable:Lt,_fallback:!0},_indexable:Lt}},additionalOptionScopes:[""]};function oe(t,o,n){const{ctx:i,chartArea:r}=t,s=Kt.get(t);n&&e.clipArea(i,r);const a=function(t,e){const o=[];for(const n of t)if(n.options.drawTime===e&&o.push({element:n,main:!0}),n.elements&&n.elements.length)for(const t of n.elements)t.options.display&&t.options.drawTime===e&&o.push({element:t});return o}(s.visibleElements,o).sort(((t,e)=>t.element.options.z-e.element.options.z));for(const t of a)ne(i,r,s,t);n&&e.unclipArea(i)}function ne(t,e,o,n){const i=n.element;n.main?(at(o,i,"beforeDraw"),i.draw(t,e),at(o,i,"afterDraw")):i.draw(t,e)}return t.Chart.register(ee),ee})); diff --git a/static/js/chartjs-plugin-annotation/package.json b/static/js/chartjs-plugin-annotation/package.json new file mode 100644 index 0000000..5cd621b --- /dev/null +++ b/static/js/chartjs-plugin-annotation/package.json @@ -0,0 +1,90 @@ +{ + "name": "chartjs-plugin-annotation", + "homepage": "https://www.chartjs.org/chartjs-plugin-annotation/index", + "description": "Annotations for Chart.js", + "version": "3.1.0", + "author": "Evert Timberg ", + "license": "MIT", + "type": "module", + "main": "dist/chartjs-plugin-annotation.cjs", + "module": "dist/chartjs-plugin-annotation.esm.js", + "types": "types/index.d.ts", + "jsdelivr": "dist/chartjs-plugin-annotation.min.js", + "unpkg": "dist/chartjs-plugin-annotation.min.js", + "exports": { + "types": "./types/index.d.ts", + "import": "./dist/chartjs-plugin-annotation.esm.js", + "require": "./dist/chartjs-plugin-annotation.cjs", + "script": "./dist/chartjs-plugin-annotation.min.js" + }, + "sideEffects": [ + "dist/chartjs-plugin-annotation.cjs", + "dist/chartjs-plugin-annotation.min.js" + ], + "repository": { + "type": "git", + "url": "https://github.com/chartjs/chartjs-plugin-annotation.git" + }, + "files": [ + "dist/*", + "!dist/docs/**", + "types/*.d.ts" + ], + "scripts": { + "build": "rollup -c", + "dev": "karma start ./karma.conf.cjs --auto-watch --no-single-run --browsers chrome", + "dev:ff": "karma start ./karma.conf.cjs --auto-watch --no-single-run --browsers firefox", + "docs": "npm run build && vuepress build docs --no-cache", + "docs:dev": "npm run build && vuepress dev docs --no-cache", + "lint": "concurrently --group \"npm:lint-*\"", + "lint-js": "eslint \"test/**/*.js\" \"src/**/*.js\"", + "lint-md": "eslint \"**/*.md\"", + "lint-types": "eslint \"types/**/*.ts\" && tsc -p types/tests/", + "test": "cross-env NODE_ENV=test concurrently --group \"npm:test-*\"", + "test-karma": "karma start ./karma.conf.cjs --auto-watch --single-run", + "test-lint": "npm run lint", + "test-types": "tsc -p types/tests/", + "test-integration": "mocha --full-trace test/integration/*-test.js" + }, + "devDependencies": { + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-terser": "^0.4.0", + "@simonbrunel/vuepress-plugin-versions": "^0.2.0", + "@typescript-eslint/eslint-plugin": "^5.51.0", + "@typescript-eslint/parser": "^5.51.0", + "chart.js": "^4.3.0", + "chartjs-test-utils": "^0.5.0", + "concurrently": "^7.6.0", + "cross-env": "^7.0.3", + "eslint": "^8.33.0", + "eslint-config-chartjs": "^0.3.0", + "eslint-plugin-es": "^4.1.0", + "eslint-plugin-html": "^7.1.0", + "eslint-plugin-markdown": "^3.0.0", + "fs-extra": "^11.1.0", + "karma": "^6.4.1", + "karma-chrome-launcher": "^3.1.1", + "karma-coverage": "^2.2.0", + "karma-firefox-launcher": "^2.1.2", + "karma-jasmine": "^5.1.0", + "karma-jasmine-html-reporter": "^2.0.0", + "karma-rollup-preprocessor": "7.0.7", + "markdown-it-include": "^2.0.0", + "mocha": "^10.2.0", + "pixelmatch": "^5.3.0", + "rollup": "^3.14.0", + "rollup-plugin-istanbul": "^4.0.0", + "typedoc": "^0.23.24", + "typedoc-plugin-markdown": "^3.14.0", + "typescript": "^4.2.4", + "vuepress": "^1.9.7", + "vuepress-plugin-flexsearch": "^0.3.0", + "vuepress-plugin-redirect": "^1.2.5", + "vuepress-plugin-typedoc": "^0.10.0", + "vuepress-theme-chartjs": "^0.2.0" + }, + "peerDependencies": { + "chart.js": ">=4.0.0" + } +} diff --git a/static/js/chartjs-plugin-annotation/types/element.d.ts b/static/js/chartjs-plugin-annotation/types/element.d.ts new file mode 100644 index 0000000..8ed43be --- /dev/null +++ b/static/js/chartjs-plugin-annotation/types/element.d.ts @@ -0,0 +1,18 @@ +import { AnnotationOptions } from './options'; + +export interface AnnotationBoxModel { + x: number, + y: number, + x2: number, + y2: number, + centerX: number, + centerY: number, + height: number, + width: number, + radius?: number +} + +export interface AnnotationElement extends AnnotationBoxModel { + label?: AnnotationElement, + options: AnnotationOptions +} diff --git a/static/js/chartjs-plugin-annotation/types/events.d.ts b/static/js/chartjs-plugin-annotation/types/events.d.ts new file mode 100644 index 0000000..996d296 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/types/events.d.ts @@ -0,0 +1,28 @@ +import { Chart, ChartEvent } from 'chart.js'; +import { AnnotationElement } from './element'; + +export interface EventContext { + chart: Chart, + element: AnnotationElement, + elements: AnnotationElement[], + id: string, + type: string +} + +/** + * Some scriptable options may be called with during the chart's initial + * display, when the element isn't fully initialized. + */ +export interface PartialEventContext { + chart: Chart, + element?: Partial, + elements?: AnnotationElement[], + id?: string, + type?: string +} + +export interface AnnotationEvents { + enter?(context: EventContext, event: ChartEvent): boolean | void, + leave?(context: EventContext, event: ChartEvent): boolean | void, + click?(context: EventContext, event: ChartEvent): boolean | void, +} diff --git a/static/js/chartjs-plugin-annotation/types/index.d.ts b/static/js/chartjs-plugin-annotation/types/index.d.ts new file mode 100644 index 0000000..be987e2 --- /dev/null +++ b/static/js/chartjs-plugin-annotation/types/index.d.ts @@ -0,0 +1,31 @@ +import { Chart, ChartType, Plugin } from 'chart.js'; +import { AnnotationPluginOptions, BoxAnnotationOptions, EllipseAnnotationOptions, LabelAnnotationOptions, LineAnnotationOptions, PointAnnotationOptions, PolygonAnnotationOptions, DoughnutLabelAnnotationOptions } from './options'; +import { AnnotationElement } from './element'; + +declare module 'chart.js' { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface PluginOptionsByType { + annotation: AnnotationPluginOptions; + } + + interface ElementOptionsByType { + boxAnnotation: BoxAnnotationOptions; + doughnutLabelAnnotation: DoughnutLabelAnnotationOptions; + ellipseAnnotation: EllipseAnnotationOptions; + labelAnnotation: LabelAnnotationOptions; + lineAnnotation: LineAnnotationOptions; + pointAnnotation: PointAnnotationOptions; + polygonAnnotation: PolygonAnnotationOptions; + } +} + +declare const Annotation: Plugin & { + getAnnotations(chart: Chart): AnnotationElement[]; +}; + +export default Annotation; + +export * from './element'; +export * from './events'; +export * from './label'; +export * from './options'; diff --git a/static/js/chartjs-plugin-annotation/types/label.d.ts b/static/js/chartjs-plugin-annotation/types/label.d.ts new file mode 100644 index 0000000..4180e7f --- /dev/null +++ b/static/js/chartjs-plugin-annotation/types/label.d.ts @@ -0,0 +1,165 @@ +import { Color, FontSpec, BorderRadius } from 'chart.js'; +import { PartialEventContext } from './events'; +import { DrawTime, Scriptable, ShadowOptions } from './options'; + +type percentString = `${number}%`; +export type LabelPosition = 'start' | 'center' | 'end' | percentString; + +export type LabelTextAlign = 'left' | 'start' | 'center' | 'right' | 'end'; + +export type CalloutPosition = 'left' | 'top' | 'bottom' | 'right' | 'auto'; + +export interface LabelPositionObject { + x?: LabelPosition, + y?: LabelPosition +} + +export interface LabelPadding { + top?: number, + left?: number, + right?: number, + bottom?: number, + x?: number, + y?: number +} + +export interface CalloutOptions { + borderCapStyle?: Scriptable, + borderColor?: Scriptable, + borderDash?: Scriptable, + borderDashOffset?: Scriptable, + borderJoinStyle?: Scriptable, + borderWidth?: Scriptable, + display?: Scriptable, + margin?: Scriptable, + position?: Scriptable, + side?: Scriptable, + start?: Scriptable, +} + +export interface CoreLabelOptions { + drawTime?: Scriptable, + font?: Scriptable | Partial[], PartialEventContext>, + color?: Scriptable, + /** + * Padding of label + * @default 6 + */ + padding?: Scriptable, + /** + * Text alignment when the content of the label is multi-line. + * @default 'center' + */ + textAlign?: Scriptable, + textStrokeColor?: Scriptable, + textStrokeWidth?: Scriptable, + /** + * Adjustment along x-axis (left-right) of label relative to above number (can be negative) + * For horizontal lines positioned left or right, negative values move + * the label toward the edge, and positive values toward the center. + * @default 0 + */ + xAdjust?: Scriptable, + /** + * Adjustment along y-axis (top-bottom) of label relative to above number (can be negative) + * For vertical lines positioned top or bottom, negative values move + * the label toward the edge, and positive values toward the center. + * @default 0 + */ + yAdjust?: Scriptable, + /** + * Text to display in label. Provide an array to display multiple lines + */ + content: Scriptable, + /** + * Overrides the width of the image. Could be set in pixel by a number, + * or in percentage of current width of image by a string + */ + width?: Scriptable, + /** + * Overrides the height of the image. Could be set in pixel by a number, + * or in percentage of current height of image by a string + */ + height?: Scriptable, + /** + * Overrides the opacity of the image. + */ + opacity?: Scriptable, + z?: Scriptable +} + +export interface ContainedLabelOptions extends CoreLabelOptions { + backgroundColor?: Scriptable, + borderWidth?: Scriptable, + borderColor?: Scriptable, + /** + * Border line cap style. See MDN. + * @default 'butt' + */ + borderCapStyle?: Scriptable, + /** + * Border line dash. See MDN. + * @default [] + */ + borderDash?: Scriptable, + /** + * Border line dash offset. See MDN. + * @default 0.0 + */ + borderDashOffset?: Scriptable, + /** + * Border line join style. See MDN. + * @default 'miter' + */ + borderJoinStyle?: Scriptable, + /** + * Border radius of the label rectangle + * @default 6 + */ + borderRadius?: Scriptable +} + +export interface LabelOptions extends ContainedLabelOptions, ShadowOptions { + position?: Scriptable, + /** + * Whether the label should be displayed + * @default true + */ + display?: Scriptable, + hitTolerance?: Scriptable, + /** + * Rotation of label, in degrees, or 'auto' to use the degrees of the line, default is 0 + * @default 90 + */ + rotation?: Scriptable, + z?: Scriptable, + callout?: CalloutOptions, +} + +export interface BoxLabelOptions extends CoreLabelOptions { + position?: Scriptable, + /** + * Whether the label should be displayed + * @default true + */ + display?: Scriptable, + hitTolerance?: Scriptable, + rotation?: Scriptable, + z?: Scriptable +} + +export interface LabelTypeOptions extends ContainedLabelOptions { + position?: Scriptable, + z?: Scriptable, + callout?: CalloutOptions, +} + +export interface DoughnutLabelOptions extends Omit { + position?: Scriptable, + /** + * Whether the label should be displayed + * @default true + */ + display?: Scriptable, + rotation?: Scriptable +} diff --git a/static/js/chartjs-plugin-annotation/types/options.d.ts b/static/js/chartjs-plugin-annotation/types/options.d.ts new file mode 100644 index 0000000..0f0886c --- /dev/null +++ b/static/js/chartjs-plugin-annotation/types/options.d.ts @@ -0,0 +1,166 @@ +import { Chart, Color, PointStyle, BorderRadius, CoreInteractionOptions } from 'chart.js'; +import { AnnotationEvents, PartialEventContext, EventContext } from './events'; +import { LabelOptions, BoxLabelOptions, LabelTypeOptions, DoughnutLabelOptions } from './label'; +import { AnnotationBoxModel, AnnotationElement } from './element'; + +export type DrawTime = 'afterDraw' | 'afterDatasetsDraw' | 'beforeDraw' | 'beforeDatasetsDraw' | number; + +export interface AnnotationTypeRegistry { + box: BoxAnnotationOptions + ellipse: EllipseAnnotationOptions + label: LabelAnnotationOptions + line: LineAnnotationOptions + point: PointAnnotationOptions + polygon: PolygonAnnotationOptions +} + +export type AnnotationType = keyof AnnotationTypeRegistry; +export type AnnotationOptions = + { [key in TYPE]: { type: key } & AnnotationTypeRegistry[key] }[TYPE] + +interface AnnotationHooks { + beforeDraw?(context: EventContext): void, + afterDraw?(context: EventContext): void +} + +export type Scriptable = T | ((ctx: TContext, options: AnnotationOptions) => T); +export type ScaleValue = number | string; + +interface ShadowOptions { + backgroundShadowColor?: Scriptable, + borderShadowColor?: Scriptable, + shadowBlur?: Scriptable, + shadowOffsetX?: Scriptable, + shadowOffsetY?: Scriptable +} + +export interface CoreAnnotationOptions extends AnnotationEvents, ShadowOptions, AnnotationHooks { + adjustScaleRange?: Scriptable, + borderColor?: Scriptable, + borderDash?: Scriptable, + borderDashOffset?: Scriptable, + borderWidth?: Scriptable, + display?: Scriptable, + drawTime?: Scriptable, + hitTolerance?: Scriptable, + init?: boolean | (({ chart: Chart, properties: AnnotationBoxModel, options: AnnotationOptions }) => void | boolean | Partial), + id?: string, + xMax?: Scriptable, + xMin?: Scriptable, + xScaleID?: Scriptable, + yMax?: Scriptable, + yMin?: Scriptable, + yScaleID?: Scriptable, + z?: Scriptable +} + +interface AnnotationPointCoordinates { + xValue?: Scriptable, + yValue?: Scriptable, +} + +export interface ArrowHeadOptions extends ShadowOptions { + backgroundColor?: Scriptable, + borderColor?: Scriptable, + borderDash?: Scriptable, + borderDashOffset?: Scriptable, + borderWidth?: Scriptable, + display?: Scriptable, + fill?: Scriptable, + length?: Scriptable, + width?: Scriptable, +} + +export interface ArrowHeadsOptions extends ArrowHeadOptions{ + end?: ArrowHeadOptions, + start?: ArrowHeadOptions, +} + +export interface ControlPointOptions { + x?: Scriptable, + y?: Scriptable, +} + +export interface LineAnnotationOptions extends CoreAnnotationOptions { + arrowHeads?: ArrowHeadsOptions, + curve?: Scriptable, + controlPoint?: Scriptable, + endValue?: Scriptable, + label?: LabelOptions, + scaleID?: Scriptable, + value?: Scriptable +} + +export interface BoxAnnotationOptions extends CoreAnnotationOptions { + backgroundColor?: Scriptable, + /** + * Border line cap style. See MDN. + * @default 'butt' + */ + borderCapStyle?: Scriptable, + /** + * Border line dash. See MDN. + * @default [] + */ + borderDash?: Scriptable, + /** + * Border line dash offset. See MDN. + * @default 0.0 + */ + borderDashOffset?: Scriptable, + /** + * Border line join style. See MDN. + * @default 'miter' + */ + borderJoinStyle?: Scriptable, + borderRadius?: Scriptable, + label?: BoxLabelOptions, + rotation?: Scriptable +} + +export interface EllipseAnnotationOptions extends CoreAnnotationOptions { + backgroundColor?: Scriptable, + label?: BoxLabelOptions, + rotation?: Scriptable +} + +export interface PointAnnotationOptions extends CoreAnnotationOptions, AnnotationPointCoordinates { + backgroundColor: Scriptable, + pointStyle?: Scriptable, + radius?: Scriptable, + rotation?: Scriptable, + xAdjust?: Scriptable, + yAdjust?: Scriptable, +} + +export interface LabelAnnotationOptions extends CoreAnnotationOptions, LabelTypeOptions, AnnotationPointCoordinates { + rotation?: Scriptable +} + +export interface DoughnutLabelAnnotationOptions extends AnnotationEvents, DoughnutLabelOptions, ShadowOptions { + autoFit?: Scriptable, + autoHide?: Scriptable, + id?: string, + init: boolean | ((chart: Chart, properties: AnnotationBoxModel, options: AnnotationOptions) => void | boolean | AnnotationBoxModel), + spacing?: Scriptable +} + +interface PolygonAnnotationOptions extends CoreAnnotationOptions, AnnotationPointCoordinates { + backgroundColor: Scriptable, + borderCapStyle?: Scriptable, + borderJoinStyle?: Scriptable, + point?: PointAnnotationOptions, + radius?: Scriptable, + rotation?: Scriptable, + sides?: Scriptable, + xAdjust?: Scriptable, + yAdjust?: Scriptable, +} + +export interface AnnotationPluginOptions extends AnnotationEvents, AnnotationHooks { + animations?: Record, + annotations: AnnotationOptions[] | Record, + clip?: boolean, + common?: BoxAnnotationOptions | EllipseAnnotationOptions | LabelAnnotationOptions | LineAnnotationOptions | PointAnnotationOptions | PolygonAnnotationOptions | DoughnutLabelAnnotationOptions, + interaction?: CoreInteractionOptions +}