From 8be1af705960395d1dc159e07f7182097002c14c Mon Sep 17 00:00:00 2001 From: Alexandra Betouni <31625338+alexandraB99@users.noreply.github.com> Date: Wed, 14 Sep 2022 14:21:59 +0300 Subject: [PATCH] feat(StatusChart): Adding chart component (#893) Needed for https://github.com/status-im/status-desktop/issues/6490 --- .../doc/src/images/status_chart_panel.png | Bin 0 -> 125299 bytes ui/StatusQ/sandbox/main.qml | 5 + .../sandbox/pages/StatusChartPanelPage.qml | 193 + .../StatusQ/Components/StatusChartPanel.qml | 144 + .../StatusQ/Components/private/chart/Chart.js | 20787 ++++++++++++++++ .../Components/private/chart/Chart.qml | 139 + .../StatusQ/Components/private/chart/LICENSE | 21 + ui/StatusQ/src/StatusQ/Components/qmldir | 2 + 8 files changed, 21291 insertions(+) create mode 100644 ui/StatusQ/doc/src/images/status_chart_panel.png create mode 100644 ui/StatusQ/sandbox/pages/StatusChartPanelPage.qml create mode 100644 ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml create mode 100644 ui/StatusQ/src/StatusQ/Components/private/chart/Chart.js create mode 100644 ui/StatusQ/src/StatusQ/Components/private/chart/Chart.qml create mode 100644 ui/StatusQ/src/StatusQ/Components/private/chart/LICENSE diff --git a/ui/StatusQ/doc/src/images/status_chart_panel.png b/ui/StatusQ/doc/src/images/status_chart_panel.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb5e13eaf4a6e910794ef7a1b0f1170c7d6eea2 GIT binary patch literal 125299 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%0AP{wtv`t9-jpO5%kr*WicoJxnLGY3KGEO)%9 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

A?eGZu`8Vvo9UHhIqX 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%0Af(=EDzEcK!IC44Ba`o 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 { + axis.paddingTop = 25; + axis.paddingBottom = 0; + }, + ticks: { + fontSize: 10, + fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1', + padding: 24, + min: d.minStep, + max: d.maxStep, + stepSize: d.stepSize, + callback: function(value, index, ticks) { + return '$' + value; + }, + } + }] + } + } + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml b/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml new file mode 100644 index 0000000000..c8b11d3b44 --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml @@ -0,0 +1,144 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import StatusQ.Controls 0.1 + +import "private/chart" + +/*! + \qmltype StatusChartPanel + \inherits Page + \inqmlmodule StatusQ.Components + \since StatusQ.Components 0.1 + \brief Displays a chart component together with an optional header in order to add a list of options. + Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-page.html}{Page}. + + The \c StatusChartPanel displays a customizable chart component. Additionally, options + can be added in the header in both right and left sides given graphTabsModel and timeRangeTabsModel + property are set respectively. + + For example: + + \qml + StatusChartPanel { + id: graphDetail + width: parent.width + height: 290 + anchors.verticalCenter: parent.verticalCenter + graphsModel: d.graphTabsModel + timeRangeModel: d.timeRangeTabsModel + chart.chartType: 'line' + chart.chartData: { + return { + datasets: [{ + data: d.data + }], + ... + } + } + chart.chartOptions: { + return { + legend: { + display: false + }, + ... + } + } + } + \endqml + + \image status_chart_panel.png + + For a list of components available see StatusQ. +*/ + +Page { + id: root + + /*! + \qmlproperty real StatusChartPanel::graphsModel + This property holds the graphs model options to be set on the left side tab bar of the header. + */ + property var graphsModel + /*! + \qmlproperty real StatusChartPanel::timeRangeModel + This property holds the time range options to be set on the right side tab bar of the header. + */ + property var timeRangeModel + + /*! + \qmlproperty real StatusChartPanel::graphComponent + This property holds holds a reference to the graph component. + */ + property alias chart: graphComponent + + /*! + \qmlproperty real StatusChartPanel::timeRangeTabBarIndex + This property holds holds a reference to the time range tab bar current index. + */ + property alias timeRangeTabBarIndex: timeRangeTabBar.currentIndex + + /*! + \qmlproperty real StatusChartPanel::selectedTimeRange + This property holds holds the text of the current time range tab bar selected tab. + */ + property string selectedTimeRange: timeRangeTabBar.currentItem.text + + /*! + \qmlsignal + This signal is emitted when a header tab bar is clicked. + */ + signal headerTabClicked(string text) + + Component { + id: tabButton + StatusTabButton { + leftPadding: 0 + width: implicitWidth + onClicked: { + root.headerTabClicked(text); + } + } + } + + Component.onCompleted: { + if (!!timeRangeModel) { + for (var i = 0; i < timeRangeModel.length; i++) { + var timeTab = tabButton.createObject(root, { text: qsTr(timeRangeModel[i].text.toString()), + enabled: timeRangeModel[i].enabled }); + timeRangeTabBar.addItem(timeTab); + } + } + if (!!graphsModel) { + for (var j = 0; j < graphsModel.length; j++) { + var graphTab = tabButton.createObject(root, { text: qsTr(graphsModel[j].text.toString()), + enabled: graphsModel[j].enabled }); + graphsTabBar.addItem(graphTab); + } + } + } + + background: null + header: Item { + height: childrenRect.height + RowLayout { + anchors.left: parent.left + anchors.leftMargin: 40 + anchors.right: parent.right + StatusTabBar { + id: graphsTabBar + } + StatusTabBar { + id: timeRangeTabBar + Layout.alignment: Qt.AlignRight + } + } + } + + contentItem: Item { + Chart { + id: graphComponent + anchors.fill: parent + } + } +} diff --git a/ui/StatusQ/src/StatusQ/Components/private/chart/Chart.js b/ui/StatusQ/src/StatusQ/Components/private/chart/Chart.js new file mode 100644 index 0000000000..050f722e9b --- /dev/null +++ b/ui/StatusQ/src/StatusQ/Components/private/chart/Chart.js @@ -0,0 +1,20787 @@ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ + + /*! + * adaptions by Elypson (Michael A. Voelkel) to get it working for QML: + * 1) changed overall library structure with UMD and global function build + * 2) animations are triggered via QML animator + * 3) tooltips do not use DOM events but QML events that are injected via bindEvents now + * 4) many smaller modifications because Chart.js relied on and used the DOM that is not available in QML in the same way + * 5) some fixes that occured in QML like setting dashed line to solid + * 6) personal customization, where we assumed that it leads to better looks + * + * also note that modifications are inspired by Shuirna (github.com/shuirna) and their changes were inspired by Julien Wintz + * + * (c) 2020 ChartJs2QML contributors (starting with Elypson, Michael A. Voelkel, https://github.com/Elypson) + * those customizations are also licensed under MIT for all matters and purposes + */ + +function UMD(global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : +typeof define === 'function' && define.amd ? define(factory) : +(global = global || self, global.Chart = factory()); +}; + +function Chart (item, config) { 'use strict'; + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function commonjsRequire () { + throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs'); +} + +function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +} + +function getCjsExportFromNamespace (n) { + return n && n['default'] || n; +} + +var colorName = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +var conversions = createCommonjsModule(function (module) { +/* MIT license */ + + +// NOTE: conversions should only return primitive values (i.e. arrays, or +// values that give correct `typeof` results). +// do not use box values types (i.e. Number(), String(), etc.) + +var reverseKeywords = {}; +for (var key in colorName) { + if (colorName.hasOwnProperty(key)) { + reverseKeywords[colorName[key]] = key; + } +} + +var convert = module.exports = { + rgb: {channels: 3, labels: 'rgb'}, + hsl: {channels: 3, labels: 'hsl'}, + hsv: {channels: 3, labels: 'hsv'}, + hwb: {channels: 3, labels: 'hwb'}, + cmyk: {channels: 4, labels: 'cmyk'}, + xyz: {channels: 3, labels: 'xyz'}, + lab: {channels: 3, labels: 'lab'}, + lch: {channels: 3, labels: 'lch'}, + hex: {channels: 1, labels: ['hex']}, + keyword: {channels: 1, labels: ['keyword']}, + ansi16: {channels: 1, labels: ['ansi16']}, + ansi256: {channels: 1, labels: ['ansi256']}, + hcg: {channels: 3, labels: ['h', 'c', 'g']}, + apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, + gray: {channels: 1, labels: ['gray']} +}; + +// hide .channels and .labels properties +for (var model in convert) { + if (convert.hasOwnProperty(model)) { + if (!('channels' in convert[model])) { + throw new Error('missing channels property: ' + model); + } + + if (!('labels' in convert[model])) { + throw new Error('missing channel labels property: ' + model); + } + + if (convert[model].labels.length !== convert[model].channels) { + throw new Error('channel and label counts mismatch: ' + model); + } + + var channels = convert[model].channels; + var labels = convert[model].labels; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], 'channels', {value: channels}); + Object.defineProperty(convert[model], 'labels', {value: labels}); + } +} + +convert.rgb.hsl = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h; + var s; + var l; + + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + + h = Math.min(h * 60, 360); + + if (h < 0) { + h += 360; + } + + l = (min + max) / 2; + + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + + return [h, s * 100, l * 100]; +}; + +convert.rgb.hsv = function (rgb) { + var rdif; + var gdif; + var bdif; + var h; + var s; + + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var v = Math.max(r, g, b); + var diff = v - Math.min(r, g, b); + var diffc = function (c) { + return (v - c) / 6 / diff + 1 / 2; + }; + + if (diff === 0) { + h = s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = (1 / 3) + rdif - bdif; + } else if (b === v) { + h = (2 / 3) + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + + return [ + h * 360, + s * 100, + v * 100 + ]; +}; + +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); +} + +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + + var currentClosestDistance = Infinity; + var currentClosestKeyword; + + for (var keyword in colorName) { + if (colorName.hasOwnProperty(keyword)) { + var value = colorName[keyword]; + + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } + + return currentClosestKeyword; +}; + +convert.keyword.rgb = function (keyword) { + return colorName[keyword]; +}; + +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y * 100, z * 100]; +}; + +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +}; + +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + + return [h, sv * 100, v * 100]; +}; + +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; + + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; + +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [h, sl * 100, l * 100]; +}; + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; + + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + + x *= 95.047; + y *= 100; + z *= 108.883; + + return [x, y, z]; +}; + +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + + if (h < 0) { + h += 360; + } + + c = Math.sqrt(a * a + b * b); + + return [l, c, h]; +}; + +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + + return [l, a, b]; +}; + +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + + value = Math.round(value / 50); + + if (value === 0) { + return 30; + } + + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); + + if (value === 2) { + ansi += 60; + } + + return ansi; +}; + +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; + +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + + if (r > 248) { + return 231; + } + + return Math.round(((r - 8) / 247) * 24) + 232; + } + + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + + return ansi; +}; + +convert.ansi16.rgb = function (args) { + var color = args % 10; + + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + + color = color / 10.5 * 255; + + return [color, color, color]; + } + + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + + return [r, g, b]; +}; + +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + + args -= 16; + + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; + + return [r, g, b]; +}; + +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + + var colorString = match[0]; + + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; + + return [r, g, b]; +}; + +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; + + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + + hue /= 6; + hue %= 1; + + return [hue * 360, chroma * 100, grayscale * 100]; +}; + +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } + + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } + + return [hsl[0], c * 100, f * 100]; +}; + +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + + var c = s * v; + var f = 0; + + if (c < 1.0) { + f = (v - c) / (1 - c); + } + + return [hsv[0], c * 100, f * 100]; +}; + +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; + + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + + mg = (1.0 - c) * g; + + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; + +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var v = c + g * (1.0 - c); + var f = 0; + + if (v > 0.0) { + f = c / v; + } + + return [hcg[0], f * 100, v * 100]; +}; + +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; + + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + + return [hcg[0], s * 100, l * 100]; +}; + +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; + +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; + + if (c < 1) { + g = (v - c) / (1 - c); + } + + return [hwb[0], c * 100, g * 100]; +}; + +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; + +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; + +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; + +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; + +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; + +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; + +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; + +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; +}); +var conversions_1 = conversions.rgb; +var conversions_2 = conversions.hsl; +var conversions_3 = conversions.hsv; +var conversions_4 = conversions.hwb; +var conversions_5 = conversions.cmyk; +var conversions_6 = conversions.xyz; +var conversions_7 = conversions.lab; +var conversions_8 = conversions.lch; +var conversions_9 = conversions.hex; +var conversions_10 = conversions.keyword; +var conversions_11 = conversions.ansi16; +var conversions_12 = conversions.ansi256; +var conversions_13 = conversions.hcg; +var conversions_14 = conversions.apple; +var conversions_15 = conversions.gray; + +/* + this function routes a model to all other models. + + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); + + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; +} + +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop + + graph[fromModel].distance = 0; + + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} + +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + + fn.conversion = path; + return fn; +} + +var route = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; + + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; + + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; + +var convert = {}; + +var models = Object.keys(conversions); + +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + return fn(args); + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + + return result; + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +models.forEach(function (fromModel) { + convert[fromModel] = {}; + + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + + var routes = route(fromModel); + var routeModels = Object.keys(routes); + + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; + + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +var colorConvert = convert; + +var colorName$1 = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/* MIT license */ + + +var colorString = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +}; + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3,4})$/i, + hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr), + hexAlpha = ""; + if (match) { + match = match[1]; + hexAlpha = match[3]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(hex)) { + hexAlpha = match[2]; + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorName$1[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgba, a) { + var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; + return "#" + hexDouble(rgba[0]) + + hexDouble(rgba[1]) + + hexDouble(rgba[2]) + + ( + (a >= 0 && a < 1) + ? hexDouble(Math.round(a * 255)) + : "" + ); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorName$1) { + reverseNames[colorName$1[name]] = name; +} + +/* MIT license */ + + + +var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } + + this.valid = false; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; + + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = colorString.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = colorString.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = colorString.getHwb(obj)) { + this.setValues('hwb', vals); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } + } +}; + +Color.prototype = { + isValid: function () { + return this.valid; + }, + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, + + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, + + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, + + hexString: function () { + return colorString.hexString(this.values.rgb); + }, + rgbString: function () { + return colorString.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return colorString.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return colorString.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return colorString.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return colorString.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return colorString.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return colorString.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, + + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = colorConvert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +var chartjsColor = Color; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ + isArray: function(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ + extend: Object.assign || function(target) { + return helpers.merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + }, + + _deprecated: function(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } + } +}; + +var helpers_core = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +var helpers_easing = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.easingEffects = effects; + +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + +/** + * @namespace Chart.helpers.canvas + */ +var exports$1 = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {number} x - The x axis of the coordinate for the rectangle starting point. + * @param {number} y - The y axis of the coordinate for the rectangle starting point. + * @param {number} width - The rectangle's width. + * @param {number} height - The rectangle's height. + * @param {number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + 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 (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + 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(); + ctx.stroke(); + }, + + /** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ + _isPointInArea: function(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + var stepped = target.steppedLine; + if (stepped) { + if (stepped === 'middle') { + var midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +var helpers_canvas = exports$1; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.clear = exports$1.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports$1.roundedRect.apply(exports$1, arguments); +}; + +var defaults = { + /** + * @private + */ + _set: function(scope, values) { + return helpers_core.merge(this[scope] || (this[scope] = {}), values); + } +}; + +// TODO(v3): remove 'global' from namespace. all default are global and +// there's inconsistency around which options are under 'global' +defaults._set('global', { + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + defaultLineHeight: 1.2, + showLines: true +}); + +var core_defaults = defaults; + +var valueOrDefault = helpers_core.valueOrDefault; + +/** + * Converts the given font object into a CSS font string. + * @param {object} font - A font object. + * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + +/** + * @alias Chart.helpers.options + * @namespace + */ +var helpers_options = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers_core.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var globalDefaults = core_defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ + resolve: function(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && helpers_core.isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } + } +}; + +/** + * @alias Chart.helpers.math + * @namespace + */ +var exports$2 = { + /** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ + _factorize: function(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + } +}; + +var helpers_math = exports$2; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers_core.log10 = exports$2.log10; + +var getRtlAdapter = function(rectX, width) { + return { + x: function(x) { + return rectX + rectX + width - x; + }, + setWidth: function(w) { + width = w; + }, + textAlign: function(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus: function(x, value) { + return x - value; + }, + leftForLtr: function(x, itemWidth) { + return x - itemWidth; + }, + }; +}; + +var getLtrAdapter = function() { + return { + x: function(x) { + return x; + }, + setWidth: function(w) { // eslint-disable-line no-unused-vars + }, + textAlign: function(align) { + return align; + }, + xPlus: function(x, value) { + return x + value; + }, + leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; +}; + +var getAdapter = function(rtl, rectX, width) { + return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); +}; + +var overrideTextDirection = function(ctx, direction) { + var style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; + + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } +}; + +var restoreTextDirection = function(ctx) { + var original = ctx.prevTextDirection; + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } +}; + +var helpers_rtl = { + getRtlAdapter: getAdapter, + overrideTextDirection: overrideTextDirection, + restoreTextDirection: restoreTextDirection, +}; + +var helpers$1 = helpers_core; +var easing = helpers_easing; +var canvas = helpers_canvas; +var options = helpers_options; +var math = helpers_math; +var rtl = helpers_rtl; +helpers$1.easing = easing; +helpers$1.canvas = canvas; +helpers$1.options = options; +helpers$1.math = math; +helpers$1.rtl = rtl; + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = chartjsColor(origin); + if (c0.valid) { + c1 = chartjsColor(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers$1.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers$1.extend(Element.prototype, { + _type: undefined, + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers$1.extend({}, me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = helpers$1.extend({}, model); + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); + } +}); + +Element.extend = helpers$1.inherits; + +var core_element = Element; + +var exports$3 = core_element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +var core_animation = exports$3; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +core_defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers$1.noop, + onComplete: helpers$1.noop + } +}); + +var core_animations = { + animations: [], + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {number} duration - The animation duration in ms. + * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + animation.startTime = Date.now(); + animation.duration = duration; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers$1.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + return; + // TBD: animation should work somehow; what is startDigest? + + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers$1.requestAnimFrame.call(undefined, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + + me.advance(); + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function() { + var animations = this.animations; + var animation, chart, numSteps, nextStep; + var i = 0; + + // 1 animation per chart, so we are looping charts here + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + numSteps = animation.numSteps; + + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); + + helpers$1.callback(animation.render, [chart, animation], chart); + helpers$1.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= numSteps) { + helpers$1.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +var resolve = helpers$1.options.resolve; + +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers$1.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers$1.extend(DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + /** + * Dataset element option keys to be resolved in _resolveDatasetElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' + ], + + /** + * Data element option keys to be resolved in _resolveDataElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' + ], + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + me._type = me.getMeta().type; + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var chart = me.chart; + var scales = chart.scales; + var dataset = me.getDataset(); + var scalesOpts = chart.options.scales; + + if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { + meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { + meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getValueScale: function() { + return this.getScaleForId(this._getValueScaleId()); + }, + + /** + * @private + */ + _getIndexScale: function() { + return this.getScaleForId(this._getIndexScaleId()); + }, + + reset: function() { + this._update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _configure: function() { + var me = this; + me._config = helpers$1.merge({}, [ + me.chart.options.datasets[me._type], + me.getDataset(), + ], { + merger: function(key, target, source) { + if (key !== '_meta' && key !== 'data') { + helpers$1._merger(key, target, source); + } + } + }); + }, + + _update: function(reset) { + var me = this; + me._configure(); + me._cachedDataOpts = null; + me.update(reset); + }, + + update: helpers$1.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + /** + * Returns a set of predefined style properties that should be used to represent the dataset + * or the data if the index is specified + * @param {number} index - data index + * @return {IStyleInterface} style object + */ + getStyle: function(index) { + var me = this; + var meta = me.getMeta(); + var dataset = meta.dataset; + var style; + + me._configure(); + if (dataset && index === undefined) { + style = me._resolveDatasetElementOptions(dataset || {}); + } else { + index = index || 0; + style = me._resolveDataElementOptions(meta.data[index] || {}, index); + } + + if (style.fill === false || style.fill === null) { + style.backgroundColor = style.borderColor; + } + + return style; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element, hover) { + var me = this; + var chart = me.chart; + var datasetOpts = me._config; + var custom = element.custom || {}; + var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; + var elementOptions = me._datasetElementOptions; + var values = {}; + var i, ilen, key, readKey; + + // Scriptable options + var context = { + chart: chart, + dataset: me.getDataset(), + datasetIndex: me.index, + hover: hover + }; + + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; + values[key] = resolve([ + custom[readKey], + datasetOpts[readKey], + options[readKey] + ], context); + } + + return values; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(element, index) { + var me = this; + var custom = element && element.custom; + var cached = me._cachedDataOpts; + if (cached && !custom) { + return cached; + } + var chart = me.chart; + var datasetOpts = me._config; + var options = chart.options.elements[me.dataElementType.prototype._type] || {}; + var elementOptions = me._dataElementOptions; + var values = {}; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: me.getDataset(), + datasetIndex: me.index + }; + + // `resolve` sets cacheable to `false` if any option is indexed or scripted + var info = {cacheable: !custom}; + + var keys, i, ilen, key; + + custom = custom || {}; + + if (helpers$1.isArray(elementOptions)) { + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + values[key] = resolve([ + custom[key], + datasetOpts[key], + options[key] + ], context, index, info); + } + } else { + keys = Object.keys(elementOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + datasetOpts[elementOptions[key]], + datasetOpts[key], + options[key] + ], context, index, info); + } + } + + if (info.cacheable) { + me._cachedDataOpts = Object.freeze(values); + } + + return values; + }, + + removeHoverStyle: function(element) { + helpers$1.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + var getHoverColor = helpers$1.getHoverColor; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); + model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); + model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); + }, + + /** + * @private + */ + _removeDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + + if (element) { + this.removeHoverStyle(element); + } + }, + + /** + * @private + */ + _setDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + var prev = {}; + var i, ilen, key, keys, hoverOptions, model; + + if (!element) { + return; + } + + model = element._model; + hoverOptions = this._resolveDatasetElementOptions(element, true); + + keys = Object.keys(hoverOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + prev[key] = model[key]; + model[key] = hoverOptions[key]; + } + + element.$previousStyle = prev; + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + var count = arguments.length; + this.insertElements(this.getDataset().data.length - count, count); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers$1.inherits; + +var core_datasetController = DatasetController; + +var TAU = Math.PI * 2; + +core_defaults._set('global', { + elements: { + arc: { + backgroundColor: core_defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2, + borderAlign: 'center' + } + } +}); + +function clipArc(ctx, arc) { + var startAngle = arc.startAngle; + var endAngle = arc.endAngle; + var pixelMargin = arc.pixelMargin; + var angleMargin = pixelMargin / arc.outerRadius; + var x = arc.x; + var y = arc.y; + + // Draw an inner border by cliping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (arc.innerRadius > pixelMargin) { + angleMargin = pixelMargin / arc.innerRadius; + ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); + } + ctx.closePath(); + ctx.clip(); +} + +function drawFullCircleBorders(ctx, vm, arc, inner) { + var endAngle = arc.endAngle; + var i; + + if (inner) { + arc.endAngle = arc.startAngle + TAU; + clipArc(ctx, arc); + arc.endAngle = endAngle; + if (arc.endAngle === arc.startAngle && arc.fullCircles) { + arc.endAngle += TAU; + arc.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } +} + +function drawBorder(ctx, vm, arc) { + var inner = vm.borderAlign === 'inner'; + + if (inner) { + ctx.lineWidth = vm.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = vm.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (arc.fullCircles) { + drawFullCircleBorders(ctx, vm, arc, inner); + } + + if (inner) { + clipArc(ctx, arc); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.stroke(); +} + +var element_arc = core_element.extend({ + _type: 'arc', + + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += TAU; + } + while (angle > endAngle) { + angle -= TAU; + } + while (angle < startAngle) { + angle += TAU; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; + var arc = { + x: vm.x, + y: vm.y, + innerRadius: vm.innerRadius, + outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), + pixelMargin: pixelMargin, + startAngle: vm.startAngle, + endAngle: vm.endAngle, + fullCircles: Math.floor(vm.circumference / TAU) + }; + var i; + + ctx.save(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + if (arc.fullCircles) { + arc.endAngle = arc.startAngle + TAU; + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.fill(); + } + arc.endAngle = arc.startAngle + vm.circumference % TAU; + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.fill(); + + if (vm.borderWidth) { + drawBorder(ctx, vm, arc); + } + + ctx.restore(); + } +}); + +var valueOrDefault$1 = helpers$1.valueOrDefault; + +var defaultColor = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: defaultColor, + borderWidth: 3, + borderColor: defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +var element_line = core_element.extend({ + _type: 'line', + + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalDefaults = core_defaults.global; + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var closePath = me._loop; + var index, previous, currentVM; + + if (!points.length) { + return; + } + + if (me._loop) { + for (index = 0; index < points.length; ++index) { + previous = helpers$1.previousItem(points, index); + // If the line has an open path, shift the point array + if (!points[index]._view.skip && previous._view.skip) { + points = points.slice(index).concat(points.slice(0, index)); + closePath = spanGaps; + break; + } + } + // If the line has a close path, add the first point again + if (closePath) { + points.push(points[0]); + } + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + // line dash fix for QML + if(ctx.getLineDash && ctx.getLineDash().length === 0) { + ctx.setLineDash([99999]); + } + + ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + + // First point moves to it's starting position no matter what + currentVM = points[0]._view; + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = 0; + } + + for (index = 1; index < points.length; ++index) { + currentVM = points[index]._view; + previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers$1.canvas.lineTo(ctx, previous._view, currentVM); + } + lastDrawnIndex = index; + } + } + + if (closePath) { + ctx.closePath(); + } + + ctx.stroke(); + ctx.restore(); + } +}); + +var valueOrDefault$2 = helpers$1.valueOrDefault; + +var defaultColor$1 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor$1, + borderColor: defaultColor$1, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +var element_point = core_element.extend({ + _type: 'point', + + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +var defaultColor$2 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaultColor$2, + borderColor: defaultColor$2, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(vm) { + return vm && vm.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + half = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - half; + y2 = vm.y + half; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; + } + + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); + } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +var element_rectangle = core_element.extend({ + _type: 'rectangle', + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.save(); + + if (outer.w !== inner.w || outer.h !== inner.h) { + ctx.beginPath(); + ctx.strokeStyle = vm.borderColor; + ctx.strokeRect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + } + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(inner.x, inner.y, inner.w, inner.h); + + ctx.restore(); + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + return inRange(this._view, mouseX, mouseY); + }, + + inLabelRange: function(mouseX, mouseY) { + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); + }, + + inXRange: function(mouseX) { + return inRange(this._view, mouseX, null); + }, + + inYRange: function(mouseY) { + return inRange(this._view, null, mouseY); + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(vm)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + + return isVertical(vm) + ? vm.width * Math.abs(vm.y - vm.base) + : vm.height * Math.abs(vm.x - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +var elements = {}; +var Arc = element_arc; +var Line = element_line; +var Point = element_point; +var Rectangle = element_rectangle; +elements.Arc = Arc; +elements.Line = Line; +elements.Point = Point; +elements.Rectangle = Rectangle; + +var deprecated = helpers$1._deprecated; +var valueOrDefault$3 = helpers$1.valueOrDefault; + +core_defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +core_defaults._set('global', { + datasets: { + bar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale._length; + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); + } + + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var min = helpers$1.isNullOrUndef(thickness) + ? computeMinSampleSize(ruler.scale, ruler.pixels) + : -1; + var size, ratio; + + if (helpers$1.isNullOrUndef(thickness)) { + size = min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - (curr - Math.min(prev, next)) / 2 * percent; + size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +var controller_bar = core_datasetController.extend({ + + dataElementType: elements.Rectangle, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'barPercentage', + 'barThickness', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength' + ], + + initialize: function() { + var me = this; + var meta, scaleOpts; + + core_datasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + + scaleOpts = me._getIndexScale().options; + deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); + deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); + deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); + deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); + deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveDataElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + if (helpers$1.isArray(dataset.data[index])) { + rectangle._model.borderSkipped = null; + } + + me._updateElementGeometry(rectangle, index, reset, options); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset, options) { + var me = this; + var model = rectangle._model; + var vscale = me._getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index, options); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {number} [last] - The dataset index + * @returns {string[]} The list of stack IDs + * @private + */ + _getStacks: function(last) { + var me = this; + var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); + var stacked = scale.options.stacked; + var ilen = metasets.length; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {number} [datasetIndex] - The dataset index + * @param {string} [name] - The stack name to find + * @returns {number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me._getIndexScale(); + var pixels = []; + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, me.index)); + } + + return { + pixels: pixels, + start: scale._startPixel, + end: scale._endPixel, + stackCount: me.getStackCount(), + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index, options) { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); + var value = scale._parseValue(datasets[datasetIndex].data[index]); + var minBarLength = options.minBarLength; + var stacked = scale.options.stacked; + var stack = me.getMeta().stack; + var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; + var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; + var i, imeta, ivalue, base, head, size, stackLength; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; + + if (imeta.index === datasetIndex) { + break; + } + + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); + ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + length); + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { + var me = this; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + valueOrDefault$3(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.min) && !isNaN(val.max)) { + rects[i].draw(); + } + } + + helpers$1.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveDataElementOptions: function() { + var me = this; + var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); + var indexOpts = me._getIndexScale().options; + var valueOpts = me._getValueScale().options; + + values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); + values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); + values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); + values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); + values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); + + return values; + } + +}); + +var valueOrDefault$4 = helpers$1.valueOrDefault; +var resolve$1 = helpers$1.options.resolve; + +core_defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + +var controller_bubble = core_datasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ], + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers$1.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveDataElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var custom = point.custom || {}; + var data = dataset.data[index] || {}; + var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + // In case values were cached (and thus frozen), we need to clone the values + if (me._cachedDataOpts === values) { + values = helpers$1.extend({}, values); + } + + // Custom radius resolution + values.radius = resolve$1([ + custom.radius, + data.r, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } +}); + +var valueOrDefault$5 = helpers$1.valueOrDefault; + +var PI$1 = Math.PI; +var DOUBLE_PI$1 = PI$1 * 2; +var HALF_PI$1 = PI$1 / 2; + +core_defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: -HALF_PI$1, + + // The total circumference of the chart. + circumference: DOUBLE_PI$1, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers$1.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +var controller_doughnut = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; + var meta = me.getMeta(); + var arcs = meta.data; + var cutout = opts.cutoutPercentage / 100 || 0; + var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); + var maxWidth, maxHeight, i, ilen; + + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < DOUBLE_PI$1) { + var startAngle = opts.rotation % DOUBLE_PI$1; + startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; + var endAngle = startAngle + circumference; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; + var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; + var contains180 = startAngle === -PI$1 || endAngle >= PI$1; + var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + } + + chart.borderWidth = me.getMaxBorderWidth(); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + me.updateElement(arcs[i], i, reset); + } + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers$1.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return DOUBLE_PI$1 * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var me = this; + var max = 0; + var chart = me.chart; + var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + if (i !== me.index) { + controller = meta.controller; + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arc = arcs[i]; + if (controller) { + controller._configure(); + options = controller._resolveDataElementOptions(arc, i); + } else { + options = arc._options; + } + if (options.borderAlign !== 'inner') { + borderWidth = options.borderWidth; + hoverWidth = options.hoverBorderWidth; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + } + return max; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); + } +}); + +core_defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + offset: true, + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + mode: 'index', + axis: 'y' + } +}); + +core_defaults._set('global', { + datasets: { + horizontalBar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +var controller_horizontalBar = controller_bar.extend({ + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + +var valueOrDefault$6 = helpers$1.valueOrDefault; +var resolve$2 = helpers$1.options.resolve; +var isPointInArea = helpers$1.canvas._isPointInArea; + +core_defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +function scaleClip(scale, halfBorderWidth) { + var tickOpts = scale && scale.options.ticks || {}; + var reverse = tickOpts.reverse; + var min = tickOpts.min === undefined ? halfBorderWidth : 0; + var max = tickOpts.max === undefined ? halfBorderWidth : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} + +function defaultClip(xScale, yScale, borderWidth) { + var halfBorderWidth = borderWidth / 2; + var x = scaleClip(xScale, halfBorderWidth); + var y = scaleClip(yScale, halfBorderWidth); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} + +function toClip(value) { + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; +} + + +var controller_line = core_datasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'cubicInterpolationMode', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var config = me._config; + var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); + var i, ilen; + + me._xScale = me.getScaleForId(meta.xAxisID); + me._yScale = me.getScaleForId(meta.yAxisID); + + // Update Line + if (showLine) { + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = me._yScale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var xScale = me._xScale; + var yScale = me._yScale; + var lineModel = meta.dataset._model; + var x, y; + + var options = me._resolveDataElementOptions(point, index); + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), + steppedLine: lineModel ? lineModel.steppedLine : false, + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element) { + var me = this; + var config = me._config; + var custom = element.custom || {}; + var options = me.chart.options; + var lineOptions = options.elements.line; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); + values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); + values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); + + return values; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var yScale = me._yScale; + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; + + if (yScale.options.stacked) { + rightValue = +yScale.getRightValue(value); + metasets = chart._getSortedVisibleDatasetMetas(); + ilen = metasets.length; + + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var lineModel = meta.dataset._model; + var area = chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (lineModel.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (lineModel.cubicInterpolationMode === 'monotone') { + helpers$1.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i)._model, + model, + helpers$1.nextItem(points, i)._model, + lineModel.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + if (isPointInArea(model, area)) { + if (i > 0 && isPointInArea(points[i - 1]._model, area)) { + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var canvas = chart.canvas; + var i = 0; + var ilen = points.length; + var clip; + + if (me._showLine) { + clip = meta.dataset._model.clip; + + helpers$1.canvas.clipArea(chart.ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? canvas.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom + }); + + meta.dataset.draw(); + + helpers$1.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = "rgba(255,0,0,1.)";//valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = 1;//valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); + model.radius = 2;//valueOrDefault$6(options.hoverRadius, options.radius); + }, +}); + +var resolve$3 = helpers$1.options.resolve; + +core_defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +var controller_polarArea = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var arcs = meta.data; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + me.updateElement(arcs[i], i, reset); + } + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max(minSize / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers$1.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + var valueOrDefault = helpers$1.valueOrDefault; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return resolve$3([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); + +core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); +core_defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +var controller_pie = controller_doughnut; + +var valueOrDefault$7 = helpers$1.valueOrDefault; + +core_defaults._set('radar', { + spanGaps: false, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } +}); + +var controller_radar = core_datasetController.extend({ + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderWidth', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var scale = me.chart.scale; + var config = me._config; + var i, ilen; + + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + line._loop = true; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + // Update bezier control points + me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + var options = me._resolveDataElementOptions(point, index); + var lineModel = me.getMeta().dataset._model; + var x = reset ? scale.xCenter : pointPosition.x; + var y = reset ? scale.yCenter : pointPosition.y; + + // Utility + point._scale = scale; + point._options = options; + point._datasetIndex = me.index; + point._index = index; + + // Desired view properties + point._model = { + x: x, // value not used in dataset scale, but we want a consistent API between scales + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), + + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function() { + var me = this; + var config = me._config; + var options = me.chart.options; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); + + return values; + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i, true)._model, + model, + helpers$1.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$7(options.hoverRadius, options.radius); + } +}); + +core_defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +core_defaults._set('global', { + datasets: { + scatter: { + showLine: false + } + } +}); + +// Scatter charts use line controllers +var controller_scatter = controller_line; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +var controllers = { + bar: controller_bar, + bubble: controller_bubble, + doughnut: controller_doughnut, + horizontalBar: controller_horizontalBar, + line: controller_line, + polarArea: controller_polarArea, + pie: controller_pie, + radar: controller_radar, + scatter: controller_scatter +}; + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {object} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers$1.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param {Chart} chart - the chart + * @param {function} handler - the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param {ChartElement[]} items - elements to filter + * @param {object} position - the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param {Chart} chart - the chart to look at elements from + * @param {object} position - the point to be nearest to + * @param {boolean} intersect - if true, only consider items that intersect the position + * @param {function} distanceMetric - function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + 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; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {string} axis - the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +var core_interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + return getNearestItems(chart, position, options.intersect, distanceMetric); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +var extend = helpers$1.extend; + +function filterByPosition(array, position) { + return helpers$1.where(array, function(v) { + return v.pos === position; + }); +} + +function sortByWeight(array, reverse) { + return array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); +} + +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) + }; +} + +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; +} + +core_defaults._set('global', { + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +/** + * @interface ILayoutItem + * @prop {string} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {function} update - Takes two parameters: width and height. Returns size of item + * @prop {function} getPadding - Returns an object with padding on the edges + * @prop {number} width - Width of item. Must be valid after update() + * @prop {number} height - Height of item. Must be valid after update() + * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +var core_layouts = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [{ + z: 0, + draw: function() { + item.draw.apply(item, arguments); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {ILayoutItem} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {ILayoutItem} item - the item to configure with the given options + * @param {object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {number} width - the width to fit into + * @param {number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers$1.options.toPadding(layoutOptions.padding); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h + }; + + // Finally update boxes in chartArea (radial scale for example) + helpers$1.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } +}; + +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +var platform_basic = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; + +var platform_dom$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +'default': platform_dom +}); + +var stylesheet = getCjsExportFromNamespace(platform_dom$1); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers$1.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers$1.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers$1.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +function createDiv(cls) { + var el = document.createElement('div'); + el.className = cls || ''; + return el; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var maxSize = 1000000; + + // NOTE(SB) Don't use innerHTML because it could be considered unsafe. + // https://github.com/chartjs/Chart.js/issues/5902 + var resizer = createDiv(CSS_SIZE_MONITOR); + var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); + var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); + + expand.appendChild(createDiv()); + shrink.appendChild(createDiv()); + + resizer.appendChild(expand); + resizer.appendChild(shrink); + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + addListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + removeListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + var container = chart.options.maintainAspectRatio && node.parentNode; + var w = container ? container.clientWidth : 0; + listener(createEvent('resize', chart)); + if (container && container.clientWidth < w && chart.canvas) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(createEvent('resize', chart)); + } + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +/** + * Injects CSS styles inline if the styles are not already present. + * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the