app/assets/javascripts/pghero/Chart.bundle.js in pghero-2.7.0 vs app/assets/javascripts/pghero/Chart.bundle.js in pghero-2.7.1
- old
+ new
@@ -1,808 +1,1240 @@
/*!
- * Chart.js v2.8.0
+ * Chart.js v2.9.3
* https://www.chartjs.org
* (c) 2019 Chart.js Contributors
* Released under the MIT License
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
-(global.Chart = factory());
+(global = global || self, global.Chart = factory());
}(this, (function () { 'use strict';
-/* MIT license */
+var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
-var conversions = {
- rgb2hsl: rgb2hsl,
- rgb2hsv: rgb2hsv,
- rgb2hwb: rgb2hwb,
- rgb2cmyk: rgb2cmyk,
- rgb2keyword: rgb2keyword,
- rgb2xyz: rgb2xyz,
- rgb2lab: rgb2lab,
- rgb2lch: rgb2lch,
+function commonjsRequire () {
+ throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
+}
- hsl2rgb: hsl2rgb,
- hsl2hsv: hsl2hsv,
- hsl2hwb: hsl2hwb,
- hsl2cmyk: hsl2cmyk,
- hsl2keyword: hsl2keyword,
+function createCommonjsModule(fn, module) {
+ return module = { exports: {} }, fn(module, module.exports), module.exports;
+}
- hsv2rgb: hsv2rgb,
- hsv2hsl: hsv2hsl,
- hsv2hwb: hsv2hwb,
- hsv2cmyk: hsv2cmyk,
- hsv2keyword: hsv2keyword,
+function getCjsExportFromNamespace (n) {
+ return n && n['default'] || n;
+}
- hwb2rgb: hwb2rgb,
- hwb2hsl: hwb2hsl,
- hwb2hsv: hwb2hsv,
- hwb2cmyk: hwb2cmyk,
- hwb2keyword: hwb2keyword,
+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]
+};
- cmyk2rgb: cmyk2rgb,
- cmyk2hsl: cmyk2hsl,
- cmyk2hsv: cmyk2hsv,
- cmyk2hwb: cmyk2hwb,
- cmyk2keyword: cmyk2keyword,
+var conversions = createCommonjsModule(function (module) {
+/* MIT license */
- keyword2rgb: keyword2rgb,
- keyword2hsl: keyword2hsl,
- keyword2hsv: keyword2hsv,
- keyword2hwb: keyword2hwb,
- keyword2cmyk: keyword2cmyk,
- keyword2lab: keyword2lab,
- keyword2xyz: keyword2xyz,
- xyz2rgb: xyz2rgb,
- xyz2lab: xyz2lab,
- xyz2lch: xyz2lch,
+// 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.)
- lab2xyz: lab2xyz,
- lab2rgb: lab2rgb,
- lab2lch: lab2lch,
+var reverseKeywords = {};
+for (var key in colorName) {
+ if (colorName.hasOwnProperty(key)) {
+ reverseKeywords[colorName[key]] = key;
+ }
+}
- lch2lab: lch2lab,
- lch2xyz: lch2xyz,
- lch2rgb: lch2rgb
+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);
+ }
-function rgb2hsl(rgb) {
- var r = rgb[0]/255,
- g = rgb[1]/255,
- b = rgb[2]/255,
- min = Math.min(r, g, b),
- max = Math.max(r, g, b),
- delta = max - min,
- h, s, l;
+ if (!('labels' in convert[model])) {
+ throw new Error('missing channel labels property: ' + model);
+ }
- 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;
+ if (convert[model].labels.length !== convert[model].channels) {
+ throw new Error('channel and label counts mismatch: ' + model);
+ }
- h = Math.min(h * 60, 360);
+ 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});
+ }
+}
- if (h < 0)
- h += 360;
+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;
- l = (min + max) / 2;
+ 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;
+ }
- if (max == min)
- s = 0;
- else if (l <= 0.5)
- s = delta / (max + min);
- else
- s = delta / (2 - max - min);
+ h = Math.min(h * 60, 360);
- return [h, s * 100, l * 100];
-}
+ if (h < 0) {
+ h += 360;
+ }
-function rgb2hsv(rgb) {
- var r = rgb[0],
- g = rgb[1],
- b = rgb[2],
- min = Math.min(r, g, b),
- max = Math.max(r, g, b),
- delta = max - min,
- h, s, v;
+ l = (min + max) / 2;
- if (max == 0)
- s = 0;
- else
- s = (delta/max * 1000)/10;
+ if (max === min) {
+ s = 0;
+ } else if (l <= 0.5) {
+ s = delta / (max + min);
+ } else {
+ s = delta / (2 - max - min);
+ }
- 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;
+ return [h, s * 100, l * 100];
+};
- h = Math.min(h * 60, 360);
+convert.rgb.hsv = function (rgb) {
+ var rdif;
+ var gdif;
+ var bdif;
+ var h;
+ var s;
- if (h < 0)
- h += 360;
+ 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;
+ };
- v = ((max / 255) * 1000) / 10;
+ if (diff === 0) {
+ h = s = 0;
+ } else {
+ s = diff / v;
+ rdif = diffc(r);
+ gdif = diffc(g);
+ bdif = diffc(b);
- return [h, s, v];
-}
+ 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;
+ }
+ }
-function rgb2hwb(rgb) {
- var r = rgb[0],
- g = rgb[1],
- b = rgb[2],
- h = rgb2hsl(rgb)[0],
- w = 1/255 * Math.min(r, Math.min(g, b)),
- b = 1 - 1/255 * Math.max(r, Math.max(g, b));
+ return [
+ h * 360,
+ s * 100,
+ v * 100
+ ];
+};
- return [h, w * 100, b * 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));
-function rgb2cmyk(rgb) {
- var r = rgb[0] / 255,
- g = rgb[1] / 255,
- b = rgb[2] / 255,
- c, m, y, k;
+ b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
- 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];
-}
+ return [h, w * 100, b * 100];
+};
-function rgb2keyword(rgb) {
- return reverseKeywords[JSON.stringify(rgb)];
+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)
+ );
}
-function rgb2xyz(rgb) {
- var r = rgb[0] / 255,
- g = rgb[1] / 255,
- b = rgb[2] / 255;
+convert.rgb.keyword = function (rgb) {
+ var reversed = reverseKeywords[rgb];
+ if (reversed) {
+ return reversed;
+ }
- // 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 currentClosestDistance = Infinity;
+ var currentClosestKeyword;
- 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);
+ for (var keyword in colorName) {
+ if (colorName.hasOwnProperty(keyword)) {
+ var value = colorName[keyword];
- return [x * 100, y *100, z * 100];
-}
+ // Compute comparative distance
+ var distance = comparativeDistance(rgb, value);
-function rgb2lab(rgb) {
- var xyz = rgb2xyz(rgb),
- x = xyz[0],
- y = xyz[1],
- z = xyz[2],
- l, a, b;
+ // Check if its less, if so set as closest
+ if (distance < currentClosestDistance) {
+ currentClosestDistance = distance;
+ currentClosestKeyword = keyword;
+ }
+ }
+ }
- x /= 95.047;
- y /= 100;
- z /= 108.883;
+ return currentClosestKeyword;
+};
- 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);
+convert.keyword.rgb = function (keyword) {
+ return colorName[keyword];
+};
- l = (116 * y) - 16;
- a = 500 * (x - y);
- b = 200 * (y - z);
+convert.rgb.xyz = function (rgb) {
+ var r = rgb[0] / 255;
+ var g = rgb[1] / 255;
+ var b = rgb[2] / 255;
- return [l, a, b];
-}
+ // 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);
-function rgb2lch(args) {
- return lab2lch(rgb2lab(args));
-}
+ 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);
-function hsl2rgb(hsl) {
- var h = hsl[0] / 360,
- s = hsl[1] / 100,
- l = hsl[2] / 100,
- t1, t2, t3, rgb, val;
+ return [x * 100, y * 100, z * 100];
+};
- if (s == 0) {
- val = l * 255;
- return [val, val, val];
- }
+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;
- if (l < 0.5)
- t2 = l * (1 + s);
- else
- t2 = l + s - l * s;
- t1 = 2 * l - t2;
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
- rgb = [0, 0, 0];
- for (var i = 0; i < 3; i++) {
- t3 = h + 1 / 3 * - (i - 1);
- t3 < 0 && t3++;
- t3 > 1 && t3--;
+ 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);
- 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;
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
- rgb[i] = val * 255;
- }
+ return [l, a, b];
+};
- return rgb;
-}
+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;
-function hsl2hsv(hsl) {
- var h = hsl[0],
- s = hsl[1] / 100,
- l = hsl[2] / 100,
- sv, v;
+ if (s === 0) {
+ val = l * 255;
+ return [val, val, val];
+ }
- if(l === 0) {
- // no need to do calc on black
- // also avoids divide by 0 error
- return [0, 0, 0];
- }
+ if (l < 0.5) {
+ t2 = l * (1 + s);
+ } else {
+ t2 = l + s - l * s;
+ }
- l *= 2;
- s *= (l <= 1) ? l : 2 - l;
- v = (l + s) / 2;
- sv = (2 * s) / (l + s);
- return [h, sv * 100, v * 100];
-}
+ t1 = 2 * l - t2;
-function hsl2hwb(args) {
- return rgb2hwb(hsl2rgb(args));
-}
+ 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--;
+ }
-function hsl2cmyk(args) {
- return rgb2cmyk(hsl2rgb(args));
-}
+ 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;
+ }
-function hsl2keyword(args) {
- return rgb2keyword(hsl2rgb(args));
-}
+ rgb[i] = val * 255;
+ }
+ return rgb;
+};
-function hsv2rgb(hsv) {
- var h = hsv[0] / 60,
- s = hsv[1] / 100,
- v = hsv[2] / 100,
- hi = Math.floor(h) % 6;
+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;
- var f = h - Math.floor(h),
- p = 255 * v * (1 - s),
- q = 255 * v * (1 - (s * f)),
- t = 255 * v * (1 - (s * (1 - f))),
- v = 255 * 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);
- 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];
- }
-}
+ return [h, sv * 100, v * 100];
+};
-function hsv2hsl(hsv) {
- var h = hsv[0],
- s = hsv[1] / 100,
- v = hsv[2] / 100,
- sl, l;
+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;
- l = (2 - s) * v;
- sl = s * v;
- sl /= (l <= 1) ? l : 2 - l;
- sl = sl || 0;
- l /= 2;
- return [h, sl * 100, l * 100];
-}
+ 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;
-function hsv2hwb(args) {
- return rgb2hwb(hsv2rgb(args))
-}
+ 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];
+ }
+};
-function hsv2cmyk(args) {
- return rgb2cmyk(hsv2rgb(args));
-}
+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;
-function hsv2keyword(args) {
- return rgb2keyword(hsv2rgb(args));
-}
+ 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
-function hwb2rgb(hwb) {
- var h = hwb[0] / 360,
- wh = hwb[1] / 100,
- bl = hwb[2] / 100,
- ratio = wh + bl,
- i, v, f, n;
+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;
- }
+ // 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
+ i = Math.floor(6 * h);
+ v = 1 - bl;
+ f = 6 * h - i;
- 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;
- }
+ if ((i & 0x01) !== 0) {
+ f = 1 - f;
+ }
- return [r * 255, g * 255, b * 255];
-}
+ n = wh + f * (v - wh); // linear interpolation
-function hwb2hsl(args) {
- return rgb2hsl(hwb2rgb(args));
-}
+ 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;
+ }
-function hwb2hsv(args) {
- return rgb2hsv(hwb2rgb(args));
-}
+ return [r * 255, g * 255, b * 255];
+};
-function hwb2cmyk(args) {
- return rgb2cmyk(hwb2rgb(args));
-}
+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;
-function hwb2keyword(args) {
- return rgb2keyword(hwb2rgb(args));
-}
+ 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);
-function cmyk2rgb(cmyk) {
- var c = cmyk[0] / 100,
- m = cmyk[1] / 100,
- y = cmyk[2] / 100,
- k = cmyk[3] / 100,
- r, g, b;
+ return [r * 255, g * 255, b * 255];
+};
- 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;
-function cmyk2hsl(args) {
- return rgb2hsl(cmyk2rgb(args));
-}
+ 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);
-function cmyk2hsv(args) {
- return rgb2hsv(cmyk2rgb(args));
-}
+ // assume sRGB
+ r = r > 0.0031308
+ ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
+ : r * 12.92;
-function cmyk2hwb(args) {
- return rgb2hwb(cmyk2rgb(args));
-}
+ g = g > 0.0031308
+ ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
+ : g * 12.92;
-function cmyk2keyword(args) {
- return rgb2keyword(cmyk2rgb(args));
-}
+ 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);
-function xyz2rgb(xyz) {
- var x = xyz[0] / 100,
- y = xyz[1] / 100,
- z = xyz[2] / 100,
- r, g, b;
+ return [r * 255, g * 255, b * 255];
+};
- 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);
+convert.xyz.lab = function (xyz) {
+ var x = xyz[0];
+ var y = xyz[1];
+ var z = xyz[2];
+ var l;
+ var a;
+ var b;
- // assume sRGB
- r = r > 0.0031308 ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
- : r = (r * 12.92);
+ x /= 95.047;
+ y /= 100;
+ z /= 108.883;
- g = g > 0.0031308 ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
- : g = (g * 12.92);
+ 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);
- b = b > 0.0031308 ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
- : b = (b * 12.92);
+ l = (116 * y) - 16;
+ a = 500 * (x - y);
+ b = 200 * (y - z);
- 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 [l, a, b];
+};
- return [r * 255, g * 255, b * 255];
-}
+convert.lab.xyz = function (lab) {
+ var l = lab[0];
+ var a = lab[1];
+ var b = lab[2];
+ var x;
+ var y;
+ var z;
-function xyz2lab(xyz) {
- var x = xyz[0],
- y = xyz[1],
- z = xyz[2],
- l, a, b;
+ y = (l + 16) / 116;
+ x = a / 500 + y;
+ z = y - b / 200;
- x /= 95.047;
- y /= 100;
- z /= 108.883;
+ 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 = 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);
+ x *= 95.047;
+ y *= 100;
+ z *= 108.883;
- l = (116 * y) - 16;
- a = 500 * (x - y);
- b = 200 * (y - z);
+ return [x, y, z];
+};
- return [l, a, b];
-}
+convert.lab.lch = function (lab) {
+ var l = lab[0];
+ var a = lab[1];
+ var b = lab[2];
+ var hr;
+ var h;
+ var c;
-function xyz2lch(args) {
- return lab2lch(xyz2lab(args));
-}
+ hr = Math.atan2(b, a);
+ h = hr * 360 / 2 / Math.PI;
-function lab2xyz(lab) {
- var l = lab[0],
- a = lab[1],
- b = lab[2],
- x, y, z, y2;
+ if (h < 0) {
+ h += 360;
+ }
- if (l <= 8) {
- y = (l * 100) / 903.3;
- y2 = (7.787 * (y / 100)) + (16 / 116);
- } else {
- y = 100 * Math.pow((l + 16) / 116, 3);
- y2 = Math.pow(y / 100, 1/3);
- }
+ c = Math.sqrt(a * a + b * b);
- x = x / 95.047 <= 0.008856 ? x = (95.047 * ((a / 500) + y2 - (16 / 116))) / 7.787 : 95.047 * Math.pow((a / 500) + y2, 3);
+ return [l, c, h];
+};
- z = z / 108.883 <= 0.008859 ? z = (108.883 * (y2 - (b / 200) - (16 / 116))) / 7.787 : 108.883 * Math.pow(y2 - (b / 200), 3);
+convert.lch.lab = function (lch) {
+ var l = lch[0];
+ var c = lch[1];
+ var h = lch[2];
+ var a;
+ var b;
+ var hr;
- return [x, y, z];
-}
+ hr = h / 360 * 2 * Math.PI;
+ a = c * Math.cos(hr);
+ b = c * Math.sin(hr);
-function lab2lch(lab) {
- var l = lab[0],
- a = lab[1],
- b = lab[2],
- hr, h, c;
+ return [l, a, b];
+};
- 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.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
-function lab2rgb(args) {
- return xyz2rgb(lab2xyz(args));
-}
+ value = Math.round(value / 50);
-function lch2lab(lch) {
- var l = lch[0],
- c = lch[1],
- h = lch[2],
- a, b, hr;
+ if (value === 0) {
+ return 30;
+ }
- hr = h / 360 * 2 * Math.PI;
- a = c * Math.cos(hr);
- b = c * Math.sin(hr);
- return [l, a, b];
-}
+ var ansi = 30
+ + ((Math.round(b / 255) << 2)
+ | (Math.round(g / 255) << 1)
+ | Math.round(r / 255));
-function lch2xyz(args) {
- return lab2xyz(lch2lab(args));
-}
+ if (value === 2) {
+ ansi += 60;
+ }
-function lch2rgb(args) {
- return lab2rgb(lch2lab(args));
-}
+ return ansi;
+};
-function keyword2rgb(keyword) {
- return cssKeywords[keyword];
-}
+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]);
+};
-function keyword2hsl(args) {
- return rgb2hsl(keyword2rgb(args));
-}
+convert.rgb.ansi256 = function (args) {
+ var r = args[0];
+ var g = args[1];
+ var b = args[2];
-function keyword2hsv(args) {
- return rgb2hsv(keyword2rgb(args));
-}
+ // 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;
+ }
-function keyword2hwb(args) {
- return rgb2hwb(keyword2rgb(args));
-}
+ if (r > 248) {
+ return 231;
+ }
-function keyword2cmyk(args) {
- return rgb2cmyk(keyword2rgb(args));
-}
+ return Math.round(((r - 8) / 247) * 24) + 232;
+ }
-function keyword2lab(args) {
- return rgb2lab(keyword2rgb(args));
-}
+ var ansi = 16
+ + (36 * Math.round(r / 255 * 5))
+ + (6 * Math.round(g / 255 * 5))
+ + Math.round(b / 255 * 5);
-function keyword2xyz(args) {
- return rgb2xyz(keyword2rgb(args));
-}
+ return ansi;
+};
-var cssKeywords = {
- 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]
+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];
};
-var reverseKeywords = {};
-for (var key in cssKeywords) {
- reverseKeywords[JSON.stringify(cssKeywords[key])] = key;
-}
+convert.ansi256.rgb = function (args) {
+ // handle greyscale
+ if (args >= 232) {
+ var c = (args - 232) * 10 + 8;
+ return [c, c, c];
+ }
-var convert = function() {
- return new Converter();
+ 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];
};
-for (var func in conversions) {
- // export Raw versions
- convert[func + "Raw"] = (function(func) {
- // accept array or plain args
- return function(arg) {
- if (typeof arg == "number")
- arg = Array.prototype.slice.call(arguments);
- return conversions[func](arg);
- }
- })(func);
+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 pair = /(\w+)2(\w+)/.exec(func),
- from = pair[1],
- to = pair[2];
+ var string = integer.toString(16).toUpperCase();
+ return '000000'.substring(string.length) + string;
+};
- // export rgb2hsl and ["rgb"]["hsl"]
- convert[from] = convert[from] || {};
+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];
+ }
- convert[from][to] = convert[func] = (function(func) {
- return function(arg) {
- if (typeof arg == "number")
- arg = Array.prototype.slice.call(arguments);
-
- var val = conversions[func](arg);
- if (typeof val == "string" || val === undefined)
- return val; // keyword
+ var colorString = match[0];
- for (var i = 0; i < val.length; i++)
- val[i] = Math.round(val[i]);
- return val;
- }
- })(func);
-}
+ 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;
-/* Converter does lazy conversion and caching */
-var Converter = function() {
- this.convs = {};
+ return [r, g, b];
};
-/* Either get the values for a space or
- set the values for a space, depending on args */
-Converter.prototype.routeSpace = function(space, args) {
- var values = args[0];
- if (values === undefined) {
- // color.rgb()
- return this.getValues(space);
- }
- // color.rgb(10, 10, 10)
- if (typeof values == "number") {
- values = Array.prototype.slice.call(args);
- }
+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;
- return this.setValues(space, values);
+ 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];
};
-
-/* Set the values for a space, invalidating cache */
-Converter.prototype.setValues = function(space, values) {
- this.space = space;
- this.convs = {};
- this.convs[space] = values;
- return this;
+
+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];
};
-/* Get the values for a space. If there's already
- a conversion for the space, fetch it, otherwise
- compute it */
-Converter.prototype.getValues = function(space) {
- var vals = this.convs[space];
- if (!vals) {
- var fspace = this.space,
- from = this.convs[fspace];
- vals = convert[fspace][space](from);
+convert.hsv.hcg = function (hsv) {
+ var s = hsv[1] / 100;
+ var v = hsv[2] / 100;
- this.convs[space] = vals;
- }
- return vals;
+ var c = s * v;
+ var f = 0;
+
+ if (c < 1.0) {
+ f = (v - c) / (1 - c);
+ }
+
+ return [hsv[0], c * 100, f * 100];
};
-["rgb", "hsl", "hsv", "cmyk", "keyword"].forEach(function(space) {
- Converter.prototype[space] = function(vals) {
- return this.routeSpace(space, arguments);
- };
+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 = {
+var colorName$1 = {
"aliceblue": [240, 248, 255],
"antiquewhite": [250, 235, 215],
"aqua": [0, 255, 255],
"aquamarine": [127, 255, 212],
"azure": [240, 255, 255],
@@ -1021,11 +1453,11 @@
}
else if (match = string.match(keyword)) {
if (match[1] == "transparent") {
return [0, 0, 0, 0];
}
- rgb = colorName[match[1]];
+ rgb = colorName$1[match[1]];
if (!rgb) {
return;
}
}
@@ -1183,12 +1615,12 @@
}
//create a list of reverse color names
var reverseNames = {};
-for (var name in colorName) {
- reverseNames[colorName[name]] = name;
+for (var name in colorName$1) {
+ reverseNames[colorName$1[name]] = name;
}
/* MIT license */
@@ -1948,18 +2380,16 @@
* @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: function(target) {
- var setFn = function(value, key) {
- target[key] = value;
- };
- for (var i = 1, ilen = arguments.length; i < ilen; ++i) {
- helpers.each(arguments[i], setFn);
- }
- return target;
+ 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
*/
@@ -1981,10 +2411,17 @@
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;
@@ -2342,11 +2779,15 @@
var rad = (rotation || 0) * RAD_PER_DEG;
if (style && typeof style === 'object') {
type = style.toString();
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
- ctx.drawImage(style, x - style.width / 2, y - style.height / 2, style.width, style.height);
+ 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) {
@@ -2534,10 +2975,12 @@
_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,
@@ -2592,12 +3035,10 @@
case 'px':
return value;
case '%':
value /= 100;
break;
- default:
- break;
}
return size * value;
},
@@ -2658,40 +3099,180 @@
* @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) {
+ 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;
@@ -2744,19 +3325,20 @@
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.clone(me._model);
+ me._view = helpers$1.extend({}, me._model);
}
me._start = {};
return me;
},
@@ -2766,11 +3348,11 @@
var start = me._start;
var view = me._view;
// No animation -> No Transition
if (!model || ease === 1) {
- me._view = model;
+ me._view = helpers$1.extend({}, model);
me._start = null;
return me;
}
if (!view) {
@@ -2800,32 +3382,32 @@
Element.extend = helpers$1.inherits;
var core_element = Element;
-var exports$2 = core_element.extend({
+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$2;
+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$2.prototype, 'animationObject', {
+Object.defineProperty(exports$3.prototype, 'animationObject', {
get: function() {
return this;
}
});
@@ -2833,11 +3415,11 @@
* 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$2.prototype, 'chartInstance', {
+Object.defineProperty(exports$3.prototype, 'chartInstance', {
get: function() {
return this.chart;
},
set: function(value) {
this.chart = value;
@@ -3051,32 +3633,65 @@
* 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 me.chart.scales)) {
- meta.xAxisID = dataset.xAxisID || me.chart.options.scales.xAxes[0].id;
+ 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 me.chart.scales)) {
- meta.yAxisID = dataset.yAxisID || me.chart.options.scales.yAxes[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];
@@ -3117,11 +3732,11 @@
_getIndexScale: function() {
return this.getScaleForId(this._getIndexScaleId());
},
reset: function() {
- this.update(true);
+ this._update(true);
},
/**
* @private
*/
@@ -3193,10 +3808,35 @@
// 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 || [];
@@ -3225,10 +3865,131 @@
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;
},
@@ -3251,10 +4012,46 @@
},
/**
* @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;
@@ -3316,10 +4113,12 @@
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',
@@ -3327,11 +4126,88 @@
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));
@@ -3342,24 +4218,24 @@
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 angle = pointRelativePosition.angle;
var distance = pointRelativePosition.distance;
// Sanitise angle range
var startAngle = vm.startAngle;
var endAngle = vm.endAngle;
while (endAngle < startAngle) {
- endAngle += 2.0 * Math.PI;
+ endAngle += TAU;
}
while (angle > endAngle) {
- angle -= 2.0 * Math.PI;
+ angle -= TAU;
}
while (angle < startAngle) {
- angle += 2.0 * Math.PI;
+ 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);
@@ -3396,55 +4272,48 @@
},
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
- var sA = vm.startAngle;
- var eA = vm.endAngle;
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
- var angleMargin;
+ 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(vm.x, vm.y, Math.max(vm.outerRadius - pixelMargin, 0), sA, eA);
- ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
+ 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.fillStyle = vm.backgroundColor;
ctx.fill();
if (vm.borderWidth) {
- if (vm.borderAlign === 'inner') {
- // 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();
- angleMargin = pixelMargin / vm.outerRadius;
- ctx.arc(vm.x, vm.y, vm.outerRadius, sA - angleMargin, eA + angleMargin);
- if (vm.innerRadius > pixelMargin) {
- angleMargin = pixelMargin / vm.innerRadius;
- ctx.arc(vm.x, vm.y, vm.innerRadius - pixelMargin, eA + angleMargin, sA - angleMargin, true);
- } else {
- ctx.arc(vm.x, vm.y, pixelMargin, eA + Math.PI / 2, sA - Math.PI / 2);
- }
- ctx.closePath();
- ctx.clip();
-
- ctx.beginPath();
- ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
- ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
- ctx.closePath();
-
- ctx.lineWidth = vm.borderWidth * 2;
- ctx.lineJoin = 'round';
- } else {
- ctx.lineWidth = vm.borderWidth;
- ctx.lineJoin = 'bevel';
- }
-
- ctx.strokeStyle = vm.borderColor;
- ctx.stroke();
+ drawBorder(ctx, vm, arc);
}
ctx.restore();
}
});
@@ -3469,26 +4338,44 @@
}
}
});
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 index, current, previous, currentVM;
+ var closePath = me._loop;
+ var index, previous, currentVM;
- // If we are looping, adding the first point again
- if (me._loop && points.length) {
- points.push(points[0]);
+ 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;
@@ -3502,39 +4389,38 @@
ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);
ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
// Stroke Line
ctx.beginPath();
- lastDrawnIndex = -1;
- for (index = 0; index < points.length; ++index) {
- current = points[index];
- previous = helpers$1.previousItem(points, index);
- currentVM = current._view;
+ // 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;
+ }
- // First point moves to it's starting position no matter what
- if (index === 0) {
- if (!currentVM.skip) {
+ 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);
- lastDrawnIndex = index;
+ } else {
+ // Line to next point
+ helpers$1.canvas.lineTo(ctx, previous._view, currentVM);
}
- } else {
- previous = lastDrawnIndex === -1 ? previous : 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, current._view);
- }
- lastDrawnIndex = index;
- }
+ lastDrawnIndex = index;
}
}
+ if (closePath) {
+ ctx.closePath();
+ }
+
ctx.stroke();
ctx.restore();
}
});
@@ -3567,10 +4453,12 @@
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;
},
@@ -3749,10 +4637,12 @@
&& (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;
@@ -3838,22 +4728,21 @@
elements.Arc = Arc;
elements.Line = Line;
elements.Point = Point;
elements.Rectangle = Rectangle;
-var resolve$1 = helpers$1.options.resolve;
+var deprecated = helpers$1._deprecated;
+var valueOrDefault$3 = helpers$1.valueOrDefault;
core_defaults._set('bar', {
hover: {
mode: 'label'
},
scales: {
xAxes: [{
type: 'category',
- categoryPercentage: 0.8,
- barPercentage: 0.9,
offset: true,
gridLines: {
offsetGridLines: true
}
}],
@@ -3862,26 +4751,34 @@
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.isHorizontal() ? scale.width : scale.height;
- var ticks = scale.getTicks();
+ 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 = ticks.length; i < ilen; ++i) {
+ for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
curr = scale.getPixelForTick(i);
- min = i > 0 ? Math.min(min, curr - prev) : min;
+ min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;
prev = curr;
}
return min;
}
@@ -3894,14 +4791,17 @@
*/
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 = ruler.min * options.categoryPercentage;
+ 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.
@@ -3953,19 +4853,41 @@
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;
+ 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;
@@ -3980,11 +4902,11 @@
updateElement: function(rectangle, index, reset) {
var me = this;
var meta = me.getMeta();
var dataset = me.getDataset();
- var options = me._resolveElementOptions(rectangle, index);
+ 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;
@@ -3995,27 +4917,31 @@
borderWidth: options.borderWidth,
datasetLabel: dataset.label,
label: me.chart.data.labels[index]
};
- me._updateElementGeometry(rectangle, index, reset);
+ 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) {
+ _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);
- var ipixels = me.calculateBarIndexPixels(me.index, index, ruler);
+ 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;
@@ -4029,25 +4955,31 @@
* @returns {string[]} The list of stack IDs
* @private
*/
_getStacks: function(last) {
var me = this;
- var chart = me.chart;
var scale = me._getIndexScale();
+ var metasets = scale._getMatchingVisibleMetas(me._type);
var stacked = scale.options.stacked;
- var ilen = last === undefined ? chart.data.datasets.length : last + 1;
+ var ilen = metasets.length;
var stacks = [];
var i, meta;
for (i = 0; i < ilen; ++i) {
- meta = chart.getDatasetMeta(i);
- if (meta.bar && chart.isDatasetVisible(i) &&
- (stacked === false ||
- (stacked === true && stacks.indexOf(meta.stack) === -1) ||
- (stacked === undefined && (meta.stack === undefined || stacks.indexOf(meta.stack) === -1)))) {
+ 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;
},
@@ -4081,78 +5013,72 @@
* @private
*/
getRuler: function() {
var me = this;
var scale = me._getIndexScale();
- var stackCount = me.getStackCount();
- var datasetIndex = me.index;
- var isHorizontal = scale.isHorizontal();
- var start = isHorizontal ? scale.left : scale.top;
- var end = start + (isHorizontal ? scale.width : scale.height);
var pixels = [];
- var i, ilen, min;
+ var i, ilen;
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
- pixels.push(scale.getPixelForValue(null, i, datasetIndex));
+ pixels.push(scale.getPixelForValue(null, i, me.index));
}
- min = helpers$1.isNullOrUndef(scale.options.barThickness)
- ? computeMinSampleSize(scale, pixels)
- : -1;
-
return {
- min: min,
pixels: pixels,
- start: start,
- end: end,
- stackCount: stackCount,
+ 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) {
+ calculateBarValuePixels: function(datasetIndex, index, options) {
var me = this;
var chart = me.chart;
- var meta = me.getMeta();
var scale = me._getValueScale();
var isHorizontal = scale.isHorizontal();
var datasets = chart.data.datasets;
- var value = +scale.getRightValue(datasets[datasetIndex].data[index]);
- var minBarLength = scale.options.minBarLength;
+ 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 = meta.stack;
- var start = 0;
- var i, imeta, ivalue, base, head, size;
+ 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 < datasetIndex; ++i) {
- imeta = chart.getDatasetMeta(i);
+ for (i = 0; i < ilen; ++i) {
+ imeta = metasets[i];
- if (imeta.bar &&
- imeta.stack === stack &&
- imeta.controller._getValueScaleId() === scale.id &&
- chart.isDatasetVisible(i)) {
+ if (imeta.index === datasetIndex) {
+ break;
+ }
- ivalue = +scale.getRightValue(datasets[i].data[index]);
- if ((value < 0 && ivalue < 0) || (value >= 0 && ivalue > 0)) {
+ 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 + value);
+ head = scale.getPixelForValue(start + length);
size = head - base;
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
size = minBarLength;
- if (value >= 0 && !isHorizontal || value < 0 && isHorizontal) {
+ if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
head = base - minBarLength;
} else {
head = base + minBarLength;
}
}
@@ -4166,21 +5092,20 @@
},
/**
* @private
*/
- calculateBarIndexPixels: function(datasetIndex, index, ruler) {
+ calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
var me = this;
- var options = ruler.scale.options;
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(
- helpers$1.valueOrDefault(options.maxBarThickness, Infinity),
+ valueOrDefault$3(options.maxBarThickness, Infinity),
range.chunk * range.ratio);
return {
base: center - size / 2,
head: center + size / 2,
@@ -4199,61 +5124,41 @@
var i = 0;
helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);
for (; i < ilen; ++i) {
- if (!isNaN(scale.getRightValue(dataset.data[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
*/
- _resolveElementOptions: function(rectangle, index) {
+ _resolveDataElementOptions: function() {
var me = this;
- var chart = me.chart;
- var datasets = chart.data.datasets;
- var dataset = datasets[me.index];
- var custom = rectangle.custom || {};
- var options = chart.options.elements.rectangle;
- var values = {};
- var i, ilen, key;
+ var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments));
+ var indexOpts = me._getIndexScale().options;
+ var valueOpts = me._getValueScale().options;
- // Scriptable options
- var context = {
- chart: chart,
- dataIndex: index,
- dataset: dataset,
- datasetIndex: me.index
- };
+ 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);
- var keys = [
- 'backgroundColor',
- 'borderColor',
- 'borderSkipped',
- 'borderWidth'
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$1([
- custom[key],
- dataset[key],
- options[key]
- ], context, index);
- }
-
return values;
}
+
});
-var valueOrDefault$3 = helpers$1.valueOrDefault;
-var resolve$2 = helpers$1.options.resolve;
+var valueOrDefault$4 = helpers$1.valueOrDefault;
+var resolve$1 = helpers$1.options.resolve;
core_defaults._set('bubble', {
hover: {
mode: 'single'
},
@@ -4291,10 +5196,26 @@
* @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();
@@ -4313,11 +5234,11 @@
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._resolveElementOptions(point, index);
+ 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);
@@ -4356,75 +5277,58 @@
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
- model.backgroundColor = valueOrDefault$3(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
- model.borderColor = valueOrDefault$3(options.hoverBorderColor, getHoverColor(options.borderColor));
- model.borderWidth = valueOrDefault$3(options.hoverBorderWidth, options.borderWidth);
+ 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
*/
- _resolveElementOptions: function(point, index) {
+ _resolveDataElementOptions: function(point, index) {
var me = this;
var chart = me.chart;
- var datasets = chart.data.datasets;
- var dataset = datasets[me.index];
+ var dataset = me.getDataset();
var custom = point.custom || {};
- var options = chart.options.elements.point;
- var data = dataset.data[index];
- var values = {};
- var i, ilen, key;
+ 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
};
- var keys = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
- 'hoverRadius',
- 'hitRadius',
- 'pointStyle',
- 'rotation'
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$2([
- custom[key],
- dataset[key],
- options[key]
- ], context, 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$2([
+ values.radius = resolve$1([
custom.radius,
- data ? data.r : undefined,
- dataset.radius,
- options.radius
+ data.r,
+ me._config.radius,
+ chart.options.elements.point.radius
], context, index);
return values;
}
});
-var resolve$3 = helpers$1.options.resolve;
-var valueOrDefault$4 = helpers$1.valueOrDefault;
+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
@@ -4432,51 +5336,45 @@
},
hover: {
mode: 'single'
},
legendCallback: function(chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
-
+ 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 (var i = 0; i < datasets[0].data.length; ++i) {
- text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
+ 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]) {
- text.push(labels[i]);
+ listItem.appendChild(document.createTextNode(labels[i]));
}
- text.push('</li>');
}
}
- text.push('</ul>');
- return text.join('');
+ 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 ds = data.datasets[0];
- var arc = meta.data[i];
- var custom = arc && arc.custom || {};
- var arcOpts = chart.options.elements.arc;
- var fill = resolve$3([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i);
- var stroke = resolve$3([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i);
- var bw = resolve$3([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i);
+ var style = meta.controller.getStyle(i);
return {
text: label,
- fillStyle: fill,
- strokeStyle: stroke,
- lineWidth: bw,
- hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+ 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
};
});
@@ -4504,14 +5402,14 @@
// 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: Math.PI * -0.5,
+ rotation: -HALF_PI$1,
// The total circumference of the chart.
- circumference: Math.PI * 2.0,
+ circumference: DOUBLE_PI$1,
// Need to override these to give a nice default
tooltips: {
callbacks: {
title: function() {
@@ -4540,10 +5438,23 @@
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) {
@@ -4558,50 +5469,56 @@
update: function(reset) {
var me = this;
var chart = me.chart;
var chartArea = chart.chartArea;
var opts = chart.options;
- var availableWidth = chartArea.right - chartArea.left;
- var availableHeight = chartArea.bottom - chartArea.top;
- var minSize = Math.min(availableWidth, availableHeight);
- var offset = {x: 0, y: 0};
+ var ratioX = 1;
+ var ratioY = 1;
+ var offsetX = 0;
+ var offsetY = 0;
var meta = me.getMeta();
var arcs = meta.data;
- var cutoutPercentage = opts.cutoutPercentage;
+ var cutout = opts.cutoutPercentage / 100 || 0;
var circumference = opts.circumference;
var chartWeight = me._getRingWeight(me.index);
- var i, ilen;
+ var maxWidth, maxHeight, i, ilen;
- // If the chart's circumference isn't a full circle, calculate minSize as a ratio of the width/height of the arc
- if (circumference < Math.PI * 2.0) {
- var startAngle = opts.rotation % (Math.PI * 2.0);
- startAngle += Math.PI * 2.0 * (startAngle >= Math.PI ? -1 : startAngle < -Math.PI ? 1 : 0);
+ // 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 start = {x: Math.cos(startAngle), y: Math.sin(startAngle)};
- var end = {x: Math.cos(endAngle), y: Math.sin(endAngle)};
- var contains0 = (startAngle <= 0 && endAngle >= 0) || (startAngle <= Math.PI * 2.0 && Math.PI * 2.0 <= endAngle);
- var contains90 = (startAngle <= Math.PI * 0.5 && Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 2.5 && Math.PI * 2.5 <= endAngle);
- var contains180 = (startAngle <= -Math.PI && -Math.PI <= endAngle) || (startAngle <= Math.PI && Math.PI <= endAngle);
- var contains270 = (startAngle <= -Math.PI * 0.5 && -Math.PI * 0.5 <= endAngle) || (startAngle <= Math.PI * 1.5 && Math.PI * 1.5 <= endAngle);
- var cutout = cutoutPercentage / 100.0;
- var min = {x: contains180 ? -1 : Math.min(start.x * (start.x < 0 ? 1 : cutout), end.x * (end.x < 0 ? 1 : cutout)), y: contains270 ? -1 : Math.min(start.y * (start.y < 0 ? 1 : cutout), end.y * (end.y < 0 ? 1 : cutout))};
- var max = {x: contains0 ? 1 : Math.max(start.x * (start.x > 0 ? 1 : cutout), end.x * (end.x > 0 ? 1 : cutout)), y: contains90 ? 1 : Math.max(start.y * (start.y > 0 ? 1 : cutout), end.y * (end.y > 0 ? 1 : cutout))};
- var size = {width: (max.x - min.x) * 0.5, height: (max.y - min.y) * 0.5};
- minSize = Math.min(availableWidth / size.width, availableHeight / size.height);
- offset = {x: (max.x + min.x) * -0.5, y: (max.y + min.y) * -0.5};
+ 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._resolveElementOptions(arcs[i], i);
+ arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
}
chart.borderWidth = me.getMaxBorderWidth();
- chart.outerRadius = Math.max((minSize - chart.borderWidth) / 2, 0);
- chart.innerRadius = Math.max(cutoutPercentage ? (chart.outerRadius / 100) * (cutoutPercentage) : 0, 0);
+ 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 = offset.x * chart.outerRadius;
- chart.offsetY = offset.y * chart.outerRadius;
+ 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);
@@ -4620,11 +5537,11 @@
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 / (2.0 * Math.PI));
+ 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, {
@@ -4686,11 +5603,11 @@
},
calculateCircumference: function(value) {
var total = this.getMeta().total;
if (total > 0 && !isNaN(value)) {
- return (Math.PI * 2.0) * (Math.abs(value) / total);
+ return DOUBLE_PI$1 * (Math.abs(value) / total);
}
return 0;
},
// gets the max border or hover width to properly scale pie charts
@@ -4718,11 +5635,16 @@
return 0;
}
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
arc = arcs[i];
- options = controller ? controller._resolveElementOptions(arc, i) : arc._options;
+ 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;
@@ -4744,58 +5666,16 @@
backgroundColor: model.backgroundColor,
borderColor: model.borderColor,
borderWidth: model.borderWidth,
};
- 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.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);
},
/**
- * @private
- */
- _resolveElementOptions: function(arc, index) {
- var me = this;
- var chart = me.chart;
- var dataset = me.getDataset();
- var custom = arc.custom || {};
- var options = chart.options.elements.arc;
- var values = {};
- var i, ilen, key;
-
- // Scriptable options
- var context = {
- chart: chart,
- dataIndex: index,
- dataset: dataset,
- datasetIndex: me.index
- };
-
- var keys = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'borderAlign',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$3([
- custom[key],
- dataset[key],
- options[key]
- ], context, index);
- }
-
- return values;
- },
-
- /**
* 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;
@@ -4811,11 +5691,11 @@
/**
* @private
*/
_getRingWeight: function(dataSetIndex) {
- return Math.max(valueOrDefault$4(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
+ 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
@@ -4838,12 +5718,10 @@
}],
yAxes: [{
type: 'category',
position: 'left',
- categoryPercentage: 0.8,
- barPercentage: 0.9,
offset: true,
gridLines: {
offsetGridLines: true
}
}]
@@ -4859,10 +5737,19 @@
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() {
@@ -4875,12 +5762,12 @@
_getIndexScaleId: function() {
return this.getMeta().yAxisID;
}
});
-var valueOrDefault$5 = helpers$1.valueOrDefault;
-var resolve$4 = helpers$1.options.resolve;
+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,
@@ -4899,44 +5786,120 @@
id: 'y-axis-0'
}]
}
});
-function lineEnabled(dataset, options) {
- return valueOrDefault$5(dataset.showLine, options.showLines);
+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 scale = me.getScaleForId(meta.yAxisID);
- var dataset = me.getDataset();
- var showLine = lineEnabled(dataset, me.chart.options);
+ 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 ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
- dataset.lineTension = dataset.tension;
+ if (config.tension !== undefined && config.lineTension === undefined) {
+ config.lineTension = config.tension;
}
// Utility
- line._scale = scale;
+ line._scale = me._yScale;
line._datasetIndex = me.index;
// Data
line._children = points;
// Model
- line._model = me._resolveLineOptions(line);
+ line._model = me._resolveDatasetElementOptions(line);
line.pivot();
}
// Update Points
@@ -4959,16 +5922,16 @@
var meta = me.getMeta();
var custom = point.custom || {};
var dataset = me.getDataset();
var datasetIndex = me.index;
var value = dataset.data[index];
- var yScale = me.getScaleForId(meta.yAxisID);
- var xScale = me.getScaleForId(meta.xAxisID);
+ var xScale = me._xScale;
+ var yScale = me._yScale;
var lineModel = meta.dataset._model;
var x, y;
- var options = me._resolvePointOptions(point, index);
+ 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
@@ -4988,139 +5951,74 @@
pointStyle: options.pointStyle,
rotation: options.rotation,
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
- tension: valueOrDefault$5(custom.tension, lineModel ? lineModel.tension : 0),
+ tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),
steppedLine: lineModel ? lineModel.steppedLine : false,
// Tooltip
hitRadius: options.hitRadius
};
},
/**
* @private
*/
- _resolvePointOptions: function(element, index) {
+ _resolveDatasetElementOptions: function(element) {
var me = this;
- var chart = me.chart;
- var dataset = chart.data.datasets[me.index];
+ var config = me._config;
var custom = element.custom || {};
- var options = chart.options.elements.point;
- var values = {};
- var i, ilen, key;
+ var options = me.chart.options;
+ var lineOptions = options.elements.line;
+ var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
- // Scriptable options
- var context = {
- chart: chart,
- dataIndex: index,
- dataset: dataset,
- datasetIndex: me.index
- };
-
- var ELEMENT_OPTIONS = {
- backgroundColor: 'pointBackgroundColor',
- borderColor: 'pointBorderColor',
- borderWidth: 'pointBorderWidth',
- hitRadius: 'pointHitRadius',
- hoverBackgroundColor: 'pointHoverBackgroundColor',
- hoverBorderColor: 'pointHoverBorderColor',
- hoverBorderWidth: 'pointHoverBorderWidth',
- hoverRadius: 'pointHoverRadius',
- pointStyle: 'pointStyle',
- radius: 'pointRadius',
- rotation: 'pointRotation'
- };
- var keys = Object.keys(ELEMENT_OPTIONS);
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$4([
- custom[key],
- dataset[ELEMENT_OPTIONS[key]],
- dataset[key],
- options[key]
- ], context, index);
- }
-
- return values;
- },
-
- /**
- * @private
- */
- _resolveLineOptions: function(element) {
- var me = this;
- var chart = me.chart;
- var dataset = chart.data.datasets[me.index];
- var custom = element.custom || {};
- var options = chart.options;
- var elementOptions = options.elements.line;
- var values = {};
- var i, ilen, key;
-
- var keys = [
- 'backgroundColor',
- 'borderWidth',
- 'borderColor',
- 'borderCapStyle',
- 'borderDash',
- 'borderDashOffset',
- 'borderJoinStyle',
- 'fill',
- 'cubicInterpolationMode'
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$4([
- custom[key],
- dataset[key],
- elementOptions[key]
- ]);
- }
-
// 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$5(dataset.spanGaps, options.spanGaps);
- values.tension = valueOrDefault$5(dataset.lineTension, elementOptions.tension);
- values.steppedLine = resolve$4([custom.steppedLine, dataset.steppedLine, elementOptions.stepped]);
+ 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 meta = me.getMeta();
- var yScale = me.getScaleForId(meta.yAxisID);
+ var yScale = me._yScale;
var sumPos = 0;
var sumNeg = 0;
- var i, ds, dsMeta;
+ var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen;
if (yScale.options.stacked) {
- for (i = 0; i < datasetIndex; i++) {
- ds = chart.data.datasets[i];
- dsMeta = chart.getDatasetMeta(i);
- if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id && chart.isDatasetVisible(i)) {
- var stackedRightValue = Number(yScale.getRightValue(ds.data[index]));
+ 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;
}
}
}
- var rightValue = Number(yScale.getRightValue(value));
if (rightValue < 0) {
return yScale.getPixelForValue(sumNeg + rightValue);
}
return yScale.getPixelForValue(sumPos + rightValue);
}
-
return yScale.getPixelForValue(value);
},
updateBezierControlPoints: function() {
var me = this;
@@ -5181,22 +6079,23 @@
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var points = meta.data || [];
var area = chart.chartArea;
- var ilen = points.length;
- var halfBorderWidth;
+ var canvas = chart.canvas;
var i = 0;
+ var ilen = points.length;
+ var clip;
- if (lineEnabled(me.getDataset(), chart.options)) {
- halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
+ if (me._showLine) {
+ clip = meta.dataset._model.clip;
helpers$1.canvas.clipArea(chart.ctx, {
- left: area.left,
- right: area.right,
- top: area.top - halfBorderWidth,
- bottom: area.bottom + halfBorderWidth
+ 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);
@@ -5221,18 +6120,18 @@
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
- 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);
- model.radius = valueOrDefault$5(options.hoverRadius, options.radius);
+ model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
+ model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));
+ model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);
+ model.radius = valueOrDefault$6(options.hoverRadius, options.radius);
},
});
-var resolve$5 = helpers$1.options.resolve;
+var resolve$3 = helpers$1.options.resolve;
core_defaults._set('polarArea', {
scale: {
type: 'radialLinear',
angleLines: {
@@ -5255,51 +6154,45 @@
animateScale: true
},
startAngle: -0.5 * Math.PI,
legendCallback: function(chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
-
+ 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 (var i = 0; i < datasets[0].data.length; ++i) {
- text.push('<li><span style="background-color:' + datasets[0].backgroundColor[i] + '"></span>');
+ 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]) {
- text.push(labels[i]);
+ listItem.appendChild(document.createTextNode(labels[i]));
}
- text.push('</li>');
}
}
- text.push('</ul>');
- return text.join('');
+ 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 ds = data.datasets[0];
- var arc = meta.data[i];
- var custom = arc.custom || {};
- var arcOpts = chart.options.elements.arc;
- var fill = resolve$5([custom.backgroundColor, ds.backgroundColor, arcOpts.backgroundColor], undefined, i);
- var stroke = resolve$5([custom.borderColor, ds.borderColor, arcOpts.borderColor], undefined, i);
- var bw = resolve$5([custom.borderWidth, ds.borderWidth, arcOpts.borderWidth], undefined, i);
+ var style = meta.controller.getStyle(i);
return {
text: label,
- fillStyle: fill,
- strokeStyle: stroke,
- lineWidth: bw,
- hidden: isNaN(ds.data[i]) || meta.data[i].hidden,
+ 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
};
});
@@ -5339,10 +6232,37 @@
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;
@@ -5361,11 +6281,11 @@
angles[i] = angle;
start += angle;
}
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
- arcs[i]._options = me._resolveElementOptions(arcs[i], i);
+ arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
me.updateElement(arcs[i], i, reset);
}
},
/**
@@ -5467,52 +6387,10 @@
},
/**
* @private
*/
- _resolveElementOptions: function(arc, index) {
- var me = this;
- var chart = me.chart;
- var dataset = me.getDataset();
- var custom = arc.custom || {};
- var options = chart.options.elements.arc;
- var values = {};
- var i, ilen, key;
-
- // Scriptable options
- var context = {
- chart: chart,
- dataIndex: index,
- dataset: dataset,
- datasetIndex: me.index
- };
-
- var keys = [
- 'backgroundColor',
- 'borderColor',
- 'borderWidth',
- 'borderAlign',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$5([
- custom[key],
- dataset[key],
- options[key]
- ], context, index);
- }
-
- return values;
- },
-
- /**
- * @private
- */
_computeAngle: function(index) {
var me = this;
var count = this.getMeta().count;
var dataset = me.getDataset();
var meta = me.getMeta();
@@ -5527,11 +6405,11 @@
dataIndex: index,
dataset: dataset,
datasetIndex: me.index
};
- return resolve$5([
+ return resolve$3([
me.chart.options.elements.arc.angle,
(2 * Math.PI) / count
], context, index);
}
});
@@ -5542,54 +6420,99 @@
});
// Pie charts are Doughnut chart with different defaults
var controller_pie = controller_doughnut;
-var valueOrDefault$6 = helpers$1.valueOrDefault;
-var resolve$6 = helpers$1.options.resolve;
+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 dataset = me.getDataset();
+ var config = me._config;
var i, ilen;
// Compatibility: If the properties are defined with only the old name, use those values
- if ((dataset.tension !== undefined) && (dataset.lineTension === undefined)) {
- dataset.lineTension = dataset.tension;
+ 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._resolveLineOptions(line);
+ line._model = me._resolveDatasetElementOptions(line);
line.pivot();
// Update Points
for (i = 0, ilen = points.length; i < ilen; ++i) {
@@ -5609,11 +6532,11 @@
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._resolvePointOptions(point, 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
@@ -5632,109 +6555,46 @@
pointStyle: options.pointStyle,
rotation: options.rotation,
backgroundColor: options.backgroundColor,
borderColor: options.borderColor,
borderWidth: options.borderWidth,
- tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),
+ tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0),
// Tooltip
hitRadius: options.hitRadius
};
},
/**
* @private
*/
- _resolvePointOptions: function(element, index) {
+ _resolveDatasetElementOptions: function() {
var me = this;
- var chart = me.chart;
- var dataset = chart.data.datasets[me.index];
- var custom = element.custom || {};
- var options = chart.options.elements.point;
- var values = {};
- var i, ilen, key;
+ var config = me._config;
+ var options = me.chart.options;
+ var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
- // Scriptable options
- var context = {
- chart: chart,
- dataIndex: index,
- dataset: dataset,
- datasetIndex: me.index
- };
+ values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps);
+ values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension);
- var ELEMENT_OPTIONS = {
- backgroundColor: 'pointBackgroundColor',
- borderColor: 'pointBorderColor',
- borderWidth: 'pointBorderWidth',
- hitRadius: 'pointHitRadius',
- hoverBackgroundColor: 'pointHoverBackgroundColor',
- hoverBorderColor: 'pointHoverBorderColor',
- hoverBorderWidth: 'pointHoverBorderWidth',
- hoverRadius: 'pointHoverRadius',
- pointStyle: 'pointStyle',
- radius: 'pointRadius',
- rotation: 'pointRotation'
- };
- var keys = Object.keys(ELEMENT_OPTIONS);
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$6([
- custom[key],
- dataset[ELEMENT_OPTIONS[key]],
- dataset[key],
- options[key]
- ], context, index);
- }
-
return values;
},
- /**
- * @private
- */
- _resolveLineOptions: function(element) {
- var me = this;
- var chart = me.chart;
- var dataset = chart.data.datasets[me.index];
- var custom = element.custom || {};
- var options = chart.options.elements.line;
- var values = {};
- var i, ilen, key;
-
- var keys = [
- 'backgroundColor',
- 'borderWidth',
- 'borderColor',
- 'borderCapStyle',
- 'borderDash',
- 'borderDashOffset',
- 'borderJoinStyle',
- 'fill'
- ];
-
- for (i = 0, ilen = keys.length; i < ilen; ++i) {
- key = keys[i];
- values[key] = resolve$6([
- custom[key],
- dataset[key],
- options[key]
- ]);
- }
-
- values.tension = valueOrDefault$6(dataset.lineTension, options.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) {
@@ -5764,14 +6624,14 @@
borderColor: model.borderColor,
borderWidth: model.borderWidth,
radius: model.radius
};
- model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
- model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));
- model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);
- model.radius = valueOrDefault$6(options.hoverRadius, options.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: {
@@ -5789,12 +6649,10 @@
type: 'linear',
position: 'left'
}]
},
- showLines: false,
-
tooltips: {
callbacks: {
title: function() {
return ''; // doesn't make sense for scatter since data are formatted as a point
},
@@ -5803,10 +6661,18 @@
}
}
}
});
+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
@@ -5845,21 +6711,17 @@
* 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 datasets = chart.data.datasets;
- var meta, i, j, ilen, jlen;
+ var metasets = chart._getSortedVisibleDatasetMetas();
+ var metadata, i, j, ilen, jlen, element;
- for (i = 0, ilen = datasets.length; i < ilen; ++i) {
- if (!chart.isDatasetVisible(i)) {
- continue;
- }
-
- meta = chart.getDatasetMeta(i);
- for (j = 0, jlen = meta.data.length; j < jlen; ++j) {
- var element = meta.data[j];
+ 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);
}
}
}
@@ -5940,19 +6802,16 @@
if (!items.length) {
return [];
}
- chart.data.datasets.forEach(function(dataset, datasetIndex) {
- if (chart.isDatasetVisible(datasetIndex)) {
- var meta = chart.getDatasetMeta(datasetIndex);
- var element = meta.data[items[0]._index];
+ 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);
- }
+ // don't count items that are skipped (null data)
+ if (element && !element._view.skip) {
+ elements.push(element);
}
});
return elements;
}
@@ -6129,61 +6988,199 @@
return items;
}
}
};
+var extend = helpers$1.extend;
+
function filterByPosition(array, position) {
return helpers$1.where(array, function(v) {
- return v.position === position;
+ return v.pos === position;
});
}
function sortByWeight(array, reverse) {
- array.forEach(function(v, i) {
- v._tmpIndex_ = i;
- return v;
- });
- array.sort(function(a, b) {
+ return array.sort(function(a, b) {
var v0 = reverse ? b : a;
var v1 = reverse ? a : b;
return v0.weight === v1.weight ?
- v0._tmpIndex_ - v1._tmpIndex_ :
+ v0.index - v1.index :
v0.weight - v1.weight;
});
- array.forEach(function(v) {
- delete v._tmpIndex_;
- });
}
-function findMaxPadding(boxes) {
- var top = 0;
- var left = 0;
- var bottom = 0;
- var right = 0;
- helpers$1.each(boxes, function(box) {
- if (box.getPadding) {
- var boxPadding = box.getPadding();
- top = Math.max(top, boxPadding.top);
- left = Math.max(left, boxPadding.left);
- bottom = Math.max(bottom, boxPadding.bottom);
- right = Math.max(right, boxPadding.right);
- }
- });
+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 {
- top: top,
- left: left,
- bottom: bottom,
- right: right
+ leftAndTop: left.concat(top),
+ rightAndBottom: right.concat(bottom),
+ chartArea: filterByPosition(layoutBoxes, 'chartArea'),
+ vertical: left.concat(right),
+ horizontal: top.concat(bottom)
};
}
-function addSizeByPosition(boxes, size) {
- helpers$1.each(boxes, function(box) {
- size[box.position] += box.isHorizontal() ? box.height : box.width;
- });
+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,
@@ -6229,10 +7226,18 @@
// 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);
},
/**
@@ -6279,31 +7284,17 @@
return;
}
var layoutOptions = chart.options.layout || {};
var padding = helpers$1.options.toPadding(layoutOptions.padding);
- var leftPadding = padding.left;
- var rightPadding = padding.right;
- var topPadding = padding.top;
- var bottomPadding = padding.bottom;
- var leftBoxes = filterByPosition(chart.boxes, 'left');
- var rightBoxes = filterByPosition(chart.boxes, 'right');
- var topBoxes = filterByPosition(chart.boxes, 'top');
- var bottomBoxes = filterByPosition(chart.boxes, 'bottom');
- var chartAreaBoxes = filterByPosition(chart.boxes, 'chartArea');
+ var availableWidth = width - padding.width;
+ var availableHeight = height - padding.height;
+ var boxes = buildLayoutBoxes(chart.boxes);
+ var verticalBoxes = boxes.vertical;
+ var horizontalBoxes = boxes.horizontal;
- // Sort boxes by weight. A higher weight is further away from the chart area
- sortByWeight(leftBoxes, true);
- sortByWeight(rightBoxes, false);
- sortByWeight(topBoxes, true);
- sortByWeight(bottomBoxes, false);
-
- var verticalBoxes = leftBoxes.concat(rightBoxes);
- var horizontalBoxes = topBoxes.concat(bottomBoxes);
- var outerBoxes = verticalBoxes.concat(horizontalBoxes);
-
// 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
@@ -6326,205 +7317,61 @@
// | | | B1 | |
// |----------------------------------------------------|
// | B2 (Full Width) |
// |----------------------------------------------------|
//
- // What we do to find the best sizing, we do the following
- // 1. Determine the minimum size of the chart area.
- // 2. Split the remaining width equally between each vertical axis
- // 3. Split the remaining height equally between each horizontal axis
- // 4. Give each layout the maximum size it can be. The layout will return it's minimum size
- // 5. Adjust the sizes of each axis based on it's minimum reported size.
- // 6. Refit each axis
- // 7. Position each axis in the final location
- // 8. Tell the chart the final location of the chart area
- // 9. Tell any axes that overlay the chart area the positions of the chart area
- // Step 1
- var chartWidth = width - leftPadding - rightPadding;
- var chartHeight = height - topPadding - bottomPadding;
- var chartAreaWidth = chartWidth / 2; // min 50%
+ 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);
- // Step 2
- var verticalBoxWidth = (width - chartAreaWidth) / verticalBoxes.length;
+ setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
- // Step 3
- // TODO re-limit horizontal axis height (this limit has affected only padding calculation since PR 1837)
- // var horizontalBoxHeight = (height - chartAreaHeight) / horizontalBoxes.length;
+ // First fit vertical boxes
+ fitBoxes(verticalBoxes, chartArea, params);
- // Step 4
- var maxChartAreaWidth = chartWidth;
- var maxChartAreaHeight = chartHeight;
- var outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding};
- var minBoxSizes = [];
- var maxPadding;
-
- function getMinimumBoxSize(box) {
- var minSize;
- var isHorizontal = box.isHorizontal();
-
- if (isHorizontal) {
- minSize = box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2);
- maxChartAreaHeight -= minSize.height;
- } else {
- minSize = box.update(verticalBoxWidth, maxChartAreaHeight);
- maxChartAreaWidth -= minSize.width;
- }
-
- minBoxSizes.push({
- horizontal: isHorizontal,
- width: minSize.width,
- box: box,
- });
+ // Then fit horizontal boxes
+ if (fitBoxes(horizontalBoxes, chartArea, params)) {
+ // if the area changed, re-fit vertical boxes
+ fitBoxes(verticalBoxes, chartArea, params);
}
- helpers$1.each(outerBoxes, getMinimumBoxSize);
+ handleMaxPadding(chartArea);
- // If a horizontal box has padding, we move the left boxes over to avoid ugly charts (see issue #2478)
- maxPadding = findMaxPadding(outerBoxes);
+ // Finally place the boxes to correct coordinates
+ placeBoxes(boxes.leftAndTop, chartArea, params);
- // At this point, maxChartAreaHeight and maxChartAreaWidth are the size the chart area could
- // be if the axes are drawn at their minimum sizes.
- // Steps 5 & 6
+ // Move to opposite side of chart
+ chartArea.x += chartArea.w;
+ chartArea.y += chartArea.h;
- // Function to fit a box
- function fitBox(box) {
- var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function(minBox) {
- return minBox.box === box;
- });
+ placeBoxes(boxes.rightAndBottom, chartArea, params);
- if (minBoxSize) {
- if (minBoxSize.horizontal) {
- var scaleMargin = {
- left: Math.max(outerBoxSizes.left, maxPadding.left),
- right: Math.max(outerBoxSizes.right, maxPadding.right),
- top: 0,
- bottom: 0
- };
-
- // Don't use min size here because of label rotation. When the labels are rotated, their rotation highly depends
- // on the margin. Sometimes they need to increase in size slightly
- box.update(box.fullWidth ? chartWidth : maxChartAreaWidth, chartHeight / 2, scaleMargin);
- } else {
- box.update(minBoxSize.width, maxChartAreaHeight);
- }
- }
- }
-
- // Update, and calculate the left and right margins for the horizontal boxes
- helpers$1.each(verticalBoxes, fitBox);
- addSizeByPosition(verticalBoxes, outerBoxSizes);
-
- // Set the Left and Right margins for the horizontal boxes
- helpers$1.each(horizontalBoxes, fitBox);
- addSizeByPosition(horizontalBoxes, outerBoxSizes);
-
- function finalFitVerticalBox(box) {
- var minBoxSize = helpers$1.findNextWhere(minBoxSizes, function(minSize) {
- return minSize.box === box;
- });
-
- var scaleMargin = {
- left: 0,
- right: 0,
- top: outerBoxSizes.top,
- bottom: outerBoxSizes.bottom
- };
-
- if (minBoxSize) {
- box.update(minBoxSize.width, maxChartAreaHeight, scaleMargin);
- }
- }
-
- // Let the left layout know the final margin
- helpers$1.each(verticalBoxes, finalFitVerticalBox);
-
- // Recalculate because the size of each layout might have changed slightly due to the margins (label rotation for instance)
- outerBoxSizes = {top: topPadding, left: leftPadding, bottom: bottomPadding, right: rightPadding};
- addSizeByPosition(outerBoxes, outerBoxSizes);
-
- // We may be adding some padding to account for rotated x axis labels
- var leftPaddingAddition = Math.max(maxPadding.left - outerBoxSizes.left, 0);
- outerBoxSizes.left += leftPaddingAddition;
- outerBoxSizes.right += Math.max(maxPadding.right - outerBoxSizes.right, 0);
-
- var topPaddingAddition = Math.max(maxPadding.top - outerBoxSizes.top, 0);
- outerBoxSizes.top += topPaddingAddition;
- outerBoxSizes.bottom += Math.max(maxPadding.bottom - outerBoxSizes.bottom, 0);
-
- // Figure out if our chart area changed. This would occur if the dataset layout label rotation
- // changed due to the application of the margins in step 6. Since we can only get bigger, this is safe to do
- // without calling `fit` again
- var newMaxChartAreaHeight = height - outerBoxSizes.top - outerBoxSizes.bottom;
- var newMaxChartAreaWidth = width - outerBoxSizes.left - outerBoxSizes.right;
-
- if (newMaxChartAreaWidth !== maxChartAreaWidth || newMaxChartAreaHeight !== maxChartAreaHeight) {
- helpers$1.each(verticalBoxes, function(box) {
- box.height = newMaxChartAreaHeight;
- });
-
- helpers$1.each(horizontalBoxes, function(box) {
- if (!box.fullWidth) {
- box.width = newMaxChartAreaWidth;
- }
- });
-
- maxChartAreaHeight = newMaxChartAreaHeight;
- maxChartAreaWidth = newMaxChartAreaWidth;
- }
-
- // Step 7 - Position the boxes
- var left = leftPadding + leftPaddingAddition;
- var top = topPadding + topPaddingAddition;
-
- function placeBox(box) {
- if (box.isHorizontal()) {
- box.left = box.fullWidth ? leftPadding : outerBoxSizes.left;
- box.right = box.fullWidth ? width - rightPadding : outerBoxSizes.left + maxChartAreaWidth;
- box.top = top;
- box.bottom = top + box.height;
-
- // Move to next point
- top = box.bottom;
-
- } else {
-
- box.left = left;
- box.right = left + box.width;
- box.top = outerBoxSizes.top;
- box.bottom = outerBoxSizes.top + maxChartAreaHeight;
-
- // Move to next point
- left = box.right;
- }
- }
-
- helpers$1.each(leftBoxes.concat(topBoxes), placeBox);
-
- // Account for chart width and height
- left += maxChartAreaWidth;
- top += maxChartAreaHeight;
-
- helpers$1.each(rightBoxes, placeBox);
- helpers$1.each(bottomBoxes, placeBox);
-
- // Step 8
chart.chartArea = {
- left: outerBoxSizes.left,
- top: outerBoxSizes.top,
- right: outerBoxSizes.left + maxChartAreaWidth,
- bottom: outerBoxSizes.top + maxChartAreaHeight
+ left: chartArea.left,
+ top: chartArea.top,
+ right: chartArea.left + chartArea.w,
+ bottom: chartArea.top + chartArea.h
};
- // Step 9
- helpers$1.each(chartAreaBoxes, function(box) {
- box.left = chart.chartArea.left;
- box.top = chart.chartArea.top;
- box.right = chart.chartArea.right;
- box.bottom = chart.chartArea.bottom;
-
- box.update(maxChartAreaWidth, maxChartAreaHeight);
+ // 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);
});
}
};
/**
@@ -6544,27 +7391,14 @@
};
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({
-default: platform_dom
+__proto__: null,
+'default': platform_dom
});
-var commonjsGlobal = 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 stylesheet = getCjsExportFromNamespace(platform_dom$1);
var EXPANDO_KEY = '$chartjs';
var CSS_PREFIX = 'chartjs-';
var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor';
@@ -6850,21 +7684,26 @@
if (resizer && resizer.parentNode) {
resizer.parentNode.removeChild(resizer);
}
}
-function injectCSS(platform, css) {
+/**
+ * Injects CSS styles inline if the styles are not already present.
+ * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>.
+ * @param {string} css - the CSS to be injected.
+ */
+function injectCSS(rootNode, css) {
// https://stackoverflow.com/q/3922139
- var style = platform._style || document.createElement('style');
- if (!platform._style) {
- platform._style = style;
+ var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {});
+ if (!expando.containsStyles) {
+ expando.containsStyles = true;
css = '/* Chart.js */\n' + css;
+ var style = document.createElement('style');
style.setAttribute('type', 'text/css');
- document.getElementsByTagName('head')[0].appendChild(style);
+ style.appendChild(document.createTextNode(css));
+ rootNode.appendChild(style);
}
-
- style.appendChild(document.createTextNode(css));
}
var platform_dom$2 = {
/**
* When `true`, prevents the automatic injection of the stylesheet required to
@@ -6881,22 +7720,22 @@
* @private
*/
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
/**
+ * Initializes resources that depend on platform options.
+ * @param {HTMLCanvasElement} canvas - The Canvas element.
* @private
*/
- _ensureLoaded: function() {
- if (this._loaded) {
- return;
- }
-
- this._loaded = true;
-
- // https://github.com/chartjs/Chart.js/issues/5208
+ _ensureLoaded: function(canvas) {
if (!this.disableCSSInjection) {
- injectCSS(this, stylesheet);
+ // If the canvas is in a shadow DOM, then the styles must also be inserted
+ // into the same shadow DOM.
+ // https://github.com/chartjs/Chart.js/issues/5763
+ var root = canvas.getRootNode ? canvas.getRootNode() : document;
+ var targetNode = root.host ? root : document.head;
+ injectCSS(targetNode, stylesheet);
}
},
acquireContext: function(item, config) {
if (typeof item === 'string') {
@@ -6914,22 +7753,21 @@
// To prevent canvas fingerprinting, some add-ons undefine the getContext
// method, for example: https://github.com/kkapsner/CanvasBlocker
// https://github.com/chartjs/Chart.js/issues/2807
var context = item && item.getContext && item.getContext('2d');
- // Load platform resources on first chart creation, to make possible to change
- // platform options after importing the library (e.g. `disableCSSInjection`).
- this._ensureLoaded();
-
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
// inside an iframe or when running in a protected environment. We could guess the
// types from their toString() value but let's keep things flexible and assume it's
// a sufficient condition if the item has a context2D which has item as `canvas`.
// https://github.com/chartjs/Chart.js/issues/3887
// https://github.com/chartjs/Chart.js/issues/4102
// https://github.com/chartjs/Chart.js/issues/4152
if (context && context.canvas === item) {
+ // Load platform resources on first chart creation, to make it possible to
+ // import the library before setting platform options.
+ this._ensureLoaded(item);
initCanvas(item, config);
return context;
}
return null;
@@ -7278,11 +8116,12 @@
core_layouts.addBox(chart, scale);
});
}
};
-var valueOrDefault$7 = helpers$1.valueOrDefault;
+var valueOrDefault$8 = helpers$1.valueOrDefault;
+var getRtlHelper = helpers$1.rtl.getRtlAdapter;
core_defaults._set('global', {
tooltips: {
enabled: true,
custom: null,
@@ -7516,32 +8355,36 @@
xPadding: tooltipOpts.xPadding,
yPadding: tooltipOpts.yPadding,
xAlign: tooltipOpts.xAlign,
yAlign: tooltipOpts.yAlign,
+ // Drawing direction and text direction
+ rtl: tooltipOpts.rtl,
+ textDirection: tooltipOpts.textDirection,
+
// Body
bodyFontColor: tooltipOpts.bodyFontColor,
- _bodyFontFamily: valueOrDefault$7(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
- _bodyFontStyle: valueOrDefault$7(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
+ _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
+ _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
_bodyAlign: tooltipOpts.bodyAlign,
- bodyFontSize: valueOrDefault$7(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
+ bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
bodySpacing: tooltipOpts.bodySpacing,
// Title
titleFontColor: tooltipOpts.titleFontColor,
- _titleFontFamily: valueOrDefault$7(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
- _titleFontStyle: valueOrDefault$7(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
- titleFontSize: valueOrDefault$7(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
+ _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
+ _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
+ titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
_titleAlign: tooltipOpts.titleAlign,
titleSpacing: tooltipOpts.titleSpacing,
titleMarginBottom: tooltipOpts.titleMarginBottom,
// Footer
footerFontColor: tooltipOpts.footerFontColor,
- _footerFontFamily: valueOrDefault$7(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
- _footerFontStyle: valueOrDefault$7(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
- footerFontSize: valueOrDefault$7(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
+ _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
+ _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
+ footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
_footerAlign: tooltipOpts.footerAlign,
footerSpacing: tooltipOpts.footerSpacing,
footerMarginTop: tooltipOpts.footerMarginTop,
// Appearance
@@ -7764,11 +8607,11 @@
*/
function getBeforeAfterBodyLines(callback) {
return pushOrConcat([], splitNewlines(callback));
}
-var exports$3 = core_element.extend({
+var exports$4 = core_element.extend({
initialize: function() {
this._model = getBaseModel(this._options);
this._lastActive = [];
},
@@ -8022,29 +8865,32 @@
return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
},
drawTitle: function(pt, vm, ctx) {
var title = vm.title;
+ var length = title.length;
+ var titleFontSize, titleSpacing, i;
- if (title.length) {
+ if (length) {
+ var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
+
pt.x = getAlignedX(vm, vm._titleAlign);
- ctx.textAlign = vm._titleAlign;
- ctx.textBaseline = 'top';
+ ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
+ ctx.textBaseline = 'middle';
- var titleFontSize = vm.titleFontSize;
- var titleSpacing = vm.titleSpacing;
+ titleFontSize = vm.titleFontSize;
+ titleSpacing = vm.titleSpacing;
ctx.fillStyle = vm.titleFontColor;
ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
- var i, len;
- for (i = 0, len = title.length; i < len; ++i) {
- ctx.fillText(title[i], pt.x, pt.y);
+ for (i = 0; i < length; ++i) {
+ ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
- if (i + 1 === title.length) {
+ if (i + 1 === length) {
pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
}
}
}
},
@@ -8053,64 +8899,72 @@
var bodyFontSize = vm.bodyFontSize;
var bodySpacing = vm.bodySpacing;
var bodyAlign = vm._bodyAlign;
var body = vm.body;
var drawColorBoxes = vm.displayColors;
- var labelColors = vm.labelColors;
var xLinePadding = 0;
var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
- var textColor;
- ctx.textAlign = bodyAlign;
- ctx.textBaseline = 'top';
- ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+ var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
- pt.x = getAlignedX(vm, bodyAlign);
-
- // Before Body
var fillLineOfText = function(line) {
- ctx.fillText(line, pt.x + xLinePadding, pt.y);
+ ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
pt.y += bodyFontSize + bodySpacing;
};
+ var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
+ var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
+
+ ctx.textAlign = bodyAlign;
+ ctx.textBaseline = 'middle';
+ ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+
+ pt.x = getAlignedX(vm, bodyAlignForCalculation);
+
// Before body lines
ctx.fillStyle = vm.bodyFontColor;
helpers$1.each(vm.beforeBody, fillLineOfText);
- xLinePadding = drawColorBoxes && bodyAlign !== 'right'
+ xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
: 0;
// Draw body lines now
- helpers$1.each(body, function(bodyItem, i) {
+ for (i = 0, ilen = body.length; i < ilen; ++i) {
+ bodyItem = body[i];
textColor = vm.labelTextColors[i];
+ labelColors = vm.labelColors[i];
+
ctx.fillStyle = textColor;
helpers$1.each(bodyItem.before, fillLineOfText);
- helpers$1.each(bodyItem.lines, function(line) {
+ lines = bodyItem.lines;
+ for (j = 0, jlen = lines.length; j < jlen; ++j) {
// Draw Legend-like boxes if needed
if (drawColorBoxes) {
+ var rtlColorX = rtlHelper.x(colorX);
+
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx.fillStyle = vm.legendColorBackground;
- ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize);
+ ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
// Border
ctx.lineWidth = 1;
- ctx.strokeStyle = labelColors[i].borderColor;
- ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize);
+ ctx.strokeStyle = labelColors.borderColor;
+ ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
// Inner square
- ctx.fillStyle = labelColors[i].backgroundColor;
- ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
+ ctx.fillStyle = labelColors.backgroundColor;
+ ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
ctx.fillStyle = textColor;
}
- fillLineOfText(line);
- });
+ fillLineOfText(lines[j]);
+ }
helpers$1.each(bodyItem.after, fillLineOfText);
- });
+ }
// Reset back to 0 for after body
xLinePadding = 0;
// After body lines
@@ -8118,25 +8972,31 @@
pt.y -= bodySpacing; // Remove last body spacing
},
drawFooter: function(pt, vm, ctx) {
var footer = vm.footer;
+ var length = footer.length;
+ var footerFontSize, i;
- if (footer.length) {
+ if (length) {
+ var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
+
pt.x = getAlignedX(vm, vm._footerAlign);
pt.y += vm.footerMarginTop;
- ctx.textAlign = vm._footerAlign;
- ctx.textBaseline = 'top';
+ ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
+ ctx.textBaseline = 'middle';
+ footerFontSize = vm.footerFontSize;
+
ctx.fillStyle = vm.footerFontColor;
- ctx.font = helpers$1.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+ ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
- helpers$1.each(footer, function(line) {
- ctx.fillText(line, pt.x, pt.y);
- pt.y += vm.footerFontSize + vm.footerSpacing;
- });
+ for (i = 0; i < length; ++i) {
+ ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
+ pt.y += footerFontSize + vm.footerSpacing;
+ }
}
},
drawBackground: function(pt, vm, ctx, tooltipSize) {
ctx.fillStyle = vm.backgroundColor;
@@ -8212,19 +9072,23 @@
this.drawBackground(pt, vm, ctx, tooltipSize);
// Draw Title, Body, and Footer
pt.y += vm.yPadding;
+ helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection);
+
// Titles
this.drawTitle(pt, vm, ctx);
// Body
this.drawBody(pt, vm, ctx);
// Footer
this.drawFooter(pt, vm, ctx);
+ helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection);
+
ctx.restore();
}
},
/**
@@ -8243,10 +9107,13 @@
// Find Active Elements for tooltips
if (e.type === 'mouseout') {
me._active = [];
} else {
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
+ if (options.reverse) {
+ me._active.reverse();
+ }
}
// Remember Last Actives
changed = !helpers$1.arrayEquals(me._active, me._lastActive);
@@ -8272,14 +9139,14 @@
/**
* @namespace Chart.Tooltip.positioners
*/
var positioners_1 = positioners;
-var core_tooltip = exports$3;
+var core_tooltip = exports$4;
core_tooltip.positioners = positioners_1;
-var valueOrDefault$8 = helpers$1.valueOrDefault;
+var valueOrDefault$9 = helpers$1.valueOrDefault;
core_defaults._set('global', {
elements: {},
events: [
'mousemove',
@@ -8316,11 +9183,11 @@
target[key] = [];
}
for (i = 0; i < slen; ++i) {
scale = source[key][i];
- type = valueOrDefault$8(scale.type, key === 'xAxes' ? 'category' : 'linear');
+ type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear');
if (i >= target[key].length) {
target[key].push({});
}
@@ -8400,14 +9267,35 @@
// Tooltip
chart.tooltip._options = newOptions.tooltips;
chart.tooltip.initialize();
}
+function nextAvailableScaleId(axesOpts, prefix, index) {
+ var id;
+ var hasId = function(obj) {
+ return obj.id === id;
+ };
+
+ do {
+ id = prefix + index++;
+ } while (helpers$1.findIndex(axesOpts, hasId) >= 0);
+
+ return id;
+}
+
function positionIsHorizontal(position) {
return position === 'top' || position === 'bottom';
}
+function compare2Level(l1, l2) {
+ return function(a, b) {
+ return a[l1] === b[l1]
+ ? a[l2] - b[l2]
+ : a[l1] - b[l1];
+ };
+}
+
var Chart = function(item, config) {
this.construct(item, config);
return this;
};
@@ -8432,10 +9320,11 @@
me.width = width;
me.height = height;
me.aspectRatio = height ? width / height : null;
me.options = config.options;
me._bufferedRender = false;
+ me._layers = [];
/**
* Provided for backward compatibility, Chart and Chart.Controller have been merged,
* the "instance" still need to be defined since it might be called from plugins.
* @prop Chart#chart
@@ -8488,13 +9377,10 @@
if (me.options.responsive) {
// Initial resize before chart draws (must be silent to preserve initial animations).
me.resize(true);
}
- // Make sure scales have IDs and are built before we build any controllers.
- me.ensureScalesHaveIDs();
- me.buildOrUpdateScales();
me.initToolTip();
// After init plugin notification
core_plugins.notify(me, 'afterInit');
@@ -8557,15 +9443,19 @@
var options = this.options;
var scalesOptions = options.scales || {};
var scaleOptions = options.scale;
helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) {
- xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
+ if (!xAxisOptions.id) {
+ xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index);
+ }
});
helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) {
- yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
+ if (!yAxisOptions.id) {
+ yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index);
+ }
});
if (scaleOptions) {
scaleOptions.id = scaleOptions.id || 'scale';
}
@@ -8605,11 +9495,11 @@
}
helpers$1.each(items, function(item) {
var scaleOptions = item.options;
var id = scaleOptions.id;
- var scaleType = valueOrDefault$8(scaleOptions.type, item.dtype);
+ var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype);
if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
scaleOptions.position = item.dposition;
}
@@ -8657,34 +9547,39 @@
},
buildOrUpdateControllers: function() {
var me = this;
var newControllers = [];
+ var datasets = me.data.datasets;
+ var i, ilen;
- helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
- var meta = me.getDatasetMeta(datasetIndex);
+ for (i = 0, ilen = datasets.length; i < ilen; i++) {
+ var dataset = datasets[i];
+ var meta = me.getDatasetMeta(i);
var type = dataset.type || me.config.type;
if (meta.type && meta.type !== type) {
- me.destroyDatasetMeta(datasetIndex);
- meta = me.getDatasetMeta(datasetIndex);
+ me.destroyDatasetMeta(i);
+ meta = me.getDatasetMeta(i);
}
meta.type = type;
+ meta.order = dataset.order || 0;
+ meta.index = i;
if (meta.controller) {
- meta.controller.updateIndex(datasetIndex);
+ meta.controller.updateIndex(i);
meta.controller.linkScales();
} else {
var ControllerClass = controllers[meta.type];
if (ControllerClass === undefined) {
throw new Error('"' + meta.type + '" is not a chart type.');
}
- meta.controller = new ControllerClass(me, datasetIndex);
+ meta.controller = new ControllerClass(me, i);
newControllers.push(meta.controller);
}
- }, me);
+ }
return newControllers;
},
/**
@@ -8706,10 +9601,11 @@
this.tooltip.initialize();
},
update: function(config) {
var me = this;
+ var i, ilen;
if (!config || typeof config !== 'object') {
// backwards compatibility
config = {
duration: config,
@@ -8732,13 +9628,13 @@
// Make sure dataset controllers are updated and new controllers are reset
var newControllers = me.buildOrUpdateControllers();
// Make sure all dataset controllers have correct meta data counts
- helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
- me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
- }, me);
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
+ me.getDatasetMeta(i).controller.buildOrUpdateElements();
+ }
me.updateLayout();
// Can only reset the new controllers after the scales have been updated
if (me.options.animation && me.options.animation.duration) {
@@ -8758,10 +9654,12 @@
me.lastActive = [];
// Do this before render so that any plugins that need final scale updates can use it
core_plugins.notify(me, 'afterUpdate');
+ me._layers.sort(compare2Level('z', '_idx'));
+
if (me._bufferedRender) {
me._bufferedRequest = {
duration: config.duration,
easing: config.easing,
lazy: config.lazy
@@ -8783,10 +9681,24 @@
return;
}
core_layouts.update(this, this.width, this.height);
+ me._layers = [];
+ helpers$1.each(me.boxes, function(box) {
+ // _configure is called twice, once in core.scale.update and once here.
+ // Here the boxes are fully updated and at their final positions.
+ if (box._configure) {
+ box._configure();
+ }
+ me._layers.push.apply(me._layers, box._layers());
+ }, me);
+
+ me._layers.forEach(function(item, index) {
+ item._idx = index;
+ });
+
/**
* Provided for backward compatibility, use `afterLayout` instead.
* @method IPlugin#afterScaleUpdate
* @deprecated since version 2.5.0
* @todo remove at version 3
@@ -8830,11 +9742,11 @@
if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
return;
}
- meta.controller.update();
+ meta.controller._update();
core_plugins.notify(me, 'afterDatasetUpdate', [args]);
},
render: function(config) {
@@ -8847,11 +9759,11 @@
lazy: arguments[1]
};
}
var animationOptions = me.options.animation;
- var duration = valueOrDefault$8(config.duration, animationOptions && animationOptions.duration);
+ var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration);
var lazy = config.lazy;
if (core_plugins.notify(me, 'beforeRender') === false) {
return;
}
@@ -8889,10 +9801,11 @@
return me;
},
draw: function(easingValue) {
var me = this;
+ var i, layers;
me.clear();
if (helpers$1.isNullOrUndef(easingValue)) {
easingValue = 1;
@@ -8906,16 +9819,25 @@
if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
return;
}
- // Draw all the scales
- helpers$1.each(me.boxes, function(box) {
- box.draw(me.chartArea);
- }, me);
+ // Because of plugin hooks (before/afterDatasetsDraw), datasets can't
+ // currently be part of layers. Instead, we draw
+ // layers <= 0 before(default, backward compat), and the rest after
+ layers = me._layers;
+ for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
+ layers[i].draw(me.chartArea);
+ }
me.drawDatasets(easingValue);
+
+ // Rest of layers
+ for (; i < layers.length; ++i) {
+ layers[i].draw(me.chartArea);
+ }
+
me._drawTooltip(easingValue);
core_plugins.notify(me, 'afterDraw', [easingValue]);
},
@@ -8933,42 +9855,67 @@
me.tooltip.transition(easingValue);
},
/**
+ * @private
+ */
+ _getSortedDatasetMetas: function(filterVisible) {
+ var me = this;
+ var datasets = me.data.datasets || [];
+ var result = [];
+ var i, ilen;
+
+ for (i = 0, ilen = datasets.length; i < ilen; ++i) {
+ if (!filterVisible || me.isDatasetVisible(i)) {
+ result.push(me.getDatasetMeta(i));
+ }
+ }
+
+ result.sort(compare2Level('order', 'index'));
+
+ return result;
+ },
+
+ /**
+ * @private
+ */
+ _getSortedVisibleDatasetMetas: function() {
+ return this._getSortedDatasetMetas(true);
+ },
+
+ /**
* Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
* hook, in which case, plugins will not be called on `afterDatasetsDraw`.
* @private
*/
drawDatasets: function(easingValue) {
var me = this;
+ var metasets, i;
if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
return;
}
- // Draw datasets reversed to support proper line stacking
- for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
- if (me.isDatasetVisible(i)) {
- me.drawDataset(i, easingValue);
- }
+ metasets = me._getSortedVisibleDatasetMetas();
+ for (i = metasets.length - 1; i >= 0; --i) {
+ me.drawDataset(metasets[i], easingValue);
}
core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
},
/**
* Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
* hook, in which case, plugins will not be called on `afterDatasetDraw`.
* @private
*/
- drawDataset: function(index, easingValue) {
+ drawDataset: function(meta, easingValue) {
var me = this;
- var meta = me.getDatasetMeta(index);
var args = {
meta: meta,
- index: index,
+ index: meta.index,
easingValue: easingValue
};
if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
return;
@@ -9044,11 +9991,13 @@
data: [],
dataset: null,
controller: null,
hidden: null, // See isDatasetVisible() comment
xAxisID: null,
- yAxisID: null
+ yAxisID: null,
+ order: dataset.order || 0,
+ index: datasetIndex
};
}
return meta;
},
@@ -9170,19 +10119,23 @@
platform.removeEventListener(me, type, listener);
});
},
updateHoverStyle: function(elements, mode, enabled) {
- var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
+ var prefix = enabled ? 'set' : 'remove';
var element, i, ilen;
for (i = 0, ilen = elements.length; i < ilen; ++i) {
element = elements[i];
if (element) {
- this.getDatasetMeta(element._datasetIndex).controller[method](element);
+ this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element);
}
}
+
+ if (mode === 'dataset') {
+ this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle']();
+ }
},
/**
* @private
*/
@@ -9394,11 +10347,11 @@
helpers$1.almostEquals = function(x, y, epsilon) {
return Math.abs(x - y) < epsilon;
};
helpers$1.almostWhole = function(x, epsilon) {
var rounded = Math.round(x);
- return (((rounded - epsilon) < x) && ((rounded + epsilon) > x));
+ return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
};
helpers$1.max = function(array) {
return array.reduce(function(max, value) {
if (!isNaN(value)) {
return Math.max(max, value);
@@ -9423,23 +10376,10 @@
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
- helpers$1.log10 = Math.log10 ?
- function(x) {
- return Math.log10(x);
- } :
- 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;
- };
helpers$1.toRadians = function(degrees) {
return degrees * (Math.PI / 180);
};
helpers$1.toDegrees = function(radians) {
return radians * (180 / Math.PI);
@@ -9876,29 +10816,34 @@
cache.font = font;
}
ctx.font = font;
var longest = 0;
- helpers$1.each(arrayOfThings, function(thing) {
+ var ilen = arrayOfThings.length;
+ var i, j, jlen, thing, nestedThing;
+ for (i = 0; i < ilen; i++) {
+ thing = arrayOfThings[i];
+
// Undefined strings and arrays should not be measured
if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) {
longest = helpers$1.measureText(ctx, data, gc, longest, thing);
} else if (helpers$1.isArray(thing)) {
// if it is an array lets measure each element
// to do maybe simplify this function a bit so we can do this more recursively?
- helpers$1.each(thing, function(nestedThing) {
+ for (j = 0, jlen = thing.length; j < jlen; j++) {
+ nestedThing = thing[j];
// Undefined strings and arrays should not be measured
if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) {
longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing);
}
- });
+ }
}
- });
+ }
var gcLen = gc.length / 2;
if (gcLen > arrayOfThings.length) {
- for (var i = 0; i < gcLen; i++) {
+ for (i = 0; i < gcLen; i++) {
delete data[gc[i]];
}
gc.splice(0, gcLen);
}
return longest;
@@ -9912,10 +10857,14 @@
if (textWidth > longest) {
longest = textWidth;
}
return longest;
};
+
+ /**
+ * @deprecated
+ */
helpers$1.numberOfLabelLines = function(arrayOfThings) {
var numberOfLines = 1;
helpers$1.each(arrayOfThings, function(thing) {
if (helpers$1.isArray(thing)) {
if (thing.length > numberOfLines) {
@@ -10109,11 +11058,13 @@
if (tickValue !== 0) {
var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1]));
if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation
var logTick = helpers$1.log10(Math.abs(tickValue));
- tickString = tickValue.toExponential(Math.floor(logTick) - Math.floor(logDelta));
+ var numExponential = Math.floor(logTick) - Math.floor(logDelta);
+ numExponential = Math.max(Math.min(numExponential, 20), 0);
+ tickString = tickValue.toExponential(numExponential);
} else {
var numDecimal = -1 * Math.floor(logDelta);
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
tickString = tickValue.toFixed(numDecimal);
}
@@ -10135,22 +11086,24 @@
return '';
}
}
};
-var valueOrDefault$9 = helpers$1.valueOrDefault;
+var isArray = helpers$1.isArray;
+var isNullOrUndef = helpers$1.isNullOrUndef;
+var valueOrDefault$a = helpers$1.valueOrDefault;
var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault;
core_defaults._set('scale', {
display: true,
position: 'left',
offset: false,
// grid line settings
gridLines: {
display: true,
- color: 'rgba(0, 0, 0, 0.1)',
+ color: 'rgba(0,0,0,0.1)',
lineWidth: 1,
drawBorder: true,
drawOnChartArea: true,
drawTicks: true,
tickMarkLength: 10,
@@ -10195,45 +11148,270 @@
minor: {},
major: {}
}
});
-function labelsFromTicks(ticks) {
- var labels = [];
- var i, ilen;
+/** Returns a new array containing numItems from arr */
+function sample(arr, numItems) {
+ var result = [];
+ var increment = arr.length / numItems;
+ var i = 0;
+ var len = arr.length;
- for (i = 0, ilen = ticks.length; i < ilen; ++i) {
- labels.push(ticks[i].label);
+ for (; i < len; i += increment) {
+ result.push(arr[Math.floor(i)]);
}
-
- return labels;
+ return result;
}
function getPixelForGridLine(scale, index, offsetGridLines) {
- var lineValue = scale.getPixelForTick(index);
+ var length = scale.getTicks().length;
+ var validIndex = Math.min(index, length - 1);
+ var lineValue = scale.getPixelForTick(validIndex);
+ var start = scale._startPixel;
+ var end = scale._endPixel;
+ var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
+ var offset;
if (offsetGridLines) {
- if (scale.getTicks().length === 1) {
- lineValue -= scale.isHorizontal() ?
- Math.max(lineValue - scale.left, scale.right - lineValue) :
- Math.max(lineValue - scale.top, scale.bottom - lineValue);
+ if (length === 1) {
+ offset = Math.max(lineValue - start, end - lineValue);
} else if (index === 0) {
- lineValue -= (scale.getPixelForTick(1) - lineValue) / 2;
+ offset = (scale.getPixelForTick(1) - lineValue) / 2;
} else {
- lineValue -= (lineValue - scale.getPixelForTick(index - 1)) / 2;
+ offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
}
+ lineValue += validIndex < index ? offset : -offset;
+
+ // Return undefined if the pixel is out of the range
+ if (lineValue < start - epsilon || lineValue > end + epsilon) {
+ return;
+ }
}
return lineValue;
}
-function computeTextSize(context, tick, font) {
- return helpers$1.isArray(tick) ?
- helpers$1.longestText(context, font, tick) :
- context.measureText(tick).width;
+function garbageCollect(caches, length) {
+ helpers$1.each(caches, function(cache) {
+ var gc = cache.gc;
+ var gcLen = gc.length / 2;
+ var i;
+ if (gcLen > length) {
+ for (i = 0; i < gcLen; ++i) {
+ delete cache.data[gc[i]];
+ }
+ gc.splice(0, gcLen);
+ }
+ });
}
-var core_scale = core_element.extend({
+/**
+ * Returns {width, height, offset} objects for the first, last, widest, highest tick
+ * labels where offset indicates the anchor point offset from the top in pixels.
+ */
+function computeLabelSizes(ctx, tickFonts, ticks, caches) {
+ var length = ticks.length;
+ var widths = [];
+ var heights = [];
+ var offsets = [];
+ var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest;
+
+ for (i = 0; i < length; ++i) {
+ label = ticks[i].label;
+ tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor;
+ ctx.font = fontString = tickFont.string;
+ cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
+ lineHeight = tickFont.lineHeight;
+ width = height = 0;
+ // Undefined labels and arrays should not be measured
+ if (!isNullOrUndef(label) && !isArray(label)) {
+ width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label);
+ height = lineHeight;
+ } else if (isArray(label)) {
+ // if it is an array let's measure each element
+ for (j = 0, jlen = label.length; j < jlen; ++j) {
+ nestedLabel = label[j];
+ // Undefined labels and arrays should not be measured
+ if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
+ width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel);
+ height += lineHeight;
+ }
+ }
+ }
+ widths.push(width);
+ heights.push(height);
+ offsets.push(lineHeight / 2);
+ }
+ garbageCollect(caches, length);
+
+ widest = widths.indexOf(Math.max.apply(null, widths));
+ highest = heights.indexOf(Math.max.apply(null, heights));
+
+ function valueAt(idx) {
+ return {
+ width: widths[idx] || 0,
+ height: heights[idx] || 0,
+ offset: offsets[idx] || 0
+ };
+ }
+
+ return {
+ first: valueAt(0),
+ last: valueAt(length - 1),
+ widest: valueAt(widest),
+ highest: valueAt(highest)
+ };
+}
+
+function getTickMarkLength(options) {
+ return options.drawTicks ? options.tickMarkLength : 0;
+}
+
+function getScaleLabelHeight(options) {
+ var font, padding;
+
+ if (!options.display) {
+ return 0;
+ }
+
+ font = helpers$1.options._parseFont(options);
+ padding = helpers$1.options.toPadding(options.padding);
+
+ return font.lineHeight + padding.height;
+}
+
+function parseFontOptions(options, nestedOpts) {
+ return helpers$1.extend(helpers$1.options._parseFont({
+ fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily),
+ fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize),
+ fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle),
+ lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight)
+ }), {
+ color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor])
+ });
+}
+
+function parseTickFontOptions(options) {
+ var minor = parseFontOptions(options, options.minor);
+ var major = options.major.enabled ? parseFontOptions(options, options.major) : minor;
+
+ return {minor: minor, major: major};
+}
+
+function nonSkipped(ticksToFilter) {
+ var filtered = [];
+ var item, index, len;
+ for (index = 0, len = ticksToFilter.length; index < len; ++index) {
+ item = ticksToFilter[index];
+ if (typeof item._index !== 'undefined') {
+ filtered.push(item);
+ }
+ }
+ return filtered;
+}
+
+function getEvenSpacing(arr) {
+ var len = arr.length;
+ var i, diff;
+
+ if (len < 2) {
+ return false;
+ }
+
+ for (diff = arr[0], i = 1; i < len; ++i) {
+ if (arr[i] - arr[i - 1] !== diff) {
+ return false;
+ }
+ }
+ return diff;
+}
+
+function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
+ var evenMajorSpacing = getEvenSpacing(majorIndices);
+ var spacing = (ticks.length - 1) / ticksLimit;
+ var factors, factor, i, ilen;
+
+ // If the major ticks are evenly spaced apart, place the minor ticks
+ // so that they divide the major ticks into even chunks
+ if (!evenMajorSpacing) {
+ return Math.max(spacing, 1);
+ }
+
+ factors = helpers$1.math._factorize(evenMajorSpacing);
+ for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
+ factor = factors[i];
+ if (factor > spacing) {
+ return factor;
+ }
+ }
+ return Math.max(spacing, 1);
+}
+
+function getMajorIndices(ticks) {
+ var result = [];
+ var i, ilen;
+ for (i = 0, ilen = ticks.length; i < ilen; i++) {
+ if (ticks[i].major) {
+ result.push(i);
+ }
+ }
+ return result;
+}
+
+function skipMajors(ticks, majorIndices, spacing) {
+ var count = 0;
+ var next = majorIndices[0];
+ var i, tick;
+
+ spacing = Math.ceil(spacing);
+ for (i = 0; i < ticks.length; i++) {
+ tick = ticks[i];
+ if (i === next) {
+ tick._index = i;
+ count++;
+ next = majorIndices[count * spacing];
+ } else {
+ delete tick.label;
+ }
+ }
+}
+
+function skip(ticks, spacing, majorStart, majorEnd) {
+ var start = valueOrDefault$a(majorStart, 0);
+ var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length);
+ var count = 0;
+ var length, i, tick, next;
+
+ spacing = Math.ceil(spacing);
+ if (majorEnd) {
+ length = majorEnd - majorStart;
+ spacing = length / Math.floor(length / spacing);
+ }
+
+ next = start;
+
+ while (next < 0) {
+ count++;
+ next = Math.round(start + count * spacing);
+ }
+
+ for (i = Math.max(start, 0); i < end; i++) {
+ tick = ticks[i];
+ if (i === next) {
+ tick._index = i;
+ count++;
+ next = Math.round(start + count * spacing);
+ } else {
+ delete tick.label;
+ }
+ }
+}
+
+var Scale = core_element.extend({
+
+ zeroLineIndex: 0,
+
/**
* Get the padding needed for the scale
* @method getPadding
* @private
* @returns {Padding} the necessary padding
@@ -10254,44 +11432,49 @@
*/
getTicks: function() {
return this._ticks;
},
+ /**
+ * @private
+ */
+ _getLabels: function() {
+ var data = this.chart.data;
+ return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
+ },
+
// These methods are ordered by lifecyle. Utilities then follow.
// Any function defined here is inherited by all scale types.
// Any function can be extended by the scale type
+ /**
+ * Provided for backward compatibility, not available anymore
+ * @function Chart.Scale.mergeTicksOptions
+ * @deprecated since version 2.8.0
+ * @todo remove at version 3
+ */
mergeTicksOptions: function() {
- var ticks = this.options.ticks;
- if (ticks.minor === false) {
- ticks.minor = {
- display: false
- };
- }
- if (ticks.major === false) {
- ticks.major = {
- display: false
- };
- }
- for (var key in ticks) {
- if (key !== 'major' && key !== 'minor') {
- if (typeof ticks.minor[key] === 'undefined') {
- ticks.minor[key] = ticks[key];
- }
- if (typeof ticks.major[key] === 'undefined') {
- ticks.major[key] = ticks[key];
- }
- }
- }
+ // noop
},
+
beforeUpdate: function() {
helpers$1.callback(this.options.beforeUpdate, [this]);
},
+ /**
+ * @param {number} maxWidth - the max width in pixels
+ * @param {number} maxHeight - the max height in pixels
+ * @param {object} margins - the space between the edge of the other scales and edge of the chart
+ * This space comes from two sources:
+ * - padding - space that's required to show the labels at the edges of the scale
+ * - thickness of scales or legends in another orientation
+ */
update: function(maxWidth, maxHeight, margins) {
var me = this;
- var i, ilen, labels, label, ticks, tick;
+ var tickOpts = me.options.ticks;
+ var sampleSize = tickOpts.sampleSize;
+ var i, ilen, labels, ticks, samplingEnabled;
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
me.beforeUpdate();
// Absorb the master measurements
@@ -10302,13 +11485,18 @@
right: 0,
top: 0,
bottom: 0
}, margins);
+ me._ticks = null;
+ me.ticks = null;
+ me._labelSizes = null;
me._maxLabelLines = 0;
me.longestLabelWidth = 0;
me.longestTextCache = me.longestTextCache || {};
+ me._gridLineItems = null;
+ me._labelItems = null;
// Dimensions
me.beforeSetDimensions();
me.setDimensions();
me.afterSetDimensions();
@@ -10332,53 +11520,85 @@
ticks = me.buildTicks() || [];
// Allow modification of ticks in callback.
ticks = me.afterBuildTicks(ticks) || ticks;
- me.beforeTickToLabelConversion();
-
- // New implementations should return the formatted tick labels but for BACKWARD
- // COMPAT, we still support no return (`this.ticks` internally changed by calling
- // this method and supposed to contain only string values).
- labels = me.convertTicksToLabels(ticks) || me.ticks;
-
- me.afterTickToLabelConversion();
-
- me.ticks = labels; // BACKWARD COMPATIBILITY
-
- // IMPORTANT: from this point, we consider that `this.ticks` will NEVER change!
-
- // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
- for (i = 0, ilen = labels.length; i < ilen; ++i) {
- label = labels[i];
- tick = ticks[i];
- if (!tick) {
- ticks.push(tick = {
- label: label,
+ // Ensure ticks contains ticks in new tick format
+ if ((!ticks || !ticks.length) && me.ticks) {
+ ticks = [];
+ for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
+ ticks.push({
+ value: me.ticks[i],
major: false
});
- } else {
- tick.label = label;
}
}
me._ticks = ticks;
+ // Compute tick rotation and fit using a sampled subset of labels
+ // We generally don't need to compute the size of every single label for determining scale size
+ samplingEnabled = sampleSize < ticks.length;
+ labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks);
+
+ // _configure is called twice, once here, once from core.controller.updateLayout.
+ // Here we haven't been positioned yet, but dimensions are correct.
+ // Variables set in _configure are needed for calculateTickRotation, and
+ // it's ok that coordinates are not correct there, only dimensions matter.
+ me._configure();
+
// Tick Rotation
me.beforeCalculateTickRotation();
me.calculateTickRotation();
me.afterCalculateTickRotation();
- // Fit
+
me.beforeFit();
me.fit();
me.afterFit();
- //
+
+ // Auto-skip
+ me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
+
+ if (samplingEnabled) {
+ // Generate labels using all non-skipped ticks
+ labels = me._convertTicksToLabels(me._ticksToDraw);
+ }
+
+ me.ticks = labels; // BACKWARD COMPATIBILITY
+
+ // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
+
me.afterUpdate();
+ // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
+ // make maxWidth and maxHeight private
return me.minSize;
+ },
+ /**
+ * @private
+ */
+ _configure: function() {
+ var me = this;
+ var reversePixels = me.options.ticks.reverse;
+ var startPixel, endPixel;
+
+ if (me.isHorizontal()) {
+ startPixel = me.left;
+ endPixel = me.right;
+ } else {
+ startPixel = me.top;
+ endPixel = me.bottom;
+ // by default vertical scales are from bottom to top, so pixels are reversed
+ reversePixels = !reversePixels;
+ }
+ me._startPixel = startPixel;
+ me._endPixel = endPixel;
+ me._reversePixels = reversePixels;
+ me._length = endPixel - startPixel;
},
+
afterUpdate: function() {
helpers$1.callback(this.options.afterUpdate, [this]);
},
//
@@ -10427,11 +11647,11 @@
},
buildTicks: helpers$1.noop,
afterBuildTicks: function(ticks) {
var me = this;
// ticks is empty for old axis implementations here
- if (helpers$1.isArray(ticks) && ticks.length) {
+ if (isArray(ticks) && ticks.length) {
return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]);
}
// Support old implementations (that modified `this.ticks` directly in buildTicks)
me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks;
return ticks;
@@ -10455,44 +11675,43 @@
beforeCalculateTickRotation: function() {
helpers$1.callback(this.options.beforeCalculateTickRotation, [this]);
},
calculateTickRotation: function() {
var me = this;
- var context = me.ctx;
- var tickOpts = me.options.ticks;
- var labels = labelsFromTicks(me._ticks);
+ var options = me.options;
+ var tickOpts = options.ticks;
+ var numTicks = me.getTicks().length;
+ var minRotation = tickOpts.minRotation || 0;
+ var maxRotation = tickOpts.maxRotation;
+ var labelRotation = minRotation;
+ var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal;
- // Get the width of each grid by calculating the difference
- // between x offsets between 0 and 1.
- var tickFont = helpers$1.options._parseFont(tickOpts);
- context.font = tickFont.string;
+ if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
+ me.labelRotation = minRotation;
+ return;
+ }
- var labelRotation = tickOpts.minRotation || 0;
+ labelSizes = me._getLabelSizes();
+ maxLabelWidth = labelSizes.widest.width;
+ maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
- if (labels.length && me.options.display && me.isHorizontal()) {
- var originalLabelWidth = helpers$1.longestText(context, tickFont.string, labels, me.longestTextCache);
- var labelWidth = originalLabelWidth;
- var cosRotation, sinRotation;
+ // Estimate the width of each grid based on the canvas width, the maximum
+ // label width and the number of tick intervals
+ maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth);
+ tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
- // Allow 3 pixels x2 padding either side for label readability
- var tickWidth = me.getPixelForTick(1) - me.getPixelForTick(0) - 6;
-
- // Max label rotation can be set or default to 90 - also act as a loop counter
- while (labelWidth > tickWidth && labelRotation < tickOpts.maxRotation) {
- var angleRadians = helpers$1.toRadians(labelRotation);
- cosRotation = Math.cos(angleRadians);
- sinRotation = Math.sin(angleRadians);
-
- if (sinRotation * originalLabelWidth > me.maxHeight) {
- // go back one step
- labelRotation--;
- break;
- }
-
- labelRotation++;
- labelWidth = cosRotation * originalLabelWidth;
- }
+ // Allow 3 pixels x2 padding either side for label readability
+ if (maxLabelWidth + 6 > tickWidth) {
+ tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
+ maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
+ - tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
+ maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
+ labelRotation = helpers$1.toDegrees(Math.min(
+ Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
+ Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
+ ));
+ labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
}
me.labelRotation = labelRotation;
},
afterCalculateTickRotation: function() {
@@ -10510,153 +11729,143 @@
var minSize = me.minSize = {
width: 0,
height: 0
};
- var labels = labelsFromTicks(me._ticks);
-
+ var chart = me.chart;
var opts = me.options;
var tickOpts = opts.ticks;
var scaleLabelOpts = opts.scaleLabel;
var gridLineOpts = opts.gridLines;
var display = me._isVisible();
- var position = opts.position;
+ var isBottom = opts.position === 'bottom';
var isHorizontal = me.isHorizontal();
- var parseFont = helpers$1.options._parseFont;
- var tickFont = parseFont(tickOpts);
- var tickMarkLength = opts.gridLines.tickMarkLength;
-
// Width
if (isHorizontal) {
- // subtract the margins to line up with the chartArea if we are a full width scale
- minSize.width = me.isFullWidth() ? me.maxWidth - me.margins.left - me.margins.right : me.maxWidth;
- } else {
- minSize.width = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
+ minSize.width = me.maxWidth;
+ } else if (display) {
+ minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
}
// height
- if (isHorizontal) {
- minSize.height = display && gridLineOpts.drawTicks ? tickMarkLength : 0;
- } else {
+ if (!isHorizontal) {
minSize.height = me.maxHeight; // fill all the height
+ } else if (display) {
+ minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
}
- // Are we showing a title for the scale?
- if (scaleLabelOpts.display && display) {
- var scaleLabelFont = parseFont(scaleLabelOpts);
- var scaleLabelPadding = helpers$1.options.toPadding(scaleLabelOpts.padding);
- var deltaHeight = scaleLabelFont.lineHeight + scaleLabelPadding.height;
-
- if (isHorizontal) {
- minSize.height += deltaHeight;
- } else {
- minSize.width += deltaHeight;
- }
- }
-
// Don't bother fitting the ticks if we are not showing the labels
if (tickOpts.display && display) {
- var largestTextWidth = helpers$1.longestText(me.ctx, tickFont.string, labels, me.longestTextCache);
- var tallestLabelHeightInLines = helpers$1.numberOfLabelLines(labels);
- var lineSpace = tickFont.size * 0.5;
- var tickPadding = me.options.ticks.padding;
+ var tickFonts = parseTickFontOptions(tickOpts);
+ var labelSizes = me._getLabelSizes();
+ var firstLabelSize = labelSizes.first;
+ var lastLabelSize = labelSizes.last;
+ var widestLabelSize = labelSizes.widest;
+ var highestLabelSize = labelSizes.highest;
+ var lineSpace = tickFonts.minor.lineHeight * 0.4;
+ var tickPadding = tickOpts.padding;
- // Store max number of lines and widest label for _autoSkip
- me._maxLabelLines = tallestLabelHeightInLines;
- me.longestLabelWidth = largestTextWidth;
-
if (isHorizontal) {
+ // A horizontal axis is more constrained by the height.
+ var isRotated = me.labelRotation !== 0;
var angleRadians = helpers$1.toRadians(me.labelRotation);
var cosRotation = Math.cos(angleRadians);
var sinRotation = Math.sin(angleRadians);
- // TODO - improve this calculation
- var labelHeight = (sinRotation * largestTextWidth)
- + (tickFont.lineHeight * tallestLabelHeightInLines)
- + lineSpace; // padding
+ var labelHeight = sinRotation * widestLabelSize.width
+ + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
+ + (isRotated ? 0 : lineSpace); // padding
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
- me.ctx.font = tickFont.string;
- var firstLabelWidth = computeTextSize(me.ctx, labels[0], tickFont.string);
- var lastLabelWidth = computeTextSize(me.ctx, labels[labels.length - 1], tickFont.string);
var offsetLeft = me.getPixelForTick(0) - me.left;
- var offsetRight = me.right - me.getPixelForTick(labels.length - 1);
+ var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1);
var paddingLeft, paddingRight;
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
// which means that the right padding is dominated by the font height
- if (me.labelRotation !== 0) {
- paddingLeft = position === 'bottom' ? (cosRotation * firstLabelWidth) : (cosRotation * lineSpace);
- paddingRight = position === 'bottom' ? (cosRotation * lineSpace) : (cosRotation * lastLabelWidth);
+ if (isRotated) {
+ paddingLeft = isBottom ?
+ cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
+ sinRotation * (firstLabelSize.height - firstLabelSize.offset);
+ paddingRight = isBottom ?
+ sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
+ cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
} else {
- paddingLeft = firstLabelWidth / 2;
- paddingRight = lastLabelWidth / 2;
+ paddingLeft = firstLabelSize.width / 2;
+ paddingRight = lastLabelSize.width / 2;
}
- me.paddingLeft = Math.max(paddingLeft - offsetLeft, 0) + 3; // add 3 px to move away from canvas edges
- me.paddingRight = Math.max(paddingRight - offsetRight, 0) + 3;
+
+ // Adjust padding taking into account changes in offsets
+ // and add 3 px to move away from canvas edges
+ me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
+ me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
} else {
// A vertical axis is more constrained by the width. Labels are the
// dominant factor here, so get that length first and account for padding
- if (tickOpts.mirror) {
- largestTextWidth = 0;
- } else {
+ var labelWidth = tickOpts.mirror ? 0 :
// use lineSpace for consistency with horizontal axis
// tickPadding is not implemented for horizontal
- largestTextWidth += tickPadding + lineSpace;
- }
+ widestLabelSize.width + tickPadding + lineSpace;
- minSize.width = Math.min(me.maxWidth, minSize.width + largestTextWidth);
+ minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
- me.paddingTop = tickFont.size / 2;
- me.paddingBottom = tickFont.size / 2;
+ me.paddingTop = firstLabelSize.height / 2;
+ me.paddingBottom = lastLabelSize.height / 2;
}
}
me.handleMargins();
- me.width = minSize.width;
- me.height = minSize.height;
+ if (isHorizontal) {
+ me.width = me._length = chart.width - me.margins.left - me.margins.right;
+ me.height = minSize.height;
+ } else {
+ me.width = minSize.width;
+ me.height = me._length = chart.height - me.margins.top - me.margins.bottom;
+ }
},
/**
* Handle margins and padding interactions
* @private
*/
handleMargins: function() {
var me = this;
if (me.margins) {
- me.paddingLeft = Math.max(me.paddingLeft - me.margins.left, 0);
- me.paddingTop = Math.max(me.paddingTop - me.margins.top, 0);
- me.paddingRight = Math.max(me.paddingRight - me.margins.right, 0);
- me.paddingBottom = Math.max(me.paddingBottom - me.margins.bottom, 0);
+ me.margins.left = Math.max(me.paddingLeft, me.margins.left);
+ me.margins.top = Math.max(me.paddingTop, me.margins.top);
+ me.margins.right = Math.max(me.paddingRight, me.margins.right);
+ me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom);
}
},
afterFit: function() {
helpers$1.callback(this.options.afterFit, [this]);
},
// Shared Methods
isHorizontal: function() {
- return this.options.position === 'top' || this.options.position === 'bottom';
+ var pos = this.options.position;
+ return pos === 'top' || pos === 'bottom';
},
isFullWidth: function() {
- return (this.options.fullWidth);
+ return this.options.fullWidth;
},
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
getRightValue: function(rawValue) {
// Null and undefined values first
- if (helpers$1.isNullOrUndef(rawValue)) {
+ if (isNullOrUndef(rawValue)) {
return NaN;
}
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
return NaN;
}
+
// If it is in fact an object, dive in one more level
if (rawValue) {
if (this.isHorizontal()) {
if (rawValue.x !== undefined) {
return this.getRightValue(rawValue.x);
@@ -10668,11 +11877,90 @@
// Value is good, return it
return rawValue;
},
+ _convertTicksToLabels: function(ticks) {
+ var me = this;
+ var labels, i, ilen;
+
+ me.ticks = ticks.map(function(tick) {
+ return tick.value;
+ });
+
+ me.beforeTickToLabelConversion();
+
+ // New implementations should return the formatted tick labels but for BACKWARD
+ // COMPAT, we still support no return (`this.ticks` internally changed by calling
+ // this method and supposed to contain only string values).
+ labels = me.convertTicksToLabels(ticks) || me.ticks;
+
+ me.afterTickToLabelConversion();
+
+ // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
+ ticks[i].label = labels[i];
+ }
+
+ return labels;
+ },
+
/**
+ * @private
+ */
+ _getLabelSizes: function() {
+ var me = this;
+ var labelSizes = me._labelSizes;
+
+ if (!labelSizes) {
+ me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache);
+ me.longestLabelWidth = labelSizes.widest.width;
+ }
+
+ return labelSizes;
+ },
+
+ /**
+ * @private
+ */
+ _parseValue: function(value) {
+ var start, end, min, max;
+
+ if (isArray(value)) {
+ start = +this.getRightValue(value[0]);
+ end = +this.getRightValue(value[1]);
+ min = Math.min(start, end);
+ max = Math.max(start, end);
+ } else {
+ value = +this.getRightValue(value);
+ start = undefined;
+ end = value;
+ min = value;
+ max = value;
+ }
+
+ return {
+ min: min,
+ max: max,
+ start: start,
+ end: end
+ };
+ },
+
+ /**
+ * @private
+ */
+ _getScaleLabel: function(rawValue) {
+ var v = this._parseValue(rawValue);
+ if (v.start !== undefined) {
+ return '[' + v.start + ', ' + v.end + ']';
+ }
+
+ return +this.getRightValue(rawValue);
+ },
+
+ /**
* Used to get the value to display in the tooltip for the data at the given index
* @param index
* @param datasetIndex
*/
getLabelForIndex: helpers$1.noop,
@@ -10698,44 +11986,37 @@
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getPixelForTick: function(index) {
var me = this;
var offset = me.options.offset;
- if (me.isHorizontal()) {
- var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
- var tickWidth = innerWidth / Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
- var pixel = (tickWidth * index) + me.paddingLeft;
+ var numTicks = me._ticks.length;
+ var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1);
- if (offset) {
- pixel += tickWidth / 2;
- }
-
- var finalVal = me.left + pixel;
- finalVal += me.isFullWidth() ? me.margins.left : 0;
- return finalVal;
- }
- var innerHeight = me.height - (me.paddingTop + me.paddingBottom);
- return me.top + (index * (innerHeight / (me._ticks.length - 1)));
+ return index < 0 || index > numTicks - 1
+ ? null
+ : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0));
},
/**
* Utility for getting the pixel location of a percentage of scale
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getPixelForDecimal: function(decimal) {
var me = this;
- if (me.isHorizontal()) {
- var innerWidth = me.width - (me.paddingLeft + me.paddingRight);
- var valueOffset = (innerWidth * decimal) + me.paddingLeft;
- var finalVal = me.left + valueOffset;
- finalVal += me.isFullWidth() ? me.margins.left : 0;
- return finalVal;
+ if (me._reversePixels) {
+ decimal = 1 - decimal;
}
- return me.top + (decimal * me.height);
+
+ return me._startPixel + decimal * me._length;
},
+ getDecimalForPixel: function(pixel) {
+ var decimal = (pixel - this._startPixel) / this._length;
+ return this._reversePixels ? 1 - decimal : decimal;
+ },
+
/**
* Returns the pixel for the minimum chart value
* The coordinate (0, 0) is at the upper-left corner of the canvas
*/
getBasePixel: function() {
@@ -10757,71 +12038,59 @@
* Returns a subset of ticks to be plotted to avoid overlapping labels.
* @private
*/
_autoSkip: function(ticks) {
var me = this;
- var isHorizontal = me.isHorizontal();
- var optionTicks = me.options.ticks.minor;
- var tickCount = ticks.length;
- var skipRatio = false;
- var maxTicks = optionTicks.maxTicksLimit;
+ var tickOpts = me.options.ticks;
+ var axisLength = me._length;
+ var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
+ var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
+ var numMajorIndices = majorIndices.length;
+ var first = majorIndices[0];
+ var last = majorIndices[numMajorIndices - 1];
+ var i, ilen, spacing, avgMajorSpacing;
- // Total space needed to display all ticks. First and last ticks are
- // drawn as their center at end of axis, so tickCount-1
- var ticksLength = me._tickSize() * (tickCount - 1);
-
- // Axis length
- var axisLength = isHorizontal
- ? me.width - (me.paddingLeft + me.paddingRight)
- : me.height - (me.paddingTop + me.PaddingBottom);
-
- var result = [];
- var i, tick;
-
- if (ticksLength > axisLength) {
- skipRatio = 1 + Math.floor(ticksLength / axisLength);
+ // If there are too many major ticks to display them all
+ if (numMajorIndices > ticksLimit) {
+ skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
+ return nonSkipped(ticks);
}
- // if they defined a max number of optionTicks,
- // increase skipRatio until that number is met
- if (tickCount > maxTicks) {
- skipRatio = Math.max(skipRatio, 1 + Math.floor(tickCount / maxTicks));
- }
+ spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
- for (i = 0; i < tickCount; i++) {
- tick = ticks[i];
-
- if (skipRatio > 1 && i % skipRatio > 0) {
- // leave tick in place but make sure it's not displayed (#4635)
- delete tick.label;
+ if (numMajorIndices > 0) {
+ for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
+ skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
}
- result.push(tick);
+ avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
+ skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
+ skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
+ return nonSkipped(ticks);
}
- return result;
+ skip(ticks, spacing);
+ return nonSkipped(ticks);
},
/**
* @private
*/
_tickSize: function() {
var me = this;
- var isHorizontal = me.isHorizontal();
- var optionTicks = me.options.ticks.minor;
+ var optionTicks = me.options.ticks;
// Calculate space needed by label in axis direction.
var rot = helpers$1.toRadians(me.labelRotation);
var cos = Math.abs(Math.cos(rot));
var sin = Math.abs(Math.sin(rot));
+ var labelSizes = me._getLabelSizes();
var padding = optionTicks.autoSkipPadding || 0;
- var w = (me.longestLabelWidth + padding) || 0;
+ var w = labelSizes ? labelSizes.widest.width + padding : 0;
+ var h = labelSizes ? labelSizes.highest.height + padding : 0;
- var tickFont = helpers$1.options._parseFont(optionTicks);
- var h = (me._maxLabelLines * tickFont.lineHeight + padding) || 0;
-
// Calculate space needed for 1 tick in axis direction.
- return isHorizontal
+ return me.isHorizontal()
? h * cos > w * sin ? w / cos : h / sin
: h * sin < w * cos ? h / cos : w / sin;
},
/**
@@ -10849,412 +12118,532 @@
return false;
},
/**
- * Actually draw the scale on the canvas
- * @param {object} chartArea - the area of the chart to draw full grid lines on
+ * @private
*/
- draw: function(chartArea) {
+ _computeGridLineItems: function(chartArea) {
var me = this;
- var options = me.options;
-
- if (!me._isVisible()) {
- return;
- }
-
var chart = me.chart;
- var context = me.ctx;
- var globalDefaults = core_defaults.global;
- var defaultFontColor = globalDefaults.defaultFontColor;
- var optionTicks = options.ticks.minor;
- var optionMajorTicks = options.ticks.major || optionTicks;
+ var options = me.options;
var gridLines = options.gridLines;
- var scaleLabel = options.scaleLabel;
var position = options.position;
-
- var isRotated = me.labelRotation !== 0;
- var isMirrored = optionTicks.mirror;
+ var offsetGridLines = gridLines.offsetGridLines;
var isHorizontal = me.isHorizontal();
+ var ticks = me._ticksToDraw;
+ var ticksLength = ticks.length + (offsetGridLines ? 1 : 0);
- var parseFont = helpers$1.options._parseFont;
- var ticks = optionTicks.display && optionTicks.autoSkip ? me._autoSkip(me.getTicks()) : me.getTicks();
- var tickFontColor = valueOrDefault$9(optionTicks.fontColor, defaultFontColor);
- var tickFont = parseFont(optionTicks);
- var lineHeight = tickFont.lineHeight;
- var majorTickFontColor = valueOrDefault$9(optionMajorTicks.fontColor, defaultFontColor);
- var majorTickFont = parseFont(optionMajorTicks);
- var tickPadding = optionTicks.padding;
- var labelOffset = optionTicks.labelOffset;
-
- var tl = gridLines.drawTicks ? gridLines.tickMarkLength : 0;
-
- var scaleLabelFontColor = valueOrDefault$9(scaleLabel.fontColor, defaultFontColor);
- var scaleLabelFont = parseFont(scaleLabel);
- var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
- var labelRotationRadians = helpers$1.toRadians(me.labelRotation);
-
- var itemsToDraw = [];
-
+ var tl = getTickMarkLength(gridLines);
+ var items = [];
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
+ var axisHalfWidth = axisWidth / 2;
var alignPixel = helpers$1._alignPixel;
- var borderValue, tickStart, tickEnd;
+ var alignBorderValue = function(pixel) {
+ return alignPixel(chart, pixel, axisWidth);
+ };
+ var borderValue, i, tick, lineValue, alignedLineValue;
+ var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
if (position === 'top') {
- borderValue = alignPixel(chart, me.bottom, axisWidth);
- tickStart = me.bottom - tl;
- tickEnd = borderValue - axisWidth / 2;
+ borderValue = alignBorderValue(me.bottom);
+ ty1 = me.bottom - tl;
+ ty2 = borderValue - axisHalfWidth;
+ y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
+ y2 = chartArea.bottom;
} else if (position === 'bottom') {
- borderValue = alignPixel(chart, me.top, axisWidth);
- tickStart = borderValue + axisWidth / 2;
- tickEnd = me.top + tl;
+ borderValue = alignBorderValue(me.top);
+ y1 = chartArea.top;
+ y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
+ ty1 = borderValue + axisHalfWidth;
+ ty2 = me.top + tl;
} else if (position === 'left') {
- borderValue = alignPixel(chart, me.right, axisWidth);
- tickStart = me.right - tl;
- tickEnd = borderValue - axisWidth / 2;
+ borderValue = alignBorderValue(me.right);
+ tx1 = me.right - tl;
+ tx2 = borderValue - axisHalfWidth;
+ x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
+ x2 = chartArea.right;
} else {
- borderValue = alignPixel(chart, me.left, axisWidth);
- tickStart = borderValue + axisWidth / 2;
- tickEnd = me.left + tl;
+ borderValue = alignBorderValue(me.left);
+ x1 = chartArea.left;
+ x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
+ tx1 = borderValue + axisHalfWidth;
+ tx2 = me.left + tl;
}
- var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
+ for (i = 0; i < ticksLength; ++i) {
+ tick = ticks[i] || {};
- helpers$1.each(ticks, function(tick, index) {
// autoskipper skipped this tick (#4635)
- if (helpers$1.isNullOrUndef(tick.label)) {
- return;
+ if (isNullOrUndef(tick.label) && i < ticks.length) {
+ continue;
}
- var label = tick.label;
- var lineWidth, lineColor, borderDash, borderDashOffset;
- if (index === me.zeroLineIndex && options.offset === gridLines.offsetGridLines) {
+ if (i === me.zeroLineIndex && options.offset === offsetGridLines) {
// Draw the first index specially
lineWidth = gridLines.zeroLineWidth;
lineColor = gridLines.zeroLineColor;
borderDash = gridLines.zeroLineBorderDash || [];
borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
} else {
- lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, index);
- lineColor = valueAtIndexOrDefault(gridLines.color, index);
+ lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1);
+ lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)');
borderDash = gridLines.borderDash || [];
borderDashOffset = gridLines.borderDashOffset || 0.0;
}
- // Common properties
- var tx1, ty1, tx2, ty2, x1, y1, x2, y2, labelX, labelY, textOffset, textAlign;
- var labelCount = helpers$1.isArray(label) ? label.length : 1;
- var lineValue = getPixelForGridLine(me, index, gridLines.offsetGridLines);
+ lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
- if (isHorizontal) {
- var labelYOffset = tl + tickPadding;
+ // Skip if the pixel is out of the range
+ if (lineValue === undefined) {
+ continue;
+ }
- if (lineValue < me.left - epsilon) {
- lineColor = 'rgba(0,0,0,0)';
- }
+ alignedLineValue = alignPixel(chart, lineValue, lineWidth);
- tx1 = tx2 = x1 = x2 = alignPixel(chart, lineValue, lineWidth);
- ty1 = tickStart;
- ty2 = tickEnd;
- labelX = me.getPixelForTick(index) + labelOffset; // x values for optionTicks (need to consider offsetLabel option)
-
- if (position === 'top') {
- y1 = alignPixel(chart, chartArea.top, axisWidth) + axisWidth / 2;
- y2 = chartArea.bottom;
- textOffset = ((!isRotated ? 0.5 : 1) - labelCount) * lineHeight;
- textAlign = !isRotated ? 'center' : 'left';
- labelY = me.bottom - labelYOffset;
- } else {
- y1 = chartArea.top;
- y2 = alignPixel(chart, chartArea.bottom, axisWidth) - axisWidth / 2;
- textOffset = (!isRotated ? 0.5 : 0) * lineHeight;
- textAlign = !isRotated ? 'center' : 'right';
- labelY = me.top + labelYOffset;
- }
+ if (isHorizontal) {
+ tx1 = tx2 = x1 = x2 = alignedLineValue;
} else {
- var labelXOffset = (isMirrored ? 0 : tl) + tickPadding;
-
- if (lineValue < me.top - epsilon) {
- lineColor = 'rgba(0,0,0,0)';
- }
-
- tx1 = tickStart;
- tx2 = tickEnd;
- ty1 = ty2 = y1 = y2 = alignPixel(chart, lineValue, lineWidth);
- labelY = me.getPixelForTick(index) + labelOffset;
- textOffset = (1 - labelCount) * lineHeight / 2;
-
- if (position === 'left') {
- x1 = alignPixel(chart, chartArea.left, axisWidth) + axisWidth / 2;
- x2 = chartArea.right;
- textAlign = isMirrored ? 'left' : 'right';
- labelX = me.right - labelXOffset;
- } else {
- x1 = chartArea.left;
- x2 = alignPixel(chart, chartArea.right, axisWidth) - axisWidth / 2;
- textAlign = isMirrored ? 'right' : 'left';
- labelX = me.left + labelXOffset;
- }
+ ty1 = ty2 = y1 = y2 = alignedLineValue;
}
- itemsToDraw.push({
+ items.push({
tx1: tx1,
ty1: ty1,
tx2: tx2,
ty2: ty2,
x1: x1,
y1: y1,
x2: x2,
y2: y2,
- labelX: labelX,
- labelY: labelY,
- glWidth: lineWidth,
- glColor: lineColor,
- glBorderDash: borderDash,
- glBorderDashOffset: borderDashOffset,
- rotation: -1 * labelRotationRadians,
+ width: lineWidth,
+ color: lineColor,
+ borderDash: borderDash,
+ borderDashOffset: borderDashOffset,
+ });
+ }
+
+ items.ticksLength = ticksLength;
+ items.borderValue = borderValue;
+
+ return items;
+ },
+
+ /**
+ * @private
+ */
+ _computeLabelItems: function() {
+ var me = this;
+ var options = me.options;
+ var optionTicks = options.ticks;
+ var position = options.position;
+ var isMirrored = optionTicks.mirror;
+ var isHorizontal = me.isHorizontal();
+ var ticks = me._ticksToDraw;
+ var fonts = parseTickFontOptions(optionTicks);
+ var tickPadding = optionTicks.padding;
+ var tl = getTickMarkLength(options.gridLines);
+ var rotation = -helpers$1.toRadians(me.labelRotation);
+ var items = [];
+ var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
+
+ if (position === 'top') {
+ y = me.bottom - tl - tickPadding;
+ textAlign = !rotation ? 'center' : 'left';
+ } else if (position === 'bottom') {
+ y = me.top + tl + tickPadding;
+ textAlign = !rotation ? 'center' : 'right';
+ } else if (position === 'left') {
+ x = me.right - (isMirrored ? 0 : tl) - tickPadding;
+ textAlign = isMirrored ? 'left' : 'right';
+ } else {
+ x = me.left + (isMirrored ? 0 : tl) + tickPadding;
+ textAlign = isMirrored ? 'right' : 'left';
+ }
+
+ for (i = 0, ilen = ticks.length; i < ilen; ++i) {
+ tick = ticks[i];
+ label = tick.label;
+
+ // autoskipper skipped this tick (#4635)
+ if (isNullOrUndef(label)) {
+ continue;
+ }
+
+ pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
+ font = tick.major ? fonts.major : fonts.minor;
+ lineHeight = font.lineHeight;
+ lineCount = isArray(label) ? label.length : 1;
+
+ if (isHorizontal) {
+ x = pixel;
+ textOffset = position === 'top'
+ ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight
+ : (!rotation ? 0.5 : 0) * lineHeight;
+ } else {
+ y = pixel;
+ textOffset = (1 - lineCount) * lineHeight / 2;
+ }
+
+ items.push({
+ x: x,
+ y: y,
+ rotation: rotation,
label: label,
- major: tick.major,
+ font: font,
textOffset: textOffset,
textAlign: textAlign
});
- });
+ }
- // Draw all of the tick labels, tick marks, and grid lines at the correct places
- helpers$1.each(itemsToDraw, function(itemToDraw) {
- var glWidth = itemToDraw.glWidth;
- var glColor = itemToDraw.glColor;
+ return items;
+ },
- if (gridLines.display && glWidth && glColor) {
- context.save();
- context.lineWidth = glWidth;
- context.strokeStyle = glColor;
- if (context.setLineDash) {
- context.setLineDash(itemToDraw.glBorderDash);
- context.lineDashOffset = itemToDraw.glBorderDashOffset;
+ /**
+ * @private
+ */
+ _drawGrid: function(chartArea) {
+ var me = this;
+ var gridLines = me.options.gridLines;
+
+ if (!gridLines.display) {
+ return;
+ }
+
+ var ctx = me.ctx;
+ var chart = me.chart;
+ var alignPixel = helpers$1._alignPixel;
+ var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
+ var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
+ var width, color, i, ilen, item;
+
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
+ item = items[i];
+ width = item.width;
+ color = item.color;
+
+ if (width && color) {
+ ctx.save();
+ ctx.lineWidth = width;
+ ctx.strokeStyle = color;
+ if (ctx.setLineDash) {
+ ctx.setLineDash(item.borderDash);
+ ctx.lineDashOffset = item.borderDashOffset;
}
- context.beginPath();
+ ctx.beginPath();
if (gridLines.drawTicks) {
- context.moveTo(itemToDraw.tx1, itemToDraw.ty1);
- context.lineTo(itemToDraw.tx2, itemToDraw.ty2);
+ ctx.moveTo(item.tx1, item.ty1);
+ ctx.lineTo(item.tx2, item.ty2);
}
if (gridLines.drawOnChartArea) {
- context.moveTo(itemToDraw.x1, itemToDraw.y1);
- context.lineTo(itemToDraw.x2, itemToDraw.y2);
+ ctx.moveTo(item.x1, item.y1);
+ ctx.lineTo(item.x2, item.y2);
}
- context.stroke();
- context.restore();
+ ctx.stroke();
+ ctx.restore();
}
-
- if (optionTicks.display) {
- // Make sure we draw text in the correct color and font
- context.save();
- context.translate(itemToDraw.labelX, itemToDraw.labelY);
- context.rotate(itemToDraw.rotation);
- context.font = itemToDraw.major ? majorTickFont.string : tickFont.string;
- context.fillStyle = itemToDraw.major ? majorTickFontColor : tickFontColor;
- context.textBaseline = 'middle';
- context.textAlign = itemToDraw.textAlign;
-
- var label = itemToDraw.label;
- var y = itemToDraw.textOffset;
- if (helpers$1.isArray(label)) {
- for (var i = 0; i < label.length; ++i) {
- // We just make sure the multiline element is a string here..
- context.fillText('' + label[i], 0, y);
- y += lineHeight;
- }
- } else {
- context.fillText(label, 0, y);
- }
- context.restore();
- }
- });
-
- if (scaleLabel.display) {
- // Draw the scale label
- var scaleLabelX;
- var scaleLabelY;
- var rotation = 0;
- var halfLineHeight = scaleLabelFont.lineHeight / 2;
-
- if (isHorizontal) {
- scaleLabelX = me.left + ((me.right - me.left) / 2); // midpoint of the width
- scaleLabelY = position === 'bottom'
- ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
- : me.top + halfLineHeight + scaleLabelPadding.top;
- } else {
- var isLeft = position === 'left';
- scaleLabelX = isLeft
- ? me.left + halfLineHeight + scaleLabelPadding.top
- : me.right - halfLineHeight - scaleLabelPadding.top;
- scaleLabelY = me.top + ((me.bottom - me.top) / 2);
- rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
- }
-
- context.save();
- context.translate(scaleLabelX, scaleLabelY);
- context.rotate(rotation);
- context.textAlign = 'center';
- context.textBaseline = 'middle';
- context.fillStyle = scaleLabelFontColor; // render in correct colour
- context.font = scaleLabelFont.string;
- context.fillText(scaleLabel.labelString, 0, 0);
- context.restore();
}
if (axisWidth) {
// Draw the line at the edge of the axis
var firstLineWidth = axisWidth;
- var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, ticks.length - 1, 0);
+ var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1);
+ var borderValue = items.borderValue;
var x1, x2, y1, y2;
- if (isHorizontal) {
+ if (me.isHorizontal()) {
x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
y1 = y2 = borderValue;
} else {
y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
x1 = x2 = borderValue;
}
- context.lineWidth = axisWidth;
- context.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
- context.beginPath();
- context.moveTo(x1, y1);
- context.lineTo(x2, y2);
- context.stroke();
+ ctx.lineWidth = axisWidth;
+ ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
}
+ },
+
+ /**
+ * @private
+ */
+ _drawLabels: function() {
+ var me = this;
+ var optionTicks = me.options.ticks;
+
+ if (!optionTicks.display) {
+ return;
+ }
+
+ var ctx = me.ctx;
+ var items = me._labelItems || (me._labelItems = me._computeLabelItems());
+ var i, j, ilen, jlen, item, tickFont, label, y;
+
+ for (i = 0, ilen = items.length; i < ilen; ++i) {
+ item = items[i];
+ tickFont = item.font;
+
+ // Make sure we draw text in the correct color and font
+ ctx.save();
+ ctx.translate(item.x, item.y);
+ ctx.rotate(item.rotation);
+ ctx.font = tickFont.string;
+ ctx.fillStyle = tickFont.color;
+ ctx.textBaseline = 'middle';
+ ctx.textAlign = item.textAlign;
+
+ label = item.label;
+ y = item.textOffset;
+ if (isArray(label)) {
+ for (j = 0, jlen = label.length; j < jlen; ++j) {
+ // We just make sure the multiline element is a string here..
+ ctx.fillText('' + label[j], 0, y);
+ y += tickFont.lineHeight;
+ }
+ } else {
+ ctx.fillText(label, 0, y);
+ }
+ ctx.restore();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _drawTitle: function() {
+ var me = this;
+ var ctx = me.ctx;
+ var options = me.options;
+ var scaleLabel = options.scaleLabel;
+
+ if (!scaleLabel.display) {
+ return;
+ }
+
+ var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor);
+ var scaleLabelFont = helpers$1.options._parseFont(scaleLabel);
+ var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
+ var halfLineHeight = scaleLabelFont.lineHeight / 2;
+ var position = options.position;
+ var rotation = 0;
+ var scaleLabelX, scaleLabelY;
+
+ if (me.isHorizontal()) {
+ scaleLabelX = me.left + me.width / 2; // midpoint of the width
+ scaleLabelY = position === 'bottom'
+ ? me.bottom - halfLineHeight - scaleLabelPadding.bottom
+ : me.top + halfLineHeight + scaleLabelPadding.top;
+ } else {
+ var isLeft = position === 'left';
+ scaleLabelX = isLeft
+ ? me.left + halfLineHeight + scaleLabelPadding.top
+ : me.right - halfLineHeight - scaleLabelPadding.top;
+ scaleLabelY = me.top + me.height / 2;
+ rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
+ }
+
+ ctx.save();
+ ctx.translate(scaleLabelX, scaleLabelY);
+ ctx.rotate(rotation);
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillStyle = scaleLabelFontColor; // render in correct colour
+ ctx.font = scaleLabelFont.string;
+ ctx.fillText(scaleLabel.labelString, 0, 0);
+ ctx.restore();
+ },
+
+ draw: function(chartArea) {
+ var me = this;
+
+ if (!me._isVisible()) {
+ return;
+ }
+
+ me._drawGrid(chartArea);
+ me._drawTitle();
+ me._drawLabels();
+ },
+
+ /**
+ * @private
+ */
+ _layers: function() {
+ var me = this;
+ var opts = me.options;
+ var tz = opts.ticks && opts.ticks.z || 0;
+ var gz = opts.gridLines && opts.gridLines.z || 0;
+
+ if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
+ // backward compatibility: draw has been overridden by custom scale
+ return [{
+ z: tz,
+ draw: function() {
+ me.draw.apply(me, arguments);
+ }
+ }];
+ }
+
+ return [{
+ z: gz,
+ draw: function() {
+ me._drawGrid.apply(me, arguments);
+ me._drawTitle.apply(me, arguments);
+ }
+ }, {
+ z: tz,
+ draw: function() {
+ me._drawLabels.apply(me, arguments);
+ }
+ }];
+ },
+
+ /**
+ * @private
+ */
+ _getMatchingVisibleMetas: function(type) {
+ var me = this;
+ var isHorizontal = me.isHorizontal();
+ return me.chart._getSortedVisibleDatasetMetas()
+ .filter(function(meta) {
+ return (!type || meta.type === type)
+ && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
+ });
}
});
+Scale.prototype._draw = Scale.prototype.draw;
+
+var core_scale = Scale;
+
+var isNullOrUndef$1 = helpers$1.isNullOrUndef;
+
var defaultConfig = {
position: 'bottom'
};
var scale_category = core_scale.extend({
- /**
- * Internal function to get the correct labels. If data.xLabels or data.yLabels are defined, use those
- * else fall back to data.labels
- * @private
- */
- getLabels: function() {
- var data = this.chart.data;
- return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels;
- },
-
determineDataLimits: function() {
var me = this;
- var labels = me.getLabels();
- me.minIndex = 0;
- me.maxIndex = labels.length - 1;
+ var labels = me._getLabels();
+ var ticksOpts = me.options.ticks;
+ var min = ticksOpts.min;
+ var max = ticksOpts.max;
+ var minIndex = 0;
+ var maxIndex = labels.length - 1;
var findIndex;
- if (me.options.ticks.min !== undefined) {
+ if (min !== undefined) {
// user specified min value
- findIndex = labels.indexOf(me.options.ticks.min);
- me.minIndex = findIndex !== -1 ? findIndex : me.minIndex;
+ findIndex = labels.indexOf(min);
+ if (findIndex >= 0) {
+ minIndex = findIndex;
+ }
}
- if (me.options.ticks.max !== undefined) {
+ if (max !== undefined) {
// user specified max value
- findIndex = labels.indexOf(me.options.ticks.max);
- me.maxIndex = findIndex !== -1 ? findIndex : me.maxIndex;
+ findIndex = labels.indexOf(max);
+ if (findIndex >= 0) {
+ maxIndex = findIndex;
+ }
}
- me.min = labels[me.minIndex];
- me.max = labels[me.maxIndex];
+ me.minIndex = minIndex;
+ me.maxIndex = maxIndex;
+ me.min = labels[minIndex];
+ me.max = labels[maxIndex];
},
buildTicks: function() {
var me = this;
- var labels = me.getLabels();
+ var labels = me._getLabels();
+ var minIndex = me.minIndex;
+ var maxIndex = me.maxIndex;
+
// If we are viewing some subset of labels, slice the original array
- me.ticks = (me.minIndex === 0 && me.maxIndex === labels.length - 1) ? labels : labels.slice(me.minIndex, me.maxIndex + 1);
+ me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1);
},
getLabelForIndex: function(index, datasetIndex) {
var me = this;
var chart = me.chart;
if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
}
- return me.ticks[index - me.minIndex];
+ return me._getLabels()[index];
},
- // Used to get data value locations. Value can either be an index or a numerical value
- getPixelForValue: function(value, index) {
+ _configure: function() {
var me = this;
var offset = me.options.offset;
- // 1 is added because we need the length but we have the indexes
- var offsetAmt = Math.max((me.maxIndex + 1 - me.minIndex - (offset ? 0 : 1)), 1);
+ var ticks = me.ticks;
- // If value is a data object, then index is the index in the data array,
- // not the index of the scale. We need to change that.
- var valueCategory;
- if (value !== undefined && value !== null) {
- valueCategory = me.isHorizontal() ? value.x : value.y;
+ core_scale.prototype._configure.call(me);
+
+ if (!me.isHorizontal()) {
+ // For backward compatibility, vertical category scale reverse is inverted.
+ me._reversePixels = !me._reversePixels;
}
- if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
- var labels = me.getLabels();
- value = valueCategory || value;
- var idx = labels.indexOf(value);
- index = idx !== -1 ? idx : index;
+
+ if (!ticks) {
+ return;
}
- if (me.isHorizontal()) {
- var valueWidth = me.width / offsetAmt;
- var widthOffset = (valueWidth * (index - me.minIndex));
+ me._startValue = me.minIndex - (offset ? 0.5 : 0);
+ me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1);
+ },
- if (offset) {
- widthOffset += (valueWidth / 2);
- }
+ // Used to get data value locations. Value can either be an index or a numerical value
+ getPixelForValue: function(value, index, datasetIndex) {
+ var me = this;
+ var valueCategory, labels, idx;
- return me.left + widthOffset;
+ if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) {
+ value = me.chart.data.datasets[datasetIndex].data[index];
}
- var valueHeight = me.height / offsetAmt;
- var heightOffset = (valueHeight * (index - me.minIndex));
- if (offset) {
- heightOffset += (valueHeight / 2);
+ // If value is a data object, then index is the index in the data array,
+ // not the index of the scale. We need to change that.
+ if (!isNullOrUndef$1(value)) {
+ valueCategory = me.isHorizontal() ? value.x : value.y;
}
-
- return me.top + heightOffset;
+ if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
+ labels = me._getLabels();
+ value = helpers$1.valueOrDefault(valueCategory, value);
+ idx = labels.indexOf(value);
+ index = idx !== -1 ? idx : index;
+ if (isNaN(index)) {
+ index = value;
+ }
+ }
+ return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
},
getPixelForTick: function(index) {
- return this.getPixelForValue(this.ticks[index], index + this.minIndex, null);
+ var ticks = this.ticks;
+ return index < 0 || index > ticks.length - 1
+ ? null
+ : this.getPixelForValue(ticks[index], index + this.minIndex);
},
getValueForPixel: function(pixel) {
var me = this;
- var offset = me.options.offset;
- var value;
- var offsetAmt = Math.max((me._ticks.length - (offset ? 0 : 1)), 1);
- var horz = me.isHorizontal();
- var valueDimension = (horz ? me.width : me.height) / offsetAmt;
-
- pixel -= horz ? me.left : me.top;
-
- if (offset) {
- pixel -= (valueDimension / 2);
- }
-
- if (pixel <= 0) {
- value = 0;
- } else {
- value = Math.round(pixel / valueDimension);
- }
-
- return value + me.minIndex;
+ var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange);
+ return Math.min(Math.max(value, 0), me.ticks.length - 1);
},
getBasePixel: function() {
return this.bottom;
}
@@ -11263,11 +12652,11 @@
// INTERNAL: static default options, registered in src/index.js
var _defaults = defaultConfig;
scale_category._defaults = _defaults;
var noop = helpers$1.noop;
-var isNullOrUndef = helpers$1.isNullOrUndef;
+var isNullOrUndef$2 = helpers$1.isNullOrUndef;
/**
* Generate a set of linear ticks
* @param generationOptions the options used to generate the ticks
* @param dataRange the range of the data
@@ -11291,21 +12680,21 @@
var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
var factor, niceMin, niceMax, numSpaces;
// Beyond MIN_SPACING floating point numbers being to lose precision
// such that we can't do the math necessary to generate ticks
- if (spacing < MIN_SPACING && isNullOrUndef(min) && isNullOrUndef(max)) {
+ if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) {
return [rmin, rmax];
}
numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
if (numSpaces > maxNumSpaces) {
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
}
- if (stepSize || isNullOrUndef(precision)) {
+ if (stepSize || isNullOrUndef$2(precision)) {
// If a precision is not specified, calculate factor based on spacing
factor = Math.pow(10, helpers$1._decimalPlaces(spacing));
} else {
// If the user specified a precision, round to that number of decimal places
factor = Math.pow(10, precision);
@@ -11316,14 +12705,14 @@
niceMax = Math.ceil(rmax / spacing) * spacing;
// If min, max and stepSize is set and they make an evenly spaced scale use it.
if (stepSize) {
// If very close to our whole number, use it.
- if (!isNullOrUndef(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
+ if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
niceMin = min;
}
- if (!isNullOrUndef(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
+ if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
niceMax = max;
}
}
numSpaces = (niceMax - niceMin) / spacing;
@@ -11334,15 +12723,15 @@
numSpaces = Math.ceil(numSpaces);
}
niceMin = Math.round(niceMin * factor) / factor;
niceMax = Math.round(niceMax * factor) / factor;
- ticks.push(isNullOrUndef(min) ? niceMin : min);
+ ticks.push(isNullOrUndef$2(min) ? niceMin : min);
for (var j = 1; j < numSpaces; ++j) {
ticks.push(Math.round((niceMin + j * spacing) * factor) / factor);
}
- ticks.push(isNullOrUndef(max) ? niceMax : max);
+ ticks.push(isNullOrUndef$2(max) ? niceMax : max);
return ticks;
}
var scale_linearbase = core_scale.extend({
@@ -11490,137 +12879,146 @@
var me = this;
me.ticksAsNumbers = me.ticks.slice();
me.zeroLineIndex = me.ticks.indexOf(0);
core_scale.prototype.convertTicksToLabels.call(me);
+ },
+
+ _configure: function() {
+ var me = this;
+ var ticks = me.getTicks();
+ var start = me.min;
+ var end = me.max;
+ var offset;
+
+ core_scale.prototype._configure.call(me);
+
+ if (me.options.offset && ticks.length) {
+ offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
+ start -= offset;
+ end += offset;
+ }
+ me._startValue = start;
+ me._endValue = end;
+ me._valueRange = end - start;
}
});
var defaultConfig$1 = {
position: 'left',
ticks: {
callback: core_ticks.formatters.linear
}
};
-var scale_linear = scale_linearbase.extend({
- determineDataLimits: function() {
- var me = this;
- var opts = me.options;
- var chart = me.chart;
- var data = chart.data;
- var datasets = data.datasets;
- var isHorizontal = me.isHorizontal();
- var DEFAULT_MIN = 0;
- var DEFAULT_MAX = 1;
+var DEFAULT_MIN = 0;
+var DEFAULT_MAX = 1;
- function IDMatches(meta) {
- return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
- }
+function getOrCreateStack(stacks, stacked, meta) {
+ var key = [
+ meta.type,
+ // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
+ stacked === undefined && meta.stack === undefined ? meta.index : '',
+ meta.stack
+ ].join('.');
- // First Calculate the range
- me.min = null;
- me.max = null;
+ if (stacks[key] === undefined) {
+ stacks[key] = {
+ pos: [],
+ neg: []
+ };
+ }
- var hasStacks = opts.stacked;
- if (hasStacks === undefined) {
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- if (hasStacks) {
- return;
- }
+ return stacks[key];
+}
- var meta = chart.getDatasetMeta(datasetIndex);
- if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
- meta.stack !== undefined) {
- hasStacks = true;
- }
- });
+function stackData(scale, stacks, meta, data) {
+ var opts = scale.options;
+ var stacked = opts.stacked;
+ var stack = getOrCreateStack(stacks, stacked, meta);
+ var pos = stack.pos;
+ var neg = stack.neg;
+ var ilen = data.length;
+ var i, value;
+
+ for (i = 0; i < ilen; ++i) {
+ value = scale._parseValue(data[i]);
+ if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
+ continue;
}
- if (opts.stacked || hasStacks) {
- var valuesPerStack = {};
+ pos[i] = pos[i] || 0;
+ neg[i] = neg[i] || 0;
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- var meta = chart.getDatasetMeta(datasetIndex);
- var key = [
- meta.type,
- // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
- ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
- meta.stack
- ].join('.');
+ if (opts.relativePoints) {
+ pos[i] = 100;
+ } else if (value.min < 0 || value.max < 0) {
+ neg[i] += value.min;
+ } else {
+ pos[i] += value.max;
+ }
+ }
+}
- if (valuesPerStack[key] === undefined) {
- valuesPerStack[key] = {
- positiveValues: [],
- negativeValues: []
- };
- }
+function updateMinMax(scale, meta, data) {
+ var ilen = data.length;
+ var i, value;
- // Store these per type
- var positiveValues = valuesPerStack[key].positiveValues;
- var negativeValues = valuesPerStack[key].negativeValues;
+ for (i = 0; i < ilen; ++i) {
+ value = scale._parseValue(data[i]);
+ if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
+ continue;
+ }
- if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
- helpers$1.each(dataset.data, function(rawValue, index) {
- var value = +me.getRightValue(rawValue);
- if (isNaN(value) || meta.data[index].hidden) {
- return;
- }
+ scale.min = Math.min(scale.min, value.min);
+ scale.max = Math.max(scale.max, value.max);
+ }
+}
- positiveValues[index] = positiveValues[index] || 0;
- negativeValues[index] = negativeValues[index] || 0;
+var scale_linear = scale_linearbase.extend({
+ determineDataLimits: function() {
+ var me = this;
+ var opts = me.options;
+ var chart = me.chart;
+ var datasets = chart.data.datasets;
+ var metasets = me._getMatchingVisibleMetas();
+ var hasStacks = opts.stacked;
+ var stacks = {};
+ var ilen = metasets.length;
+ var i, meta, data, values;
- if (opts.relativePoints) {
- positiveValues[index] = 100;
- } else if (value < 0) {
- negativeValues[index] += value;
- } else {
- positiveValues[index] += value;
- }
- });
- }
- });
+ me.min = Number.POSITIVE_INFINITY;
+ me.max = Number.NEGATIVE_INFINITY;
- helpers$1.each(valuesPerStack, function(valuesForType) {
- var values = valuesForType.positiveValues.concat(valuesForType.negativeValues);
- var minVal = helpers$1.min(values);
- var maxVal = helpers$1.max(values);
- me.min = me.min === null ? minVal : Math.min(me.min, minVal);
- me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
- });
+ if (hasStacks === undefined) {
+ for (i = 0; !hasStacks && i < ilen; ++i) {
+ meta = metasets[i];
+ hasStacks = meta.stack !== undefined;
+ }
+ }
- } else {
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- var meta = chart.getDatasetMeta(datasetIndex);
- if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
- helpers$1.each(dataset.data, function(rawValue, index) {
- var value = +me.getRightValue(rawValue);
- if (isNaN(value) || meta.data[index].hidden) {
- return;
- }
-
- if (me.min === null) {
- me.min = value;
- } else if (value < me.min) {
- me.min = value;
- }
-
- if (me.max === null) {
- me.max = value;
- } else if (value > me.max) {
- me.max = value;
- }
- });
- }
- });
+ for (i = 0; i < ilen; ++i) {
+ meta = metasets[i];
+ data = datasets[meta.index].data;
+ if (hasStacks) {
+ stackData(me, stacks, meta, data);
+ } else {
+ updateMinMax(me, meta, data);
+ }
}
- me.min = isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
- me.max = isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
+ helpers$1.each(stacks, function(stackValues) {
+ values = stackValues.pos.concat(stackValues.neg);
+ me.min = Math.min(me.min, helpers$1.min(values));
+ me.max = Math.max(me.max, helpers$1.max(values));
+ });
+ me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
+ me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
+
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
- this.handleTickRangeOptions();
+ me.handleTickRangeOptions();
},
// Returns the maximum number of ticks based on the scale dimension
_computeTickLimit: function() {
var me = this;
@@ -11640,74 +13038,62 @@
this.ticks.reverse();
}
},
getLabelForIndex: function(index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
},
// Utils
getPixelForValue: function(value) {
- // This must be called after fit has been run so that
- // this.left, this.top, this.right, and this.bottom have been defined
var me = this;
- var start = me.start;
-
- var rightValue = +me.getRightValue(value);
- var pixel;
- var range = me.end - start;
-
- if (me.isHorizontal()) {
- pixel = me.left + (me.width / range * (rightValue - start));
- } else {
- pixel = me.bottom - (me.height / range * (rightValue - start));
- }
- return pixel;
+ return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
},
getValueForPixel: function(pixel) {
- var me = this;
- var isHorizontal = me.isHorizontal();
- var innerDimension = isHorizontal ? me.width : me.height;
- var offset = (isHorizontal ? pixel - me.left : me.bottom - pixel) / innerDimension;
- return me.start + ((me.end - me.start) * offset);
+ return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
},
getPixelForTick: function(index) {
- return this.getPixelForValue(this.ticksAsNumbers[index]);
+ var ticks = this.ticksAsNumbers;
+ if (index < 0 || index > ticks.length - 1) {
+ return null;
+ }
+ return this.getPixelForValue(ticks[index]);
}
});
// INTERNAL: static default options, registered in src/index.js
var _defaults$1 = defaultConfig$1;
scale_linear._defaults = _defaults$1;
-var valueOrDefault$a = helpers$1.valueOrDefault;
+var valueOrDefault$b = helpers$1.valueOrDefault;
+var log10 = helpers$1.math.log10;
/**
* Generate a set of logarithmic ticks
* @param generationOptions the options used to generate the ticks
* @param dataRange the range of the data
* @returns {number[]} array of tick values
*/
function generateTicks$1(generationOptions, dataRange) {
var ticks = [];
- var tickVal = valueOrDefault$a(generationOptions.min, Math.pow(10, Math.floor(helpers$1.log10(dataRange.min))));
+ var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
- var endExp = Math.floor(helpers$1.log10(dataRange.max));
+ var endExp = Math.floor(log10(dataRange.max));
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
var exp, significand;
if (tickVal === 0) {
- exp = Math.floor(helpers$1.log10(dataRange.minNotZero));
+ exp = Math.floor(log10(dataRange.minNotZero));
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
ticks.push(tickVal);
tickVal = significand * Math.pow(10, exp);
} else {
- exp = Math.floor(helpers$1.log10(tickVal));
+ exp = Math.floor(log10(tickVal));
significand = Math.floor(tickVal / Math.pow(10, exp));
}
var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
do {
@@ -11721,11 +13107,11 @@
}
tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
} while (exp < endExp || (exp === endExp && significand < endSignificand));
- var lastTick = valueOrDefault$a(generationOptions.max, tickVal);
+ var lastTick = valueOrDefault$b(generationOptions.max, tickVal);
ticks.push(lastTick);
return ticks;
}
@@ -11746,42 +13132,39 @@
var scale_logarithmic = core_scale.extend({
determineDataLimits: function() {
var me = this;
var opts = me.options;
var chart = me.chart;
- var data = chart.data;
- var datasets = data.datasets;
+ var datasets = chart.data.datasets;
var isHorizontal = me.isHorizontal();
function IDMatches(meta) {
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
}
+ var datasetIndex, meta, value, data, i, ilen;
// Calculate Range
- me.min = null;
- me.max = null;
- me.minNotZero = null;
+ me.min = Number.POSITIVE_INFINITY;
+ me.max = Number.NEGATIVE_INFINITY;
+ me.minNotZero = Number.POSITIVE_INFINITY;
var hasStacks = opts.stacked;
if (hasStacks === undefined) {
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- if (hasStacks) {
- return;
- }
-
- var meta = chart.getDatasetMeta(datasetIndex);
+ for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
+ meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
meta.stack !== undefined) {
hasStacks = true;
+ break;
}
- });
+ }
}
if (opts.stacked || hasStacks) {
var valuesPerStack = {};
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- var meta = chart.getDatasetMeta(datasetIndex);
+ for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
+ meta = chart.getDatasetMeta(datasetIndex);
var key = [
meta.type,
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
meta.stack
@@ -11790,63 +13173,60 @@
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
if (valuesPerStack[key] === undefined) {
valuesPerStack[key] = [];
}
- helpers$1.each(dataset.data, function(rawValue, index) {
+ data = datasets[datasetIndex].data;
+ for (i = 0, ilen = data.length; i < ilen; i++) {
var values = valuesPerStack[key];
- var value = +me.getRightValue(rawValue);
+ value = me._parseValue(data[i]);
// invalid, hidden and negative values are ignored
- if (isNaN(value) || meta.data[index].hidden || value < 0) {
- return;
+ if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
+ continue;
}
- values[index] = values[index] || 0;
- values[index] += value;
- });
+ values[i] = values[i] || 0;
+ values[i] += value.max;
+ }
}
- });
+ }
helpers$1.each(valuesPerStack, function(valuesForType) {
if (valuesForType.length > 0) {
var minVal = helpers$1.min(valuesForType);
var maxVal = helpers$1.max(valuesForType);
- me.min = me.min === null ? minVal : Math.min(me.min, minVal);
- me.max = me.max === null ? maxVal : Math.max(me.max, maxVal);
+ me.min = Math.min(me.min, minVal);
+ me.max = Math.max(me.max, maxVal);
}
});
} else {
- helpers$1.each(datasets, function(dataset, datasetIndex) {
- var meta = chart.getDatasetMeta(datasetIndex);
+ for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
+ meta = chart.getDatasetMeta(datasetIndex);
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
- helpers$1.each(dataset.data, function(rawValue, index) {
- var value = +me.getRightValue(rawValue);
+ data = datasets[datasetIndex].data;
+ for (i = 0, ilen = data.length; i < ilen; i++) {
+ value = me._parseValue(data[i]);
// invalid, hidden and negative values are ignored
- if (isNaN(value) || meta.data[index].hidden || value < 0) {
- return;
+ if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
+ continue;
}
- if (me.min === null) {
- me.min = value;
- } else if (value < me.min) {
- me.min = value;
- }
+ me.min = Math.min(value.min, me.min);
+ me.max = Math.max(value.max, me.max);
- if (me.max === null) {
- me.max = value;
- } else if (value > me.max) {
- me.max = value;
+ if (value.min !== 0) {
+ me.minNotZero = Math.min(value.min, me.minNotZero);
}
-
- if (value !== 0 && (me.minNotZero === null || value < me.minNotZero)) {
- me.minNotZero = value;
- }
- });
+ }
}
- });
+ }
}
+ me.min = helpers$1.isFinite(me.min) ? me.min : null;
+ me.max = helpers$1.isFinite(me.max) ? me.max : null;
+ me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null;
+
// Common base implementation to handle ticks.min, ticks.max
this.handleTickRangeOptions();
},
handleTickRangeOptions: function() {
@@ -11858,30 +13238,30 @@
me.min = nonNegativeOrDefault(tickOpts.min, me.min);
me.max = nonNegativeOrDefault(tickOpts.max, me.max);
if (me.min === me.max) {
if (me.min !== 0 && me.min !== null) {
- me.min = Math.pow(10, Math.floor(helpers$1.log10(me.min)) - 1);
- me.max = Math.pow(10, Math.floor(helpers$1.log10(me.max)) + 1);
+ me.min = Math.pow(10, Math.floor(log10(me.min)) - 1);
+ me.max = Math.pow(10, Math.floor(log10(me.max)) + 1);
} else {
me.min = DEFAULT_MIN;
me.max = DEFAULT_MAX;
}
}
if (me.min === null) {
- me.min = Math.pow(10, Math.floor(helpers$1.log10(me.max)) - 1);
+ me.min = Math.pow(10, Math.floor(log10(me.max)) - 1);
}
if (me.max === null) {
me.max = me.min !== 0
- ? Math.pow(10, Math.floor(helpers$1.log10(me.min)) + 1)
+ ? Math.pow(10, Math.floor(log10(me.min)) + 1)
: DEFAULT_MAX;
}
if (me.minNotZero === null) {
if (me.min > 0) {
me.minNotZero = me.min;
} else if (me.max < 1) {
- me.minNotZero = Math.pow(10, Math.floor(helpers$1.log10(me.max)));
+ me.minNotZero = Math.pow(10, Math.floor(log10(me.max)));
} else {
me.minNotZero = DEFAULT_MIN;
}
}
},
@@ -11921,126 +13301,90 @@
core_scale.prototype.convertTicksToLabels.call(this);
},
// Get the correct tooltip label
getLabelForIndex: function(index, datasetIndex) {
- return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
+ return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
},
getPixelForTick: function(index) {
- return this.getPixelForValue(this.tickValues[index]);
+ var ticks = this.tickValues;
+ if (index < 0 || index > ticks.length - 1) {
+ return null;
+ }
+ return this.getPixelForValue(ticks[index]);
},
/**
* Returns the value of the first tick.
* @param {number} value - The minimum not zero value.
* @return {number} The first tick value.
* @private
*/
_getFirstTickValue: function(value) {
- var exp = Math.floor(helpers$1.log10(value));
+ var exp = Math.floor(log10(value));
var significand = Math.floor(value / Math.pow(10, exp));
return significand * Math.pow(10, exp);
},
- getPixelForValue: function(value) {
+ _configure: function() {
var me = this;
- var tickOpts = me.options.ticks;
- var reverse = tickOpts.reverse;
- var log10 = helpers$1.log10;
- var firstTickValue = me._getFirstTickValue(me.minNotZero);
+ var start = me.min;
var offset = 0;
- var innerDimension, pixel, start, end, sign;
- value = +me.getRightValue(value);
- if (reverse) {
- start = me.end;
- end = me.start;
- sign = -1;
- } else {
- start = me.start;
- end = me.end;
- sign = 1;
+ core_scale.prototype._configure.call(me);
+
+ if (start === 0) {
+ start = me._getFirstTickValue(me.minNotZero);
+ offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length;
}
- if (me.isHorizontal()) {
- innerDimension = me.width;
- pixel = reverse ? me.right : me.left;
- } else {
- innerDimension = me.height;
- sign *= -1; // invert, since the upper-left corner of the canvas is at pixel (0, 0)
- pixel = reverse ? me.top : me.bottom;
- }
- if (value !== start) {
- if (start === 0) { // include zero tick
- offset = valueOrDefault$a(tickOpts.fontSize, core_defaults.global.defaultFontSize);
- innerDimension -= offset;
- start = firstTickValue;
- }
- if (value !== 0) {
- offset += innerDimension / (log10(end) - log10(start)) * (log10(value) - log10(start));
- }
- pixel += sign * offset;
- }
- return pixel;
+
+ me._startValue = log10(start);
+ me._valueOffset = offset;
+ me._valueRange = (log10(me.max) - log10(start)) / (1 - offset);
},
- getValueForPixel: function(pixel) {
+ getPixelForValue: function(value) {
var me = this;
- var tickOpts = me.options.ticks;
- var reverse = tickOpts.reverse;
- var log10 = helpers$1.log10;
- var firstTickValue = me._getFirstTickValue(me.minNotZero);
- var innerDimension, start, end, value;
+ var decimal = 0;
- if (reverse) {
- start = me.end;
- end = me.start;
- } else {
- start = me.start;
- end = me.end;
+ value = +me.getRightValue(value);
+
+ if (value > me.min && value > 0) {
+ decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
}
- if (me.isHorizontal()) {
- innerDimension = me.width;
- value = reverse ? me.right - pixel : pixel - me.left;
- } else {
- innerDimension = me.height;
- value = reverse ? pixel - me.top : me.bottom - pixel;
- }
- if (value !== start) {
- if (start === 0) { // include zero tick
- var offset = valueOrDefault$a(tickOpts.fontSize, core_defaults.global.defaultFontSize);
- value -= offset;
- innerDimension -= offset;
- start = firstTickValue;
- }
- value *= log10(end) - log10(start);
- value /= innerDimension;
- value = Math.pow(10, log10(start) + value);
- }
- return value;
+ return me.getPixelForDecimal(decimal);
+ },
+
+ getValueForPixel: function(pixel) {
+ var me = this;
+ var decimal = me.getDecimalForPixel(pixel);
+ return decimal === 0 && me.min === 0
+ ? 0
+ : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange);
}
});
// INTERNAL: static default options, registered in src/index.js
var _defaults$2 = defaultConfig$2;
scale_logarithmic._defaults = _defaults$2;
-var valueOrDefault$b = helpers$1.valueOrDefault;
+var valueOrDefault$c = helpers$1.valueOrDefault;
var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault;
-var resolve$7 = helpers$1.options.resolve;
+var resolve$4 = helpers$1.options.resolve;
var defaultConfig$3 = {
display: true,
// Boolean - Whether to animate scaling the chart from the centre
animate: true,
position: 'chartArea',
angleLines: {
display: true,
- color: 'rgba(0, 0, 0, 0.1)',
+ color: 'rgba(0,0,0,0.1)',
lineWidth: 1,
borderDash: [],
borderDashOffset: 0.0
},
@@ -12077,20 +13421,15 @@
return label;
}
}
};
-function getValueCount(scale) {
- var opts = scale.options;
- return opts.angleLines.display || opts.pointLabels.display ? scale.chart.data.labels.length : 0;
-}
-
function getTickBackdropHeight(opts) {
var tickOpts = opts.ticks;
if (tickOpts.display && opts.display) {
- return valueOrDefault$b(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
+ return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
}
return 0;
}
function measureLabelSize(ctx, lineHeight, label) {
@@ -12171,14 +13510,14 @@
var i, textSize, pointPosition;
scale.ctx.font = plFont.string;
scale._pointLabelSizes = [];
- var valueCount = getValueCount(scale);
+ var valueCount = scale.chart.data.labels.length;
for (i = 0; i < valueCount; i++) {
pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
- textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i] || '');
+ textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]);
scale._pointLabelSizes[i] = textSize;
// Add quarter circle to make degree 0 mean top of circle
var angleRadians = scale.getIndexAngle(i);
var angle = helpers$1.toDegrees(angleRadians) % 360;
@@ -12242,65 +13581,42 @@
}
function drawPointLabels(scale) {
var ctx = scale.ctx;
var opts = scale.options;
- var angleLineOpts = opts.angleLines;
- var gridLineOpts = opts.gridLines;
var pointLabelOpts = opts.pointLabels;
- var lineWidth = valueOrDefault$b(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
- var lineColor = valueOrDefault$b(angleLineOpts.color, gridLineOpts.color);
var tickBackdropHeight = getTickBackdropHeight(opts);
-
- ctx.save();
- ctx.lineWidth = lineWidth;
- ctx.strokeStyle = lineColor;
- if (ctx.setLineDash) {
- ctx.setLineDash(resolve$7([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
- ctx.lineDashOffset = resolve$7([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
- }
-
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
-
- // Point Label Font
var plFont = helpers$1.options._parseFont(pointLabelOpts);
+ ctx.save();
+
ctx.font = plFont.string;
ctx.textBaseline = 'middle';
- for (var i = getValueCount(scale) - 1; i >= 0; i--) {
- if (angleLineOpts.display && lineWidth && lineColor) {
- var outerPosition = scale.getPointPosition(i, outerDistance);
- ctx.beginPath();
- ctx.moveTo(scale.xCenter, scale.yCenter);
- ctx.lineTo(outerPosition.x, outerPosition.y);
- ctx.stroke();
- }
+ for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) {
+ // Extra pixels out for some label spacing
+ var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
+ var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
- if (pointLabelOpts.display) {
- // Extra pixels out for some label spacing
- var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
- var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
+ // Keep this in loop since we may support array properties here
+ var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
+ ctx.fillStyle = pointLabelFontColor;
- // Keep this in loop since we may support array properties here
- var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
- ctx.fillStyle = pointLabelFontColor;
-
- var angleRadians = scale.getIndexAngle(i);
- var angle = helpers$1.toDegrees(angleRadians);
- ctx.textAlign = getTextAlignForAngle(angle);
- adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
- fillText(ctx, scale.pointLabels[i] || '', pointLabelPosition, plFont.lineHeight);
- }
+ var angleRadians = scale.getIndexAngle(i);
+ var angle = helpers$1.toDegrees(angleRadians);
+ ctx.textAlign = getTextAlignForAngle(angle);
+ adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
+ fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
}
ctx.restore();
}
function drawRadiusLine(scale, gridLineOpts, radius, index) {
var ctx = scale.ctx;
var circular = gridLineOpts.circular;
- var valueCount = getValueCount(scale);
+ var valueCount = scale.chart.data.labels.length;
var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1);
var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1);
var pointPosition;
if ((!circular && !valueCount) || !lineColor || !lineWidth) {
@@ -12389,11 +13705,14 @@
var me = this;
scale_linearbase.prototype.convertTicksToLabels.call(me);
// Point labels
- me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
+ me.pointLabels = me.chart.data.labels.map(function() {
+ var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me);
+ return label || label === 0 ? label : '';
+ });
},
getLabelForIndex: function(index, datasetIndex) {
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
},
@@ -12441,26 +13760,26 @@
me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
},
getIndexAngle: function(index) {
- var angleMultiplier = (Math.PI * 2) / getValueCount(this);
- var startAngle = this.chart.options && this.chart.options.startAngle ?
- this.chart.options.startAngle :
- 0;
+ var chart = this.chart;
+ var angleMultiplier = 360 / chart.data.labels.length;
+ var options = chart.options || {};
+ var startAngle = options.startAngle || 0;
- var startAngleRadians = startAngle * Math.PI * 2 / 360;
-
// Start from the top instead of right, so remove a quarter of the circle
- return index * angleMultiplier + startAngleRadians;
+ var angle = (index * angleMultiplier + startAngle) % 360;
+
+ return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360;
},
getDistanceFromCenterForValue: function(value) {
var me = this;
- if (value === null) {
- return 0; // null always in center
+ if (helpers$1.isNullOrUndef(value)) {
+ return NaN;
}
// Take into account half font size + the yPadding of the top value
var scalingFactor = me.drawingArea / (me.max - me.min);
if (me.options.ticks.reverse) {
@@ -12480,128 +13799,179 @@
getPointPositionForValue: function(index, value) {
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
},
- getBasePosition: function() {
+ getBasePosition: function(index) {
var me = this;
var min = me.min;
var max = me.max;
- return me.getPointPositionForValue(0,
+ return me.getPointPositionForValue(index || 0,
me.beginAtZero ? 0 :
min < 0 && max < 0 ? max :
min > 0 && max > 0 ? min :
0);
},
- draw: function() {
+ /**
+ * @private
+ */
+ _drawGrid: function() {
var me = this;
+ var ctx = me.ctx;
var opts = me.options;
var gridLineOpts = opts.gridLines;
+ var angleLineOpts = opts.angleLines;
+ var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
+ var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color);
+ var i, offset, position;
+
+ if (opts.pointLabels.display) {
+ drawPointLabels(me);
+ }
+
+ if (gridLineOpts.display) {
+ helpers$1.each(me.ticks, function(label, index) {
+ if (index !== 0) {
+ offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+ drawRadiusLine(me, gridLineOpts, offset, index);
+ }
+ });
+ }
+
+ if (angleLineOpts.display && lineWidth && lineColor) {
+ ctx.save();
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = lineColor;
+ if (ctx.setLineDash) {
+ ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
+ ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
+ }
+
+ for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
+ offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
+ position = me.getPointPosition(i, offset);
+ ctx.beginPath();
+ ctx.moveTo(me.xCenter, me.yCenter);
+ ctx.lineTo(position.x, position.y);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ }
+ },
+
+ /**
+ * @private
+ */
+ _drawLabels: function() {
+ var me = this;
+ var ctx = me.ctx;
+ var opts = me.options;
var tickOpts = opts.ticks;
- if (opts.display) {
- var ctx = me.ctx;
- var startAngle = this.getIndexAngle(0);
- var tickFont = helpers$1.options._parseFont(tickOpts);
+ if (!tickOpts.display) {
+ return;
+ }
- if (opts.angleLines.display || opts.pointLabels.display) {
- drawPointLabels(me);
+ var startAngle = me.getIndexAngle(0);
+ var tickFont = helpers$1.options._parseFont(tickOpts);
+ var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor);
+ var offset, width;
+
+ ctx.save();
+ ctx.font = tickFont.string;
+ ctx.translate(me.xCenter, me.yCenter);
+ ctx.rotate(startAngle);
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ helpers$1.each(me.ticks, function(label, index) {
+ if (index === 0 && !tickOpts.reverse) {
+ return;
}
- helpers$1.each(me.ticks, function(label, index) {
- // Don't draw a centre value (if it is minimum)
- if (index > 0 || tickOpts.reverse) {
- var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
+ offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
- // Draw circular lines around the scale
- if (gridLineOpts.display && index !== 0) {
- drawRadiusLine(me, gridLineOpts, yCenterOffset, index);
- }
+ if (tickOpts.showLabelBackdrop) {
+ width = ctx.measureText(label).width;
+ ctx.fillStyle = tickOpts.backdropColor;
- if (tickOpts.display) {
- var tickFontColor = valueOrDefault$b(tickOpts.fontColor, core_defaults.global.defaultFontColor);
- ctx.font = tickFont.string;
+ ctx.fillRect(
+ -width / 2 - tickOpts.backdropPaddingX,
+ -offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
+ width + tickOpts.backdropPaddingX * 2,
+ tickFont.size + tickOpts.backdropPaddingY * 2
+ );
+ }
- ctx.save();
- ctx.translate(me.xCenter, me.yCenter);
- ctx.rotate(startAngle);
+ ctx.fillStyle = tickFontColor;
+ ctx.fillText(label, 0, -offset);
+ });
- if (tickOpts.showLabelBackdrop) {
- var labelWidth = ctx.measureText(label).width;
- ctx.fillStyle = tickOpts.backdropColor;
- ctx.fillRect(
- -labelWidth / 2 - tickOpts.backdropPaddingX,
- -yCenterOffset - tickFont.size / 2 - tickOpts.backdropPaddingY,
- labelWidth + tickOpts.backdropPaddingX * 2,
- tickFont.size + tickOpts.backdropPaddingY * 2
- );
- }
+ ctx.restore();
+ },
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillStyle = tickFontColor;
- ctx.fillText(label, 0, -yCenterOffset);
- ctx.restore();
- }
- }
- });
- }
- }
+ /**
+ * @private
+ */
+ _drawTitle: helpers$1.noop
});
// INTERNAL: static default options, registered in src/index.js
var _defaults$3 = defaultConfig$3;
scale_radialLinear._defaults = _defaults$3;
-var valueOrDefault$c = helpers$1.valueOrDefault;
+var deprecated$1 = helpers$1._deprecated;
+var resolve$5 = helpers$1.options.resolve;
+var valueOrDefault$d = helpers$1.valueOrDefault;
// Integer constants are from the ES6 spec.
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
var INTERVALS = {
millisecond: {
common: true,
size: 1,
- steps: [1, 2, 5, 10, 20, 50, 100, 250, 500]
+ steps: 1000
},
second: {
common: true,
size: 1000,
- steps: [1, 2, 5, 10, 15, 30]
+ steps: 60
},
minute: {
common: true,
size: 60000,
- steps: [1, 2, 5, 10, 15, 30]
+ steps: 60
},
hour: {
common: true,
size: 3600000,
- steps: [1, 2, 3, 6, 12]
+ steps: 24
},
day: {
common: true,
size: 86400000,
- steps: [1, 2, 5]
+ steps: 30
},
week: {
common: false,
size: 604800000,
- steps: [1, 2, 3, 4]
+ steps: 4
},
month: {
common: true,
size: 2.628e9,
- steps: [1, 2, 3]
+ steps: 12
},
quarter: {
common: false,
size: 7.884e9,
- steps: [1, 2, 3, 4]
+ steps: 4
},
year: {
common: true,
size: 3.154e10
}
@@ -12627,10 +13997,18 @@
}
return out;
}
+function getMin(options) {
+ return helpers$1.valueOrDefault(options.time.min, options.ticks.min);
+}
+
+function getMax(options) {
+ return helpers$1.valueOrDefault(options.time.max, options.ticks.max);
+}
+
/**
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position
* (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
* extremity (left + width or top + height). Note that it would be more optimized to directly
@@ -12779,44 +14157,19 @@
return value;
}
/**
- * Returns the number of unit to skip to be able to display up to `capacity` number of ticks
- * in `unit` for the given `min` / `max` range and respecting the interval steps constraints.
- */
-function determineStepSize(min, max, unit, capacity) {
- var range = max - min;
- var interval = INTERVALS[unit];
- var milliseconds = interval.size;
- var steps = interval.steps;
- var i, ilen, factor;
-
- if (!steps) {
- return Math.ceil(range / (capacity * milliseconds));
- }
-
- for (i = 0, ilen = steps.length; i < ilen; ++i) {
- factor = steps[i];
- if (Math.ceil(range / (milliseconds * factor)) <= capacity) {
- break;
- }
- }
-
- return factor;
-}
-
-/**
* Figures out what unit results in an appropriate number of auto-generated ticks
*/
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
var ilen = UNITS.length;
var i, interval, factor;
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
interval = INTERVALS[UNITS[i]];
- factor = interval.steps ? interval.steps[interval.steps.length - 1] : MAX_INTEGER;
+ factor = interval.steps ? interval.steps : MAX_INTEGER;
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
return UNITS[i];
}
}
@@ -12825,17 +14178,16 @@
}
/**
* Figures out what unit to format a set of ticks with
*/
-function determineUnitForFormatting(scale, ticks, minUnit, min, max) {
- var ilen = UNITS.length;
+function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
var i, unit;
- for (i = ilen - 1; i >= UNITS.indexOf(minUnit); i--) {
+ for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
unit = UNITS[i];
- if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= ticks.length) {
+ if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
return unit;
}
}
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
@@ -12849,64 +14201,46 @@
}
}
/**
* Generates a maximum of `capacity` timestamps between min and max, rounded to the
- * `minor` unit, aligned on the `major` unit and using the given scale time `options`.
+ * `minor` unit using the given scale time `options`.
* Important: this method can return ticks outside the min and max range, it's the
* responsibility of the calling code to clamp values if needed.
*/
function generate(scale, min, max, capacity) {
var adapter = scale._adapter;
var options = scale.options;
var timeOpts = options.time;
var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
- var major = determineMajorUnit(minor);
- var stepSize = valueOrDefault$c(timeOpts.stepSize, timeOpts.unitStepSize);
+ var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]);
var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
- var majorTicksEnabled = options.ticks.major.enabled;
- var interval = INTERVALS[minor];
var first = min;
- var last = max;
var ticks = [];
var time;
- if (!stepSize) {
- stepSize = determineStepSize(min, max, minor, capacity);
- }
-
// For 'week' unit, handle the first day of week option
if (weekday) {
first = +adapter.startOf(first, 'isoWeek', weekday);
- last = +adapter.startOf(last, 'isoWeek', weekday);
}
- // Align first/last ticks on unit
+ // Align first ticks on unit
first = +adapter.startOf(first, weekday ? 'day' : minor);
- last = +adapter.startOf(last, weekday ? 'day' : minor);
- // Make sure that the last tick include max
- if (last < max) {
- last = +adapter.add(last, 1, minor);
+ // Prevent browser from freezing in case user options request millions of milliseconds
+ if (adapter.diff(max, min, minor) > 100000 * stepSize) {
+ throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor;
}
- time = first;
-
- if (majorTicksEnabled && major && !weekday && !timeOpts.round) {
- // Align the first tick on the previous `minor` unit aligned on the `major` unit:
- // we first aligned time on the previous `major` unit then add the number of full
- // stepSize there is between first and the previous major time.
- time = +adapter.startOf(time, major);
- time = +adapter.add(time, ~~((first - time) / (interval.size * stepSize)) * stepSize, minor);
+ for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
+ ticks.push(time);
}
- for (; time < last; time = +adapter.add(time, stepSize, minor)) {
- ticks.push(+time);
+ if (time === max || options.bounds === 'ticks') {
+ ticks.push(time);
}
- ticks.push(+time);
-
return ticks;
}
/**
* Returns the start and end offsets from edges in the form of {start, end}
@@ -12918,46 +14252,61 @@
var start = 0;
var end = 0;
var first, last;
if (options.offset && ticks.length) {
- if (!options.time.min) {
- first = interpolate$1(table, 'time', ticks[0], 'pos');
- if (ticks.length === 1) {
- start = 1 - first;
- } else {
- start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
- }
+ first = interpolate$1(table, 'time', ticks[0], 'pos');
+ if (ticks.length === 1) {
+ start = 1 - first;
+ } else {
+ start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
}
- if (!options.time.max) {
- last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
- if (ticks.length === 1) {
- end = last;
- } else {
- end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
- }
+ last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
+ if (ticks.length === 1) {
+ end = last;
+ } else {
+ end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
}
}
- return {start: start, end: end};
+ return {start: start, end: end, factor: 1 / (start + 1 + end)};
}
+function setMajorTicks(scale, ticks, map, majorUnit) {
+ var adapter = scale._adapter;
+ var first = +adapter.startOf(ticks[0].value, majorUnit);
+ var last = ticks[ticks.length - 1].value;
+ var major, index;
+
+ for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
+ index = map[major];
+ if (index >= 0) {
+ ticks[index].major = true;
+ }
+ }
+ return ticks;
+}
+
function ticksFromTimestamps(scale, values, majorUnit) {
var ticks = [];
- var i, ilen, value, major;
+ var map = {};
+ var ilen = values.length;
+ var i, value;
- for (i = 0, ilen = values.length; i < ilen; ++i) {
+ for (i = 0; i < ilen; ++i) {
value = values[i];
- major = majorUnit ? value === +scale._adapter.startOf(value, majorUnit) : false;
+ map[value] = i;
ticks.push({
value: value,
- major: major
+ major: false
});
}
- return ticks;
+ // We set the major ticks separately from the above loop because calling startOf for every tick
+ // is expensive when there is a large number of ticks
+ return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
}
var defaultConfig$4 = {
position: 'bottom',
@@ -12980,11 +14329,10 @@
bounds: 'data',
adapters: {},
time: {
parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
- format: false, // DEPRECATED false == date objects, moment object, callback or a pattern string from https://momentjs.com/docs/#/parsing/string-format/
unit: false, // false == automatic or override with week, month, year, etc.
round: false, // none, or override with week, month, year, etc.
displayFormat: false, // DEPRECATED
isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
minUnit: 'millisecond',
@@ -13020,13 +14368,13 @@
var options = me.options;
var time = options.time || (options.time = {});
var adapter = me._adapter = new core_adapters._date(options.adapters.date);
// DEPRECATIONS: output a message only one time per update
- if (time.format) {
- console.warn('options.time.format is deprecated and replaced by options.time.parser.');
- }
+ deprecated$1('time scale', time.format, 'time.format', 'time.parser');
+ deprecated$1('time scale', time.min, 'time.min', 'ticks.min');
+ deprecated$1('time scale', time.max, 'time.max', 'ticks.max');
// Backward compatibility: before introducing adapter, `displayFormats` was
// supposed to contain *all* unit/string pairs but this can't be resolved
// when loading the scale (adapters are loaded afterward), so let's populate
// missing formats on update
@@ -13047,26 +14395,24 @@
determineDataLimits: function() {
var me = this;
var chart = me.chart;
var adapter = me._adapter;
- var timeOpts = me.options.time;
- var unit = timeOpts.unit || 'day';
+ var options = me.options;
+ var unit = options.time.unit || 'day';
var min = MAX_INTEGER;
var max = MIN_INTEGER;
var timestamps = [];
var datasets = [];
var labels = [];
- var i, j, ilen, jlen, data, timestamp;
- var dataLabels = chart.data.labels || [];
+ var i, j, ilen, jlen, data, timestamp, labelsAdded;
+ var dataLabels = me._getLabels();
- // Convert labels to timestamps
for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
labels.push(parse(me, dataLabels[i]));
}
- // Convert data to timestamps
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
if (chart.isDatasetVisible(i)) {
data = chart.data.datasets[i].data;
// Let's consider that all data have the same format.
@@ -13077,46 +14423,44 @@
timestamp = parse(me, data[j]);
timestamps.push(timestamp);
datasets[i][j] = timestamp;
}
} else {
- for (j = 0, jlen = labels.length; j < jlen; ++j) {
- timestamps.push(labels[j]);
- }
datasets[i] = labels.slice(0);
+ if (!labelsAdded) {
+ timestamps = timestamps.concat(labels);
+ labelsAdded = true;
+ }
}
} else {
datasets[i] = [];
}
}
if (labels.length) {
- // Sort labels **after** data have been converted
- labels = arrayUnique(labels).sort(sorter);
min = Math.min(min, labels[0]);
max = Math.max(max, labels[labels.length - 1]);
}
if (timestamps.length) {
- timestamps = arrayUnique(timestamps).sort(sorter);
+ timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
min = Math.min(min, timestamps[0]);
max = Math.max(max, timestamps[timestamps.length - 1]);
}
- min = parse(me, timeOpts.min) || min;
- max = parse(me, timeOpts.max) || max;
+ min = parse(me, getMin(options)) || min;
+ max = parse(me, getMax(options)) || max;
// In case there is no valid min/max, set limits based on unit time option
min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max;
// Make sure that max is strictly higher than min (required by the lookup table)
me.min = Math.min(min, max);
me.max = Math.max(min + 1, max);
// PRIVATE
- me._horizontal = me.isHorizontal();
me._table = [];
me._timestamps = {
data: timestamps,
datasets: datasets,
labels: labels
@@ -13126,35 +14470,35 @@
buildTicks: function() {
var me = this;
var min = me.min;
var max = me.max;
var options = me.options;
+ var tickOpts = options.ticks;
var timeOpts = options.time;
- var timestamps = [];
+ var timestamps = me._timestamps;
var ticks = [];
+ var capacity = me.getLabelCapacity(min);
+ var source = tickOpts.source;
+ var distribution = options.distribution;
var i, ilen, timestamp;
- switch (options.ticks.source) {
- case 'data':
- timestamps = me._timestamps.data;
- break;
- case 'labels':
- timestamps = me._timestamps.labels;
- break;
- case 'auto':
- default:
- timestamps = generate(me, min, max, me.getLabelCapacity(min), options);
+ if (source === 'data' || (source === 'auto' && distribution === 'series')) {
+ timestamps = timestamps.data;
+ } else if (source === 'labels') {
+ timestamps = timestamps.labels;
+ } else {
+ timestamps = generate(me, min, max, capacity);
}
if (options.bounds === 'ticks' && timestamps.length) {
min = timestamps[0];
max = timestamps[timestamps.length - 1];
}
// Enforce limits with user min/max options
- min = parse(me, timeOpts.min) || min;
- max = parse(me, timeOpts.max) || max;
+ min = parse(me, getMin(options)) || min;
+ max = parse(me, getMax(options)) || max;
// Remove ticks outside the min/max range
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
timestamp = timestamps[i];
if (timestamp >= min && timestamp <= max) {
@@ -13164,16 +14508,21 @@
me.min = min;
me.max = max;
// PRIVATE
- me._unit = timeOpts.unit || determineUnitForFormatting(me, ticks, timeOpts.minUnit, me.min, me.max);
- me._majorUnit = determineMajorUnit(me._unit);
- me._table = buildLookupTable(me._timestamps.data, min, max, options.distribution);
+ // determineUnitForFormatting relies on the number of ticks so we don't use it when
+ // autoSkip is enabled because we don't yet know what the final number of ticks will be
+ me._unit = timeOpts.unit || (tickOpts.autoSkip
+ ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
+ : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
+ me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
+ : determineMajorUnit(me._unit);
+ me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
me._offsets = computeOffsets(me._table, ticks, min, max, options);
- if (options.ticks.reverse) {
+ if (tickOpts.reverse) {
ticks.reverse();
}
return ticksFromTimestamps(me, ticks, me._majorUnit);
},
@@ -13208,16 +14557,21 @@
var options = me.options;
var formats = options.time.displayFormats;
var minorFormat = formats[me._unit];
var majorUnit = me._majorUnit;
var majorFormat = formats[majorUnit];
- var majorTime = +adapter.startOf(time, majorUnit);
- var majorTickOpts = options.ticks.major;
- var major = majorTickOpts.enabled && majorUnit && majorFormat && time === majorTime;
+ var tick = ticks[index];
+ var tickOpts = options.ticks;
+ var major = majorUnit && majorFormat && tick && tick.major;
var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat);
- var tickOpts = major ? majorTickOpts : options.ticks.minor;
- var formatter = valueOrDefault$c(tickOpts.callback, tickOpts.userCallback);
+ var nestedTickOpts = major ? tickOpts.major : tickOpts.minor;
+ var formatter = resolve$5([
+ nestedTickOpts.callback,
+ nestedTickOpts.userCallback,
+ tickOpts.callback,
+ tickOpts.userCallback
+ ]);
return formatter ? formatter(label, index, ticks) : label;
},
convertTicksToLabels: function(ticks) {
@@ -13234,17 +14588,13 @@
/**
* @private
*/
getPixelForOffset: function(time) {
var me = this;
- var isReverse = me.options.ticks.reverse;
- var size = me._horizontal ? me.width : me.height;
- var start = me._horizontal ? isReverse ? me.right : me.left : isReverse ? me.bottom : me.top;
+ var offsets = me._offsets;
var pos = interpolate$1(me._table, 'time', time, 'pos');
- var offset = size * (me._offsets.start + pos) / (me._offsets.start + 1 + me._offsets.end);
-
- return isReverse ? start - offset : start + offset;
+ return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
},
getPixelForValue: function(value, index, datasetIndex) {
var me = this;
var time = null;
@@ -13269,48 +14619,62 @@
null;
},
getValueForPixel: function(pixel) {
var me = this;
- var size = me._horizontal ? me.width : me.height;
- var start = me._horizontal ? me.left : me.top;
- var pos = (size ? (pixel - start) / size : 0) * (me._offsets.start + 1 + me._offsets.start) - me._offsets.end;
+ var offsets = me._offsets;
+ var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
var time = interpolate$1(me._table, 'pos', pos, 'time');
// DEPRECATION, we should return time directly
return me._adapter._create(time);
},
/**
- * Crude approximation of what the label width might be
* @private
*/
- getLabelWidth: function(label) {
+ _getLabelSize: function(label) {
var me = this;
var ticksOpts = me.options.ticks;
var tickLabelWidth = me.ctx.measureText(label).width;
- var angle = helpers$1.toRadians(ticksOpts.maxRotation);
+ var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
var cosRotation = Math.cos(angle);
var sinRotation = Math.sin(angle);
- var tickFontSize = valueOrDefault$c(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
+ var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
- return (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation);
+ return {
+ w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
+ h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
+ };
},
/**
+ * Crude approximation of what the label width might be
* @private
*/
+ getLabelWidth: function(label) {
+ return this._getLabelSize(label).w;
+ },
+
+ /**
+ * @private
+ */
getLabelCapacity: function(exampleTime) {
var me = this;
+ var timeOpts = me.options.time;
+ var displayFormats = timeOpts.displayFormats;
// pick the longest format (milliseconds) for guestimation
- var format = me.options.time.displayFormats.millisecond;
- var exampleLabel = me.tickFormatFunction(exampleTime, 0, [], format);
- var tickLabelWidth = me.getLabelWidth(exampleLabel);
- var innerWidth = me.isHorizontal() ? me.width : me.height;
- var capacity = Math.floor(innerWidth / tickLabelWidth);
+ var format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
+ var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format);
+ var size = me._getLabelSize(exampleLabel);
+ var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h);
+ if (me.options.offset) {
+ capacity--;
+ }
+
return capacity > 0 ? capacity : 1;
}
});
// INTERNAL: static default options, registered in src/index.js
@@ -13325,11 +14689,11 @@
time: scale_time
};
var moment = createCommonjsModule(function (module, exports) {
(function (global, factory) {
- module.exports = factory();
+ module.exports = factory() ;
}(commonjsGlobal, (function () {
var hookCallback;
function hooks () {
return hookCallback.apply(null, arguments);
@@ -17961,11 +19325,11 @@
add: function(time, amount, unit) {
return moment(time).add(amount, unit).valueOf();
},
diff: function(max, min, unit) {
- return moment.duration(moment(max).diff(moment(min))).as(unit);
+ return moment(max).diff(moment(min), unit);
},
startOf: function(time, unit, weekday) {
time = moment(time);
if (unit === 'isoWeek') {
@@ -18016,10 +19380,16 @@
boundary: function(source) {
var boundary = source.boundary;
var x = boundary ? boundary.x : null;
var y = boundary ? boundary.y : null;
+ if (helpers$1.isArray(boundary)) {
+ return function(point, i) {
+ return boundary[i];
+ };
+ }
+
return function(point) {
return {
x: x === null ? point.x : x,
y: y === null ? point.y : y,
};
@@ -18075,11 +19445,11 @@
default:
return false;
}
}
-function computeBoundary(source) {
+function computeLinearBoundary(source) {
var model = source.el._model || {};
var scale = source.el._scale || {};
var fill = source.fill;
var target = null;
var horizontal;
@@ -18096,12 +19466,10 @@
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
} else if (fill === 'end') {
target = model.scaleTop === undefined ? scale.top : model.scaleTop;
} else if (model.scaleZero !== undefined) {
target = model.scaleZero;
- } else if (scale.getBasePosition) {
- target = scale.getBasePosition();
} else if (scale.getBasePixel) {
target = scale.getBasePixel();
}
if (target !== undefined && target !== null) {
@@ -18119,10 +19487,48 @@
}
return null;
}
+function computeCircularBoundary(source) {
+ var scale = source.el._scale;
+ var options = scale.options;
+ var length = scale.chart.data.labels.length;
+ var fill = source.fill;
+ var target = [];
+ var start, end, center, i, point;
+
+ if (!length) {
+ return null;
+ }
+
+ start = options.ticks.reverse ? scale.max : scale.min;
+ end = options.ticks.reverse ? scale.min : scale.max;
+ center = scale.getPointPositionForValue(0, start);
+ for (i = 0; i < length; ++i) {
+ point = fill === 'start' || fill === 'end'
+ ? scale.getPointPositionForValue(i, fill === 'start' ? start : end)
+ : scale.getBasePosition(i);
+ if (options.gridLines.circular) {
+ point.cx = center.x;
+ point.cy = center.y;
+ point.angle = scale.getIndexAngle(i) - Math.PI / 2;
+ }
+ target.push(point);
+ }
+ return target;
+}
+
+function computeBoundary(source) {
+ var scale = source.el._scale || {};
+
+ if (scale.getPointPositionForValue) {
+ return computeCircularBoundary(source);
+ }
+ return computeLinearBoundary(source);
+}
+
function resolveTarget(sources, index, propagate) {
var source = sources[index];
var fill = source.fill;
var visited = [index];
var target;
@@ -18170,11 +19576,11 @@
function isDrawable(point) {
return point && !point.skip;
}
function drawArea(ctx, curve0, curve1, len0, len1) {
- var i;
+ var i, cx, cy, r;
if (!len0 || !len1) {
return;
}
@@ -18182,10 +19588,20 @@
ctx.moveTo(curve0[0].x, curve0[0].y);
for (i = 1; i < len0; ++i) {
helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
}
+ if (curve1[0].angle !== undefined) {
+ cx = curve1[0].cx;
+ cy = curve1[0].cy;
+ r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
+ for (i = len1 - 1; i > 0; --i) {
+ ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
+ }
+ return;
+ }
+
// joining the two area curves
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
// building opposite area curve (reverse)
for (i = len1 - 1; i > 0; --i) {
@@ -18198,21 +19614,26 @@
var span = view.spanGaps;
var curve0 = [];
var curve1 = [];
var len0 = 0;
var len1 = 0;
- var i, ilen, index, p0, p1, d0, d1;
+ var i, ilen, index, p0, p1, d0, d1, loopOffset;
ctx.beginPath();
- for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
+ for (i = 0, ilen = count; i < ilen; ++i) {
index = i % count;
p0 = points[index]._view;
p1 = mapper(p0, index, view);
d0 = isDrawable(p0);
d1 = isDrawable(p1);
+ if (loop && loopOffset === undefined && d0) {
+ loopOffset = i + 1;
+ ilen = count + loopOffset;
+ }
+
if (d0 && d1) {
len0 = curve0.push(p0);
len1 = curve1.push(p1);
} else if (len0 && len1) {
if (!span) {
@@ -18275,38 +19696,46 @@
source.boundary = computeBoundary(source);
source.mapper = createMapper(source);
}
},
- beforeDatasetDraw: function(chart, args) {
- var meta = args.meta.$filler;
- if (!meta) {
- return;
- }
-
+ beforeDatasetsDraw: function(chart) {
+ var metasets = chart._getSortedVisibleDatasetMetas();
var ctx = chart.ctx;
- var el = meta.el;
- var view = el._view;
- var points = el._children || [];
- var mapper = meta.mapper;
- var color = view.backgroundColor || core_defaults.global.defaultColor;
+ var meta, i, el, view, points, mapper, color;
- if (mapper && color && points.length) {
- helpers$1.canvas.clipArea(ctx, chart.chartArea);
- doFill(ctx, points, mapper, view, color, el._loop);
- helpers$1.canvas.unclipArea(ctx);
+ for (i = metasets.length - 1; i >= 0; --i) {
+ meta = metasets[i].$filler;
+
+ if (!meta || !meta.visible) {
+ continue;
+ }
+
+ el = meta.el;
+ view = el._view;
+ points = el._children || [];
+ mapper = meta.mapper;
+ color = view.backgroundColor || core_defaults.global.defaultColor;
+
+ if (mapper && color && points.length) {
+ helpers$1.canvas.clipArea(ctx, chart.chartArea);
+ doFill(ctx, points, mapper, view, color, el._loop);
+ helpers$1.canvas.unclipArea(ctx);
+ }
}
}
};
+var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter;
var noop$1 = helpers$1.noop;
-var valueOrDefault$d = helpers$1.valueOrDefault;
+var valueOrDefault$e = helpers$1.valueOrDefault;
core_defaults._set('global', {
legend: {
display: true,
position: 'top',
+ align: 'center',
fullWidth: true,
reverse: false,
weight: 1000,
// a callback that will handle
@@ -18338,44 +19767,55 @@
// lineDash
// lineDashOffset :
// lineJoin :
// lineWidth :
generateLabels: function(chart) {
- var data = chart.data;
- return helpers$1.isArray(data.datasets) ? data.datasets.map(function(dataset, i) {
+ var datasets = chart.data.datasets;
+ var options = chart.options.legend || {};
+ var usePointStyle = options.labels && options.labels.usePointStyle;
+
+ return chart._getSortedDatasetMetas().map(function(meta) {
+ var style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
+
return {
- text: dataset.label,
- fillStyle: (!helpers$1.isArray(dataset.backgroundColor) ? dataset.backgroundColor : dataset.backgroundColor[0]),
- hidden: !chart.isDatasetVisible(i),
- lineCap: dataset.borderCapStyle,
- lineDash: dataset.borderDash,
- lineDashOffset: dataset.borderDashOffset,
- lineJoin: dataset.borderJoinStyle,
- lineWidth: dataset.borderWidth,
- strokeStyle: dataset.borderColor,
- pointStyle: dataset.pointStyle,
+ text: datasets[meta.index].label,
+ fillStyle: style.backgroundColor,
+ hidden: !chart.isDatasetVisible(meta.index),
+ lineCap: style.borderCapStyle,
+ lineDash: style.borderDash,
+ lineDashOffset: style.borderDashOffset,
+ lineJoin: style.borderJoinStyle,
+ lineWidth: style.borderWidth,
+ strokeStyle: style.borderColor,
+ pointStyle: style.pointStyle,
+ rotation: style.rotation,
// Below is extra data used for toggling the datasets
- datasetIndex: i
+ datasetIndex: meta.index
};
- }, this) : [];
+ }, this);
}
}
},
legendCallback: function(chart) {
- var text = [];
- text.push('<ul class="' + chart.id + '-legend">');
- for (var i = 0; i < chart.data.datasets.length; i++) {
- text.push('<li><span style="background-color:' + chart.data.datasets[i].backgroundColor + '"></span>');
- if (chart.data.datasets[i].label) {
- text.push(chart.data.datasets[i].label);
+ var list = document.createElement('ul');
+ var datasets = chart.data.datasets;
+ var i, ilen, listItem, listItemSpan;
+
+ list.setAttribute('class', chart.id + '-legend');
+
+ for (i = 0, ilen = datasets.length; i < ilen; i++) {
+ listItem = list.appendChild(document.createElement('li'));
+ listItemSpan = listItem.appendChild(document.createElement('span'));
+ listItemSpan.style.backgroundColor = datasets[i].backgroundColor;
+ if (datasets[i].label) {
+ listItem.appendChild(document.createTextNode(datasets[i].label));
}
- text.push('</li>');
}
- text.push('</ul>');
- return text.join('');
+
+ return list.outerHTML;
}
});
/**
* Helper function to get the box width based on the usePointStyle option
@@ -18393,22 +19833,23 @@
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
*/
var Legend = core_element.extend({
initialize: function(config) {
- helpers$1.extend(this, config);
+ var me = this;
+ helpers$1.extend(me, config);
// Contains hit boxes for each dataset (in dataset order)
- this.legendHitBoxes = [];
+ me.legendHitBoxes = [];
/**
* @private
*/
- this._hoveredItem = null;
+ me._hoveredItem = null;
// Are we in doughnut mode which has a different data type
- this.doughnutMode = false;
+ me.doughnutMode = false;
},
// These methods are ordered by lifecycle. Utilities then follow.
// Any function defined here is inherited by all legend types.
// Any function can be extended by the legend type
@@ -18527,83 +19968,86 @@
minSize.width = display ? 10 : 0;
minSize.height = me.maxHeight; // fill all the height
}
// Increase sizes here
- if (display) {
- ctx.font = labelFont.string;
+ if (!display) {
+ me.width = minSize.width = me.height = minSize.height = 0;
+ return;
+ }
+ ctx.font = labelFont.string;
- if (isHorizontal) {
- // Labels
+ if (isHorizontal) {
+ // Labels
- // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
- var lineWidths = me.lineWidths = [0];
- var totalHeight = 0;
+ // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
+ var lineWidths = me.lineWidths = [0];
+ var totalHeight = 0;
- ctx.textAlign = 'left';
- ctx.textBaseline = 'top';
+ ctx.textAlign = 'left';
+ ctx.textBaseline = 'middle';
- helpers$1.each(me.legendItems, function(legendItem, i) {
- var boxWidth = getBoxWidth(labelOpts, fontSize);
- var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+ helpers$1.each(me.legendItems, function(legendItem, i) {
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
- if (i === 0 || lineWidths[lineWidths.length - 1] + width + labelOpts.padding > minSize.width) {
- totalHeight += fontSize + labelOpts.padding;
- lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = labelOpts.padding;
- }
+ if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) {
+ totalHeight += fontSize + labelOpts.padding;
+ lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
+ }
- // Store the hitbox width and height here. Final position will be updated in `draw`
- hitboxes[i] = {
- left: 0,
- top: 0,
- width: width,
- height: fontSize
- };
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: width,
+ height: fontSize
+ };
- lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
- });
+ lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
+ });
- minSize.height += totalHeight;
+ minSize.height += totalHeight;
- } else {
- var vPadding = labelOpts.padding;
- var columnWidths = me.columnWidths = [];
- var totalWidth = labelOpts.padding;
- var currentColWidth = 0;
- var currentColHeight = 0;
- var itemHeight = fontSize + vPadding;
+ } else {
+ var vPadding = labelOpts.padding;
+ var columnWidths = me.columnWidths = [];
+ var columnHeights = me.columnHeights = [];
+ var totalWidth = labelOpts.padding;
+ var currentColWidth = 0;
+ var currentColHeight = 0;
- helpers$1.each(me.legendItems, function(legendItem, i) {
- var boxWidth = getBoxWidth(labelOpts, fontSize);
- var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+ helpers$1.each(me.legendItems, function(legendItem, i) {
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
- // If too tall, go to new column
- if (i > 0 && currentColHeight + itemHeight > minSize.height - vPadding) {
- totalWidth += currentColWidth + labelOpts.padding;
- columnWidths.push(currentColWidth); // previous column width
+ // If too tall, go to new column
+ if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) {
+ totalWidth += currentColWidth + labelOpts.padding;
+ columnWidths.push(currentColWidth); // previous column width
+ columnHeights.push(currentColHeight);
+ currentColWidth = 0;
+ currentColHeight = 0;
+ }
- currentColWidth = 0;
- currentColHeight = 0;
- }
+ // Get max width
+ currentColWidth = Math.max(currentColWidth, itemWidth);
+ currentColHeight += fontSize + vPadding;
- // Get max width
- currentColWidth = Math.max(currentColWidth, itemWidth);
- currentColHeight += itemHeight;
+ // Store the hitbox width and height here. Final position will be updated in `draw`
+ hitboxes[i] = {
+ left: 0,
+ top: 0,
+ width: itemWidth,
+ height: fontSize
+ };
+ });
- // Store the hitbox width and height here. Final position will be updated in `draw`
- hitboxes[i] = {
- left: 0,
- top: 0,
- width: itemWidth,
- height: fontSize
- };
- });
-
- totalWidth += currentColWidth;
- columnWidths.push(currentColWidth);
- minSize.width += totalWidth;
- }
+ totalWidth += currentColWidth;
+ columnWidths.push(currentColWidth);
+ columnHeights.push(currentColHeight);
+ minSize.width += totalWidth;
}
me.width = minSize.width;
me.height = minSize.height;
},
@@ -18620,143 +20064,167 @@
var opts = me.options;
var labelOpts = opts.labels;
var globalDefaults = core_defaults.global;
var defaultColor = globalDefaults.defaultColor;
var lineDefault = globalDefaults.elements.line;
+ var legendHeight = me.height;
+ var columnHeights = me.columnHeights;
var legendWidth = me.width;
var lineWidths = me.lineWidths;
- if (opts.display) {
- var ctx = me.ctx;
- var fontColor = valueOrDefault$d(labelOpts.fontColor, globalDefaults.defaultFontColor);
- var labelFont = helpers$1.options._parseFont(labelOpts);
- var fontSize = labelFont.size;
- var cursor;
+ if (!opts.display) {
+ return;
+ }
- // Canvas setup
- ctx.textAlign = 'left';
- ctx.textBaseline = 'middle';
- ctx.lineWidth = 0.5;
- ctx.strokeStyle = fontColor; // for strikethrough effect
- ctx.fillStyle = fontColor; // render in correct colour
- ctx.font = labelFont.string;
+ var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width);
+ var ctx = me.ctx;
+ var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor);
+ var labelFont = helpers$1.options._parseFont(labelOpts);
+ var fontSize = labelFont.size;
+ var cursor;
- var boxWidth = getBoxWidth(labelOpts, fontSize);
- var hitboxes = me.legendHitBoxes;
+ // Canvas setup
+ ctx.textAlign = rtlHelper.textAlign('left');
+ ctx.textBaseline = 'middle';
+ ctx.lineWidth = 0.5;
+ ctx.strokeStyle = fontColor; // for strikethrough effect
+ ctx.fillStyle = fontColor; // render in correct colour
+ ctx.font = labelFont.string;
- // current position
- var drawLegendBox = function(x, y, legendItem) {
- if (isNaN(boxWidth) || boxWidth <= 0) {
- return;
- }
+ var boxWidth = getBoxWidth(labelOpts, fontSize);
+ var hitboxes = me.legendHitBoxes;
- // Set the ctx for the box
- ctx.save();
+ // current position
+ var drawLegendBox = function(x, y, legendItem) {
+ if (isNaN(boxWidth) || boxWidth <= 0) {
+ return;
+ }
- var lineWidth = valueOrDefault$d(legendItem.lineWidth, lineDefault.borderWidth);
- ctx.fillStyle = valueOrDefault$d(legendItem.fillStyle, defaultColor);
- ctx.lineCap = valueOrDefault$d(legendItem.lineCap, lineDefault.borderCapStyle);
- ctx.lineDashOffset = valueOrDefault$d(legendItem.lineDashOffset, lineDefault.borderDashOffset);
- ctx.lineJoin = valueOrDefault$d(legendItem.lineJoin, lineDefault.borderJoinStyle);
- ctx.lineWidth = lineWidth;
- ctx.strokeStyle = valueOrDefault$d(legendItem.strokeStyle, defaultColor);
+ // Set the ctx for the box
+ ctx.save();
- if (ctx.setLineDash) {
- // IE 9 and 10 do not support line dash
- ctx.setLineDash(valueOrDefault$d(legendItem.lineDash, lineDefault.borderDash));
- }
+ var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth);
+ ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor);
+ ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle);
+ ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset);
+ ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle);
+ ctx.lineWidth = lineWidth;
+ ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor);
- if (opts.labels && opts.labels.usePointStyle) {
- // Recalculate x and y for drawPoint() because its expecting
- // x and y to be center of figure (instead of top left)
- var radius = boxWidth * Math.SQRT2 / 2;
- var centerX = x + boxWidth / 2;
- var centerY = y + fontSize / 2;
+ if (ctx.setLineDash) {
+ // IE 9 and 10 do not support line dash
+ ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash));
+ }
- // Draw pointStyle as legend symbol
- helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
- } else {
- // Draw box as legend symbol
- if (lineWidth !== 0) {
- ctx.strokeRect(x, y, boxWidth, fontSize);
- }
- ctx.fillRect(x, y, boxWidth, fontSize);
+ if (labelOpts && labelOpts.usePointStyle) {
+ // Recalculate x and y for drawPoint() because its expecting
+ // x and y to be center of figure (instead of top left)
+ var radius = boxWidth * Math.SQRT2 / 2;
+ var centerX = rtlHelper.xPlus(x, boxWidth / 2);
+ var centerY = y + fontSize / 2;
+
+ // Draw pointStyle as legend symbol
+ helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
+ } else {
+ // Draw box as legend symbol
+ ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
+ if (lineWidth !== 0) {
+ ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
}
+ }
- ctx.restore();
- };
- var fillText = function(x, y, legendItem, textWidth) {
- var halfFontSize = fontSize / 2;
- var xLeft = boxWidth + halfFontSize + x;
- var yMiddle = y + halfFontSize;
+ ctx.restore();
+ };
- ctx.fillText(legendItem.text, xLeft, yMiddle);
+ var fillText = function(x, y, legendItem, textWidth) {
+ var halfFontSize = fontSize / 2;
+ var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
+ var yMiddle = y + halfFontSize;
- if (legendItem.hidden) {
- // Strikethrough the text if hidden
- ctx.beginPath();
- ctx.lineWidth = 2;
- ctx.moveTo(xLeft, yMiddle);
- ctx.lineTo(xLeft + textWidth, yMiddle);
- ctx.stroke();
- }
- };
+ ctx.fillText(legendItem.text, xLeft, yMiddle);
- // Horizontal
- var isHorizontal = me.isHorizontal();
- if (isHorizontal) {
- cursor = {
- x: me.left + ((legendWidth - lineWidths[0]) / 2) + labelOpts.padding,
- y: me.top + labelOpts.padding,
- line: 0
- };
- } else {
- cursor = {
- x: me.left + labelOpts.padding,
- y: me.top + labelOpts.padding,
- line: 0
- };
+ if (legendItem.hidden) {
+ // Strikethrough the text if hidden
+ ctx.beginPath();
+ ctx.lineWidth = 2;
+ ctx.moveTo(xLeft, yMiddle);
+ ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle);
+ ctx.stroke();
}
+ };
- var itemHeight = fontSize + labelOpts.padding;
- helpers$1.each(me.legendItems, function(legendItem, i) {
- var textWidth = ctx.measureText(legendItem.text).width;
- var width = boxWidth + (fontSize / 2) + textWidth;
- var x = cursor.x;
- var y = cursor.y;
+ var alignmentOffset = function(dimension, blockSize) {
+ switch (opts.align) {
+ case 'start':
+ return labelOpts.padding;
+ case 'end':
+ return dimension - blockSize;
+ default: // center
+ return (dimension - blockSize + labelOpts.padding) / 2;
+ }
+ };
- // Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
- // instead of me.right and me.bottom because me.width and me.height
- // may have been changed since me.minSize was calculated
- if (isHorizontal) {
- if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
- y = cursor.y += itemHeight;
- cursor.line++;
- x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2) + labelOpts.padding;
- }
- } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
- x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
- y = cursor.y = me.top + labelOpts.padding;
+ // Horizontal
+ var isHorizontal = me.isHorizontal();
+ if (isHorizontal) {
+ cursor = {
+ x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
+ y: me.top + labelOpts.padding,
+ line: 0
+ };
+ } else {
+ cursor = {
+ x: me.left + labelOpts.padding,
+ y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
+ line: 0
+ };
+ }
+
+ helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection);
+
+ var itemHeight = fontSize + labelOpts.padding;
+ helpers$1.each(me.legendItems, function(legendItem, i) {
+ var textWidth = ctx.measureText(legendItem.text).width;
+ var width = boxWidth + (fontSize / 2) + textWidth;
+ var x = cursor.x;
+ var y = cursor.y;
+
+ rtlHelper.setWidth(me.minSize.width);
+
+ // Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
+ // instead of me.right and me.bottom because me.width and me.height
+ // may have been changed since me.minSize was calculated
+ if (isHorizontal) {
+ if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
+ y = cursor.y += itemHeight;
cursor.line++;
+ x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
}
+ } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
+ x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
+ cursor.line++;
+ y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
+ }
- drawLegendBox(x, y, legendItem);
+ var realX = rtlHelper.x(x);
- hitboxes[i].left = x;
- hitboxes[i].top = y;
+ drawLegendBox(realX, y, legendItem);
- // Fill the actual label
- fillText(x, y, legendItem, textWidth);
+ hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width);
+ hitboxes[i].top = y;
- if (isHorizontal) {
- cursor.x += width + labelOpts.padding;
- } else {
- cursor.y += itemHeight;
- }
+ // Fill the actual label
+ fillText(realX, y, legendItem, textWidth);
- });
- }
+ if (isHorizontal) {
+ cursor.x += width + labelOpts.padding;
+ } else {
+ cursor.y += itemHeight;
+ }
+ });
+
+ helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection);
},
/**
* @private
*/
@@ -18990,27 +20458,24 @@
beforeFit: noop$2,
fit: function() {
var me = this;
var opts = me.options;
- var display = opts.display;
- var minSize = me.minSize;
- var lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
- var fontOpts = helpers$1.options._parseFont(opts);
- var textSize = display ? (lineCount * fontOpts.lineHeight) + (opts.padding * 2) : 0;
+ var minSize = me.minSize = {};
+ var isHorizontal = me.isHorizontal();
+ var lineCount, textSize;
- if (me.isHorizontal()) {
- minSize.width = me.maxWidth; // fill all the width
- minSize.height = textSize;
- } else {
- minSize.width = textSize;
- minSize.height = me.maxHeight; // fill all the height
+ if (!opts.display) {
+ me.width = minSize.width = me.height = minSize.height = 0;
+ return;
}
- me.width = minSize.width;
- me.height = minSize.height;
+ lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
+ textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2;
+ me.width = minSize.width = isHorizontal ? me.maxWidth : textSize;
+ me.height = minSize.height = isHorizontal ? textSize : me.maxHeight;
},
afterFit: noop$2,
// Shared Methods
isHorizontal: function() {
@@ -19022,55 +20487,57 @@
draw: function() {
var me = this;
var ctx = me.ctx;
var opts = me.options;
- if (opts.display) {
- var fontOpts = helpers$1.options._parseFont(opts);
- var lineHeight = fontOpts.lineHeight;
- var offset = lineHeight / 2 + opts.padding;
- var rotation = 0;
- var top = me.top;
- var left = me.left;
- var bottom = me.bottom;
- var right = me.right;
- var maxWidth, titleX, titleY;
+ if (!opts.display) {
+ return;
+ }
- ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
- ctx.font = fontOpts.string;
+ var fontOpts = helpers$1.options._parseFont(opts);
+ var lineHeight = fontOpts.lineHeight;
+ var offset = lineHeight / 2 + opts.padding;
+ var rotation = 0;
+ var top = me.top;
+ var left = me.left;
+ var bottom = me.bottom;
+ var right = me.right;
+ var maxWidth, titleX, titleY;
- // Horizontal
- if (me.isHorizontal()) {
- titleX = left + ((right - left) / 2); // midpoint of the width
- titleY = top + offset;
- maxWidth = right - left;
- } else {
- titleX = opts.position === 'left' ? left + offset : right - offset;
- titleY = top + ((bottom - top) / 2);
- maxWidth = bottom - top;
- rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
- }
+ ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
+ ctx.font = fontOpts.string;
- ctx.save();
- ctx.translate(titleX, titleY);
- ctx.rotate(rotation);
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
+ // Horizontal
+ if (me.isHorizontal()) {
+ titleX = left + ((right - left) / 2); // midpoint of the width
+ titleY = top + offset;
+ maxWidth = right - left;
+ } else {
+ titleX = opts.position === 'left' ? left + offset : right - offset;
+ titleY = top + ((bottom - top) / 2);
+ maxWidth = bottom - top;
+ rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
+ }
- var text = opts.text;
- if (helpers$1.isArray(text)) {
- var y = 0;
- for (var i = 0; i < text.length; ++i) {
- ctx.fillText(text[i], 0, y, maxWidth);
- y += lineHeight;
- }
- } else {
- ctx.fillText(text, 0, 0, maxWidth);
- }
+ ctx.save();
+ ctx.translate(titleX, titleY);
+ ctx.rotate(rotation);
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
- ctx.restore();
+ var text = opts.text;
+ if (helpers$1.isArray(text)) {
+ var y = 0;
+ for (var i = 0; i < text.length; ++i) {
+ ctx.fillText(text[i], 0, y, maxWidth);
+ y += lineHeight;
+ }
+ } else {
+ ctx.fillText(text, 0, 0, maxWidth);
}
+
+ ctx.restore();
}
});
function createNewTitleBlockAndAttach(chart, titleOpts) {
var title = new Title({
@@ -19138,10 +20605,10 @@
core_controller.helpers = helpers$1;
// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
-core_helpers(core_controller);
+core_helpers();
core_controller._adapters = core_adapters;
core_controller.Animation = core_animation;
core_controller.animationService = core_animations;
core_controller.controllers = controllers;